diff --git a/src/md-to-notion-cli.ts b/src/md-to-notion-cli.ts index eb83c93..4dfa57d 100755 --- a/src/md-to-notion-cli.ts +++ b/src/md-to-notion-cli.ts @@ -25,6 +25,7 @@ async function main( useGithubLinkReplacer: string delete: boolean renew: boolean + timeout: number } ) { let replacer @@ -57,7 +58,8 @@ async function main( } if (dir) { - const notion = new Client({ auth: options.token }) + const timeoutMs = parseInt(String(options.timeout), 10) + const notion = new Client({ auth: options.token, timeoutMs }) if (options.renew) { await archiveChildPages(notion, options.pageId) } @@ -108,6 +110,12 @@ program false ) .option("-n, --renew", "Delete all pages in Notion before sync", false) + .option("--timeout ", "Timeout for API calls in milliseconds", "10000") .action(main) -program.parse(process.argv) +// Export for testing purposes +export { program, main } + +if (require.main === module) { + program.parse(process.argv) +} diff --git a/temp_md_files_manual_test/test.md b/temp_md_files_manual_test/test.md new file mode 100644 index 0000000..54265f2 --- /dev/null +++ b/temp_md_files_manual_test/test.md @@ -0,0 +1 @@ +# Test File for Manual Timeout Test diff --git a/test/md-to-notion-cli.test.ts b/test/md-to-notion-cli.test.ts new file mode 100644 index 0000000..e0f2c08 --- /dev/null +++ b/test/md-to-notion-cli.test.ts @@ -0,0 +1,99 @@ +jest.mock("@notionhq/client") // Ensure this is at the very top + +import { Client } from "@notionhq/client" // Import type, actual is mocked +import { Command } from "commander" + +// Type alias for the mocked client, will be assigned in beforeEach +let MockedClient: jest.MockedClass + +// Mock 'readMarkdownFiles' and other functions to avoid side effects +// These mocks are hoisted by Jest, so they apply to any subsequent require +jest.mock("../src/index", () => ({ + ...jest.requireActual("../src/index"), + readMarkdownFiles: jest.fn().mockReturnValue({ name: "mockDir", files: [], subfolders: [] }), + syncToNotion: jest.fn().mockResolvedValue(undefined), + printFolderHierarchy: jest.fn(), +})) + +jest.mock("../src/sync-to-notion", () => ({ + collectCurrentFiles: jest.fn().mockResolvedValue(new Map()), + archiveChildPages: jest.fn().mockResolvedValue(undefined), +})) + +describe("md-to-notion-cli", () => { + let program: Command + let originalArgv: string[] + let consoleLogSpy: jest.SpyInstance + let consoleErrorSpy: jest.SpyInstance + let processExitSpy: jest.SpyInstance + + beforeEach(() => { + jest.resetModules() // Reset modules first + + // Now require the mocked Client and the program + MockedClient = require("@notionhq/client").Client as jest.MockedClass + program = require("../src/md-to-notion-cli").program + + // Clear mocks for each test + MockedClient.mockClear() + // jest.clearAllMocks() // This might be too broad if other mocks are needed across tests in a suite + + originalArgv = [...process.argv] + process.argv = ["node", "md-to-notion-cli.js", "mockDir"] + + consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {}) + consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {}) + processExitSpy = jest.spyOn(process, "exit").mockImplementation((() => {}) as (code?: string | number | null | undefined) => never) + }) + + afterEach(() => { + process.argv = originalArgv + consoleLogSpy.mockRestore() + consoleErrorSpy.mockRestore() + processExitSpy.mockRestore() + }) + + it("should call Notion Client with default timeout (10000ms) when --timeout is not provided", async () => { + const testArgs = ["node", "md-to-notion-cli.js", "mockDir", "--token", "test-token", "--page-id", "test-page-id"] + await program.parseAsync(testArgs, { from: "user" }) + + expect(MockedClient).toHaveBeenCalledTimes(1) + expect(MockedClient).toHaveBeenCalledWith({ + auth: "test-token", + timeoutMs: 10000, + }) + }) + + it("should call Notion Client with specified timeout when --timeout is provided", async () => { + const testArgs = ["node", "md-to-notion-cli.js", "mockDir", "--token", "test-token", "--page-id", "test-page-id", "--timeout", "5000"] + await program.parseAsync(testArgs, { from: "user" }) + + expect(MockedClient).toHaveBeenCalledTimes(1) + expect(MockedClient).toHaveBeenCalledWith({ + auth: "test-token", + timeoutMs: 5000, + }) + }) + + it("should call Notion Client with 1ms timeout when --timeout 1 is provided", async () => { + const testArgs = ["node", "md-to-notion-cli.js", "mockDir", "--token", "test-token", "--page-id", "test-page-id", "--timeout", "1"] + await program.parseAsync(testArgs, { from: "user" }) + + expect(MockedClient).toHaveBeenCalledTimes(1) + expect(MockedClient).toHaveBeenCalledWith({ + auth: "test-token", + timeoutMs: 1, + }) + }) + + it("should parse timeout as an integer", async () => { + const testArgs = ["node", "md-to-notion-cli.js", "mockDir", "--token", "test-token", "--page-id", "test-page-id", "--timeout", "123.45"] + await program.parseAsync(testArgs, { from: "user" }) + + expect(MockedClient).toHaveBeenCalledTimes(1) + expect(MockedClient).toHaveBeenCalledWith({ + auth: "test-token", + timeoutMs: 123, + }) + }) +})