116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { createGoogleSheetsMcpConfig, readConfigFromEnv, readHttpConfigFromEnv } from "./config.js";
|
|
|
|
const serviceAccount = {
|
|
client_email: "service@example.test",
|
|
private_key: "-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----",
|
|
project_id: "project-1",
|
|
};
|
|
|
|
describe("Google Sheets MCP config", () => {
|
|
it("reads inline service-account JSON and de-duplicates allowed spreadsheet IDs", () => {
|
|
const config = createGoogleSheetsMcpConfig({
|
|
serviceAccountJson: JSON.stringify(serviceAccount),
|
|
allowedSpreadsheetIds: "sheet-1,sheet-2\nsheet-1",
|
|
});
|
|
|
|
expect(config.serviceAccount.client_email).toBe("service@example.test");
|
|
expect(config.allowedSpreadsheetIds).toEqual(["sheet-1", "sheet-2"]);
|
|
expect(config.secretRedactions).toContain(serviceAccount.private_key);
|
|
});
|
|
|
|
it("reads service-account JSON from a path", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "paperclip-sheets-mcp-"));
|
|
try {
|
|
const file = join(dir, "service-account.json");
|
|
writeFileSync(file, JSON.stringify(serviceAccount));
|
|
|
|
const config = readConfigFromEnv(
|
|
{
|
|
GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON_PATH: file,
|
|
GOOGLE_SHEETS_ALLOWED_SPREADSHEET_IDS: "sheet-1",
|
|
} as NodeJS.ProcessEnv,
|
|
[],
|
|
);
|
|
|
|
expect(config.serviceAccount.project_id).toBe("project-1");
|
|
expect(config.allowedSpreadsheetIds).toEqual(["sheet-1"]);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("requires an allowlist", () => {
|
|
expect(() =>
|
|
createGoogleSheetsMcpConfig({
|
|
serviceAccountJson: JSON.stringify(serviceAccount),
|
|
allowedSpreadsheetIds: "",
|
|
})
|
|
).toThrow("At least one allowed spreadsheet ID is required.");
|
|
});
|
|
|
|
it("reads HTTP host, port, and token while reusing MCP config env", () => {
|
|
const config = readHttpConfigFromEnv(
|
|
{
|
|
GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON: JSON.stringify(serviceAccount),
|
|
GOOGLE_SHEETS_ALLOWED_SPREADSHEET_IDS: "sheet-1",
|
|
GOOGLE_SHEETS_MCP_PORT: "9911",
|
|
GOOGLE_SHEETS_MCP_HOST: "0.0.0.0",
|
|
GOOGLE_SHEETS_MCP_TOKEN: " local-token ",
|
|
} as NodeJS.ProcessEnv,
|
|
[],
|
|
);
|
|
|
|
expect(config.port).toBe(9911);
|
|
expect(config.host).toBe("0.0.0.0");
|
|
expect(config.token).toBe("local-token");
|
|
expect(config.mcpConfig.allowedSpreadsheetIds).toEqual(["sheet-1"]);
|
|
});
|
|
|
|
it("lets PORT override GOOGLE_SHEETS_MCP_PORT for platform hosts", () => {
|
|
const config = readHttpConfigFromEnv(
|
|
{
|
|
GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON: JSON.stringify(serviceAccount),
|
|
GOOGLE_SHEETS_ALLOWED_SPREADSHEET_IDS: "sheet-1",
|
|
PORT: "8080",
|
|
GOOGLE_SHEETS_MCP_PORT: "9911",
|
|
} as NodeJS.ProcessEnv,
|
|
[],
|
|
);
|
|
|
|
expect(config.port).toBe(8080);
|
|
expect(config.host).toBe("127.0.0.1");
|
|
expect(config.token).toBeNull();
|
|
});
|
|
|
|
it("requires a token for non-loopback HTTP binds", () => {
|
|
expect(() =>
|
|
readHttpConfigFromEnv(
|
|
{
|
|
GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON: JSON.stringify(serviceAccount),
|
|
GOOGLE_SHEETS_ALLOWED_SPREADSHEET_IDS: "sheet-1",
|
|
GOOGLE_SHEETS_MCP_HOST: "0.0.0.0",
|
|
} as NodeJS.ProcessEnv,
|
|
[],
|
|
)
|
|
).toThrow("GOOGLE_SHEETS_MCP_TOKEN is required when GOOGLE_SHEETS_MCP_HOST is not loopback.");
|
|
});
|
|
|
|
it("allows loopback HTTP binds without a token", () => {
|
|
const config = readHttpConfigFromEnv(
|
|
{
|
|
GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON: JSON.stringify(serviceAccount),
|
|
GOOGLE_SHEETS_ALLOWED_SPREADSHEET_IDS: "sheet-1",
|
|
GOOGLE_SHEETS_MCP_HOST: "::1",
|
|
} as NodeJS.ProcessEnv,
|
|
[],
|
|
);
|
|
|
|
expect(config.host).toBe("::1");
|
|
expect(config.token).toBeNull();
|
|
});
|
|
});
|