diff --git a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx index 8b155975cf..43da508ffb 100644 --- a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx +++ b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx @@ -30,6 +30,16 @@ import { const MAX_KEY_NAME_LENGTH = 200; const MAX_SELECTED_MODELS = 500; +function toLocalDateTimeInputValue(value: string | null | undefined): string { + if (!value) return ""; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return ""; + const pad = (n: number) => String(n).padStart(2, "0"); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad( + date.getHours() + )}:${pad(date.getMinutes())}`; +} + // Debounce hook for search optimization function useDebouncedValue(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value); @@ -2128,15 +2138,32 @@ const PermissionsModal = memo(function PermissionsModal({ Key will automatically stop working after this date.

- { - const val = e.target.value; - setExpiresAt(val ? new Date(val).toISOString() : ""); - }} - className="w-full px-2 py-1.5 text-sm border border-border rounded-md bg-background text-text-main" - /> +
+ { + const val = e.target.value; + if (!val) { + setExpiresAt(""); + return; + } + const date = new Date(val); + if (!Number.isNaN(date.getTime())) { + setExpiresAt(date.toISOString()); + } + }} + className="min-w-0 flex-1 px-2 py-1.5 text-sm border border-border rounded-md bg-background text-text-main" + /> + +
{/* Management Access */}
diff --git a/tests/unit/api-manager-page-static.test.ts b/tests/unit/api-manager-page-static.test.ts index 43a2cc2f33..29d920e82f 100644 --- a/tests/unit/api-manager-page-static.test.ts +++ b/tests/unit/api-manager-page-static.test.ts @@ -35,6 +35,21 @@ test("permissions modal uses i18n for management access description", () => { assert.doesNotMatch(managementBlock, /Allow this API key to manage OmniRoute configuration\./); }); +test("permissions modal converts API key expiration ISO timestamps to local datetime input values", () => { + const source = readApiManagerPage(); + const expirationBlock = source.slice( + source.indexOf("{/* Expiration Date */}", source.indexOf("const PermissionsModal")), + source.indexOf("{/* Management Access */}", source.indexOf("const PermissionsModal")) + ); + + assert.match(expirationBlock, /value=\{toLocalDateTimeInputValue\(expiresAt\)\}/); + assert.match(expirationBlock, /const date = new Date\(val\)/); + assert.match(expirationBlock, /setExpiresAt\(date\.toISOString\(\)\)/); + assert.match(expirationBlock, /onClick=\{\(\) => setExpiresAt\(""\)\}/); + assert.match(expirationBlock, /\{tc\("clear"\)\}/); + assert.doesNotMatch(expirationBlock, /expiresAt\.slice\(0, 16\)/); +}); + test("permissions modal switch buttons declare button type", () => { const source = readApiManagerPage(); const modalStart = source.indexOf("const PermissionsModal"); diff --git a/tests/unit/batch-processor-expiration.test.ts b/tests/unit/batch-processor-expiration.test.ts index ec884b5f1f..4c8281ce98 100644 --- a/tests/unit/batch-processor-expiration.test.ts +++ b/tests/unit/batch-processor-expiration.test.ts @@ -1,6 +1,5 @@ -import { afterEach, describe, it } from "node:test"; +import { describe, it } from "node:test"; import assert from "node:assert"; -import { deleteFile, listFiles } from "@/lib/localDb"; // Helper functions from batchProcessor.ts function parseBatchWindowSeconds(window: string | null | undefined): number { @@ -18,15 +17,6 @@ function parseBatchWindowSeconds(window: string | null | undefined): number { } describe("Batch Processor - File Expiration", () => { - afterEach(() => { - const allFiles = listFiles({ limit: 1000 }); - for (const f of allFiles) { - if (f.filename?.includes("test-batch-")) { - deleteFile(f.id); - } - } - }); - describe("getBatchOutputExpiresAt logic", () => { it("should calculate output expiration as 30 days from batch completion", () => { const completedAt = Math.floor(Date.now() / 1000) - 5 * 24 * 60 * 60; // 5 days ago diff --git a/tests/unit/console-log-levels.test.ts b/tests/unit/console-log-levels.test.ts index 119da1f1ff..a691d7e3bd 100644 --- a/tests/unit/console-log-levels.test.ts +++ b/tests/unit/console-log-levels.test.ts @@ -8,7 +8,13 @@ import { updateSettings } from "../../src/lib/db/settings"; const TEST_LOG_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-console-log-levels-")); const TEST_LOG_PATH = path.join(TEST_LOG_DIR, "app.log"); -process.once("exit", () => fs.rmSync(TEST_LOG_DIR, { recursive: true, force: true })); +process.once("exit", () => { + try { + fs.rmSync(TEST_LOG_DIR, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }); + } catch { + // Best-effort cleanup only; tmpdir residue must not fail otherwise-passing tests. + } +}); const originalLogFilePath = process.env.APP_LOG_FILE_PATH; process.env.APP_LOG_FILE_PATH = TEST_LOG_PATH;