import { describe, it, beforeEach, afterEach, after } from "node:test"; import assert from "node:assert/strict"; import Database from "better-sqlite3"; import path from "node:path"; import fs from "node:fs"; import os from "node:os"; function assertAlmostEqual(actual, expected, epsilon = 1e-9, message = "") { assert.ok( Math.abs(actual - expected) <= epsilon, message || `expected ${actual} to be within ${epsilon} of ${expected}` ); } // ─── Test Setup: Single temp dir for whole file (core caches DATA_DIR at first import) ──────── const fileTmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-domain-test-")); const originalDataDir = process.env.DATA_DIR; process.env.DATA_DIR = fileTmpDir; async function removeStorageFiles(dir) { const storage = path.join(dir, "storage.sqlite"); try { const core = await import("../../src/lib/db/core.ts"); core.resetDbInstance(); } catch { /* core may not be loaded yet */ } for (const suffix of ["", "-wal", "-shm", "-journal"]) { const p = storage + suffix; try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch {} } } beforeEach(async () => { await removeStorageFiles(fileTmpDir); }); afterEach(async () => { const core = await import("../../src/lib/db/core.ts"); core.resetDbInstance(); }); after(() => { process.env.DATA_DIR = originalDataDir; if (fs.existsSync(fileTmpDir)) fs.rmSync(fileTmpDir, { recursive: true, force: true }); }); // ─── Fallback Policy Tests ──────────────────────── describe("fallbackPolicy persistence", () => { it("should register and resolve a fallback chain", async () => { const { registerFallback, resolveFallbackChain, hasFallback, resetAllFallbacks } = await import("../../src/domain/fallbackPolicy.ts"); resetAllFallbacks(); registerFallback("gpt-4o", [ { provider: "openai", priority: 0, enabled: true }, { provider: "anthropic", priority: 1, enabled: true }, { provider: "disabled-one", priority: 2, enabled: false }, ]); assert.ok(hasFallback("gpt-4o")); const chain = resolveFallbackChain("gpt-4o"); assert.equal(chain.length, 2); // disabled-one excluded assert.equal(chain[0].provider, "openai"); assert.equal(chain[1].provider, "anthropic"); resetAllFallbacks(); }); it("should resolve with exclusions", async () => { const { registerFallback, resolveFallbackChain, resetAllFallbacks } = await import("../../src/domain/fallbackPolicy.ts"); resetAllFallbacks(); registerFallback("claude-3", [ { provider: "anthropic", priority: 0 }, { provider: "openai", priority: 1 }, ]); const chain = resolveFallbackChain("claude-3", ["anthropic"]); assert.equal(chain.length, 1); assert.equal(chain[0].provider, "openai"); resetAllFallbacks(); }); it("should get next fallback correctly", async () => { const { registerFallback, getNextFallback, resetAllFallbacks } = await import("../../src/domain/fallbackPolicy.ts"); resetAllFallbacks(); registerFallback("test-model", [ { provider: "p1", priority: 0 }, { provider: "p2", priority: 1 }, ]); assert.equal(getNextFallback("test-model"), "p1"); assert.equal(getNextFallback("test-model", ["p1"]), "p2"); assert.equal(getNextFallback("test-model", ["p1", "p2"]), null); // Non-existing model assert.equal(getNextFallback("no-model"), null); resetAllFallbacks(); }); it("should remove fallback chain", async () => { const { registerFallback, removeFallback, hasFallback, resetAllFallbacks } = await import("../../src/domain/fallbackPolicy.ts"); resetAllFallbacks(); registerFallback("gpt-4", [{ provider: "openai" }]); assert.ok(hasFallback("gpt-4")); removeFallback("gpt-4"); assert.ok(!hasFallback("gpt-4")); resetAllFallbacks(); }); it("should get all fallback chains", async () => { const { registerFallback, getAllFallbackChains, resetAllFallbacks } = await import("../../src/domain/fallbackPolicy.ts"); resetAllFallbacks(); registerFallback("m1", [{ provider: "p1" }]); registerFallback("m2", [{ provider: "p2" }]); const all = getAllFallbackChains(); assert.ok("m1" in all); assert.ok("m2" in all); resetAllFallbacks(); }); }); // ─── Cost Rules Tests ──────────────────────── describe("costRules persistence", () => { it("should set and check budget", async () => { const { setBudget, getBudget, checkBudget, resetCostData } = await import("../../src/domain/costRules.ts"); resetCostData(); setBudget("key1", { dailyLimitUsd: 10 }); const budget = getBudget("key1"); assert.equal(budget.dailyLimitUsd, 10); assert.equal(budget.warningThreshold, 0.8); const check = checkBudget("key1"); assert.ok(check.allowed); assert.equal(check.dailyLimit, 10); resetCostData(); }); it("should record cost and check daily total", async () => { const { setBudget, recordCost, getDailyTotal, checkBudget, resetCostData } = await import("../../src/domain/costRules.ts"); resetCostData(); setBudget("key2", { dailyLimitUsd: 5 }); recordCost("key2", 3.5); recordCost("key2", 1.0); const total = getDailyTotal("key2"); assertAlmostEqual(total, 4.5, 1e-9, `daily total ${total} should equal 4.5 (3.5 + 1.0)`); // Should still be allowed const check = checkBudget("key2", 0); assert.ok(check.allowed); // Should be denied with additional cost const checkOver = checkBudget("key2", 1.0); assert.ok(!checkOver.allowed); resetCostData(); }); it("should return allowed=true when no budget set", async () => { const { checkBudget, resetCostData } = await import("../../src/domain/costRules.ts"); resetCostData(); const check = checkBudget("no-budget-key"); assert.ok(check.allowed); assert.equal(check.dailyLimit, 0); resetCostData(); }); it("should get cost summary", async () => { const { setBudget, recordCost, getCostSummary, resetCostData } = await import("../../src/domain/costRules.ts"); resetCostData(); setBudget("key3", { dailyLimitUsd: 100 }); recordCost("key3", 1.5); recordCost("key3", 2.5); const summary = getCostSummary("key3"); assertAlmostEqual( summary.dailyTotal, 4.0, 1e-9, `dailyTotal ${summary.dailyTotal} should equal 4.0 (1.5 + 2.5)` ); assertAlmostEqual( summary.monthlyTotal, 4.0, 1e-9, `monthlyTotal ${summary.monthlyTotal} should equal 4.0` ); assert.equal(summary.budget.dailyLimitUsd, 100); resetCostData(); }); }); // ─── Lockout Policy Tests ──────────────────────── describe("lockoutPolicy persistence", () => { it("should track failed attempts and trigger lockout", async () => { const { recordFailedAttempt, checkLockout, recordSuccess } = await import("../../src/domain/lockoutPolicy.ts"); const id = "test-ip-" + Date.now(); const config = { maxAttempts: 3, lockoutDurationMs: 5000, attemptWindowMs: 10000 }; // First attempts should not lock let result = recordFailedAttempt(id, config); assert.ok(!result.locked); result = recordFailedAttempt(id, config); assert.ok(!result.locked); // Third attempt triggers lockout result = recordFailedAttempt(id, config); assert.ok(result.locked); assert.ok(result.remainingMs > 0); // Check lockout const lockCheck = checkLockout(id, config); assert.ok(lockCheck.locked); // Clean up recordSuccess(id); }); it("should unlock after success", async () => { const { recordFailedAttempt, recordSuccess, checkLockout } = await import("../../src/domain/lockoutPolicy.ts"); const id = "test-unlock-" + Date.now(); const config = { maxAttempts: 3, lockoutDurationMs: 5000, attemptWindowMs: 10000 }; recordFailedAttempt(id, config); recordFailedAttempt(id, config); recordSuccess(id); const check = checkLockout(id, config); assert.ok(!check.locked); }); it("should force-unlock", async () => { const { recordFailedAttempt, forceUnlock, checkLockout } = await import("../../src/domain/lockoutPolicy.ts"); const id = "test-force-" + Date.now(); const config = { maxAttempts: 2, lockoutDurationMs: 60000, attemptWindowMs: 60000 }; recordFailedAttempt(id, config); recordFailedAttempt(id, config); const lockCheck = checkLockout(id, config); assert.ok(lockCheck.locked); forceUnlock(id); const afterUnlock = checkLockout(id, config); assert.ok(!afterUnlock.locked); }); }); // ─── Circuit Breaker Tests ──────────────────────── describe("circuitBreaker persistence", () => { it("should open after threshold failures", async () => { const { CircuitBreaker, STATE } = await import("../../src/shared/utils/circuitBreaker.ts"); const cb = new CircuitBreaker("test-cb-" + Date.now(), { failureThreshold: 3 }); assert.equal(cb.state, STATE.CLOSED); // Simulate failures for (let i = 0; i < 3; i++) { try { await cb.execute(() => Promise.reject(new Error("fail"))); } catch { // expected } } assert.equal(cb.state, STATE.OPEN); assert.ok(!cb.canExecute()); }); it("should reset correctly", async () => { const { CircuitBreaker, STATE } = await import("../../src/shared/utils/circuitBreaker.ts"); const cb = new CircuitBreaker("test-reset-" + Date.now(), { failureThreshold: 2 }); try { await cb.execute(() => Promise.reject(new Error("fail"))); } catch {} try { await cb.execute(() => Promise.reject(new Error("fail"))); } catch {} assert.equal(cb.state, STATE.OPEN); cb.reset(); assert.equal(cb.state, STATE.CLOSED); assert.equal(cb.failureCount, 0); assert.ok(cb.canExecute()); }); it("should close on success after half-open", async () => { const { CircuitBreaker, STATE } = await import("../../src/shared/utils/circuitBreaker.ts"); const cb = new CircuitBreaker("test-halfopen-" + Date.now(), { failureThreshold: 2, resetTimeout: 10, // 10ms for test speed }); // Open the circuit try { await cb.execute(() => Promise.reject(new Error("fail"))); } catch {} try { await cb.execute(() => Promise.reject(new Error("fail"))); } catch {} assert.equal(cb.state, STATE.OPEN); // Wait for resetTimeout await new Promise((r) => setTimeout(r, 20)); // Should transition to HALF_OPEN and succeed const result = await cb.execute(() => Promise.resolve("ok")); assert.equal(result, "ok"); assert.equal(cb.state, STATE.CLOSED); }); it("registry should return statuses", async () => { const { getCircuitBreaker, getAllCircuitBreakerStatuses } = await import("../../src/shared/utils/circuitBreaker.ts"); const name = "reg-test-" + Date.now(); const cb = getCircuitBreaker(name, { failureThreshold: 5 }); assert.ok(cb); const statuses = getAllCircuitBreakerStatuses(); const found = statuses.find((s) => s.name === name); assert.ok(found); assert.equal(found.state, "CLOSED"); }); });