Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
Expand Down Expand Up @@ -2128,15 +2138,32 @@ const PermissionsModal = memo(function PermissionsModal({
Key will automatically stop working after this date.
</p>
</div>
<input
type="datetime-local"
value={expiresAt ? expiresAt.slice(0, 16) : ""}
onChange={(e) => {
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"
/>
<div className="flex gap-2">
<input
type="datetime-local"
value={toLocalDateTimeInputValue(expiresAt)}
onChange={(e) => {
const val = e.target.value;
if (!val) {
setExpiresAt("");
return;
}
const date = new Date(val);
if (!Number.isNaN(date.getTime())) {
setExpiresAt(date.toISOString());
}
}}
Comment thread
xz-dev marked this conversation as resolved.
className="min-w-0 flex-1 px-2 py-1.5 text-sm border border-border rounded-md bg-background text-text-main"
/>
<button
type="button"
onClick={() => setExpiresAt("")}
disabled={!expiresAt}
className="shrink-0 px-3 py-1.5 text-sm font-medium border border-border rounded-md text-text-muted hover:text-text-main hover:bg-black/5 dark:hover:bg-white/5 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent"
>
{tc("clear")}
</button>
</div>
</div>
{/* Management Access */}
<div className="flex flex-col gap-2 p-3 rounded-lg border border-border bg-surface/40">
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/api-manager-page-static.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
12 changes: 1 addition & 11 deletions tests/unit/batch-processor-expiration.test.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion tests/unit/console-log-levels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading