import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { after, before, test } from "node:test"; import { eq } from "drizzle-orm"; import { db } from "@/lib/db"; import { aiModel, aiProvider } from "@/lib/db/schema"; import { recordAiModelHealthFailure, reviveAiModelsFromCooldown } from "@/lib/ai/runtime/model-health"; const database = new URL(process.env.POSTGRES_URL || "http://missing"); if (database.hostname !== "127.0.0.1" || !/_test$/.test(database.pathname)) throw new Error("Use a disposable local database"); const providerId = randomUUID(); const authModelId = randomUUID(); const transientModelId = randomUUID(); before(async () => { await db.insert(aiProvider).values({ id: providerId, name: "Synthetic credential test", type: "custom", baseUrl: "https://invalid.example", apiKey: "synthetic-unused-test-value" }); await db.insert(aiModel).values([ { id: authModelId, providerId, modelId: "auth-test", displayName: "Auth test" }, { id: transientModelId, providerId, modelId: "transient-test", displayName: "Transient test" }, ]); }); after(async () => { await db.delete(aiProvider).where(eq(aiProvider.id, providerId)); await db.$client.end({ timeout: 2 }); }); test("a confirmed authentication rejection disables the invalid model immediately", async () => { await recordAiModelHealthFailure({ model: { id: authModelId }, error: Object.assign(new Error("Invalid Authentication"), { statusCode: 401 }) }); const [model] = await db.select().from(aiModel).where(eq(aiModel.id, authModelId)); assert.equal(model.isEnabled, false); assert.equal(model.healthStatus, "auto_disabled"); assert.equal(model.healthConsecutiveFailures, 1); assert.match(model.healthAutoDisabledReason || "", /^provider_authentication_failed/); }); test("waiting for cooldown cannot reactivate a model with invalid credentials", async () => { await db.update(aiModel).set({ healthAutoDisabledAt: new Date(Date.now() - 3 * 60 * 60_000) }).where(eq(aiModel.id, authModelId)); assert.deepEqual(await reviveAiModelsFromCooldown({ modelId: authModelId }), []); const [model] = await db.select().from(aiModel).where(eq(aiModel.id, authModelId)); assert.equal(model.isEnabled, false); }); test("temporary outages can still enter the normal observation pool after cooldown", async () => { for (let i = 0; i < 3; i++) await recordAiModelHealthFailure({ model: { id: transientModelId }, error: Object.assign(new Error("Service unavailable"), { statusCode: 503 }) }); await db.update(aiModel).set({ healthAutoDisabledAt: new Date(Date.now() - 3 * 60 * 60_000) }).where(eq(aiModel.id, transientModelId)); const revived = await reviveAiModelsFromCooldown({ modelId: transientModelId }); assert.equal(revived.length, 1); assert.equal(revived[0].isEnabled, true); assert.equal(revived[0].healthStatus, "degraded"); });