Skip to content

feat: Add --timeout option to CLI #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/md-to-notion-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async function main(
useGithubLinkReplacer: string
delete: boolean
renew: boolean
timeout: number
}
) {
let replacer
Expand Down Expand Up @@ -57,7 +58,8 @@ async function main(
}

if (dir) {
const notion = new Client({ auth: options.token })
const timeoutMs = parseInt(String(options.timeout), 10)
Copy link
Preview

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding validation to check if the result of parseInt is NaN and handle it appropriately, such as reverting to the default timeout or throwing a clear error.

Suggested change
const timeoutMs = parseInt(String(options.timeout), 10)
let timeoutMs = parseInt(String(options.timeout), 10)
if (isNaN(timeoutMs)) {
timeoutMs = 30000 // Default timeout value in milliseconds
}

Copilot uses AI. Check for mistakes.

const notion = new Client({ auth: options.token, timeoutMs })
if (options.renew) {
await archiveChildPages(notion, options.pageId)
}
Expand Down Expand Up @@ -108,6 +110,12 @@ program
false
)
.option("-n, --renew", "Delete all pages in Notion before sync", false)
.option("--timeout <number>", "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)
}
1 change: 1 addition & 0 deletions temp_md_files_manual_test/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Test File for Manual Timeout Test
99 changes: 99 additions & 0 deletions test/md-to-notion-cli.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof Client>

// 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<typeof Client>
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 () => {
Copy link
Preview

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider documenting that the timeout value is truncated to an integer when a non-integer input is provided, to clarify the behavior for future maintainers and users.

Copilot uses AI. Check for mistakes.

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,
})
})
})
Loading