import { expect, test } from "bun:test"; import { getDreaminaModelCatalog } from "../src/services/local-dreamina-model-catalog"; import { LocalRuntimeClientError } from "../src/services/local-runtime-session"; test("Dreamina model discovery reads only the signed Runtime catalog", async () => { const requests: string[] = []; const catalog = await getDreaminaModelCatalog({ async request(path) { requests.push(path); return new Response( JSON.stringify({ ok: true, provider: "dreamina-cli", accountBinding: "a".repeat(64), sessionEpoch: 3, models: [ { provider: "dreamina-cli", id: "seedance2.0mini", displayName: "seedance2.0mini", modality: "video", operations: ["text-to-video", "image-to-video", "reference-to-video"], adapterSupported: true, accountEntitlement: "unknown", currentlyObservedAvailable: "unknown", settings: { aliases: [], aspects: ["16:9"], maxReferenceImages: 9, minDuration: 4, maxDuration: 15, tiers: ["720p"] }, source: "runtime-execution-contract", }, ], }), { status: 200, headers: { "content-type": "application/json" } }, ); }, }); expect(catalog).toHaveLength(1); expect(catalog[0]).toMatchObject({ adapterSupported: true, accountEntitlement: "unknown", currentlyObservedAvailable: "unknown", }); expect(catalog[0]?.settings.minDuration).toBe(4); expect(requests).toEqual(["/dreamina/models"]); }); test("Dreamina model discovery returns its authenticated cache scope with one signed GET", async () => { const { getDreaminaModelCatalogSnapshot } = await import("../src/services/local-dreamina-model-catalog"); let requests = 0; const snapshot = await getDreaminaModelCatalogSnapshot({ async request() { requests += 1; return catalogResponse(); }, }); expect(requests).toBe(1); expect(snapshot).toMatchObject({ accountBinding: "a".repeat(64), sessionEpoch: 3 }); expect(snapshot.models).toHaveLength(1); }); test("Dreamina model discovery treats a signed scope denial as an error, never a successful empty catalog", async () => { await expect( getDreaminaModelCatalog({ async request() { return new Response( JSON.stringify({ ok: false, code: "scope_denied", message: "public failure", }), { status: 403, headers: { "content-type": "application/json" } }, ); }, }), ).rejects.toThrow("Dreamina model catalog is unavailable"); }); test("Dreamina catalog recovery replaces an obsolete 401/403 session and retries only the safe GET once", async () => { const module = await import("../src/services/local-dreamina-model-catalog").catch(() => ({})); const recover = ( module as { getDreaminaModelCatalogWithSessionRecovery?: (client: { request(path: string, init?: RequestInit): Promise; connect(signal?: AbortSignal): Promise<{ state: string }>; revokeLocalSession(): void }) => Promise; } ).getDreaminaModelCatalogWithSessionRecovery; expect(typeof recover).toBe("function"); if (!recover) return; for (const rejectedStatus of [401, 403]) { const requests: Array<{ path: string; method: string }> = []; let revoked = 0; let connected = 0; const models = await recover({ async request(path, init) { requests.push({ path, method: String(init?.method) }); if (requests.length === 1) { return new Response(JSON.stringify({ ok: false }), { status: rejectedStatus }); } return catalogResponse(); }, async connect() { connected += 1; return { state: "connected" }; }, revokeLocalSession() { revoked += 1; }, }); expect(models).toHaveLength(1); expect(requests).toEqual([ { path: "/dreamina/models", method: "GET" }, { path: "/dreamina/models", method: "GET" }, ]); expect({ revoked, connected }).toEqual({ revoked: 1, connected: 1 }); } }); test("Dreamina catalog snapshot recovery preserves the authenticated cache scope after one safe reconnect", async () => { const module = await import("../src/services/local-dreamina-model-catalog").catch(() => ({})); const recover = ( module as { getDreaminaModelCatalogSnapshotWithSessionRecovery?: (client: { request(path: string, init?: RequestInit): Promise; connect(signal?: AbortSignal): Promise<{ state: string }>; revokeLocalSession(): void; }) => Promise<{ accountBinding: string; sessionEpoch: number; models: unknown[] }>; } ).getDreaminaModelCatalogSnapshotWithSessionRecovery; expect(typeof recover).toBe("function"); if (!recover) return; let requests = 0; let revoked = 0; let connected = 0; const snapshot = await recover({ async request() { requests += 1; if (requests === 1) return new Response(JSON.stringify({ ok: false }), { status: 401 }); return catalogResponse(); }, async connect() { connected += 1; return { state: "connected" }; }, revokeLocalSession() { revoked += 1; }, }); expect(snapshot).toMatchObject({ accountBinding: "a".repeat(64), sessionEpoch: 3 }); expect(snapshot.models).toHaveLength(1); expect({ requests, revoked, connected }).toEqual({ requests: 2, revoked: 1, connected: 1 }); }); test("Dreamina catalog recovery reconnects once when the browser rejects an expired session before fetch", async () => { const { getDreaminaModelCatalogWithSessionRecovery } = await import("../src/services/local-dreamina-model-catalog"); let requests = 0; let revoked = 0; let connected = 0; const models = await getDreaminaModelCatalogWithSessionRecovery({ async request() { requests += 1; if (requests === 1) throw new LocalRuntimeClientError("session_required", "public failure", 401); return catalogResponse(); }, async connect() { connected += 1; return { state: "connected" as const }; }, revokeLocalSession() { revoked += 1; }, }); expect(models).toHaveLength(1); expect({ requests, revoked, connected }).toEqual({ requests: 2, revoked: 1, connected: 1 }); }); test("effective config projects an asynchronously arriving Dreamina catalog without persisting the local channel", async () => { const module = await import("../src/stores/use-config-store").catch(() => ({})); const project = ( module as { effectiveConfigWithDreamina?: ( config: typeof import("../src/stores/use-config-store").defaultConfig, state: "idle" | "loading" | "ready" | "error", models: Array>, ) => typeof import("../src/stores/use-config-store").defaultConfig; } ).effectiveConfigWithDreamina; expect(typeof project).toBe("function"); if (!project) return; const { defaultConfig } = await import("../src/stores/use-config-store"); const pending = project(defaultConfig, "loading", []); const ready = project(defaultConfig, "ready", [ { provider: "dreamina-cli", id: "seedance2.0mini", displayName: "seedance2.0mini", modality: "video", operations: ["text-to-video", "image-to-video", "reference-to-video"], adapterSupported: true, accountEntitlement: "unknown", currentlyObservedAvailable: "unknown", settings: { aliases: [], aspects: ["16:9"], maxReferenceImages: 9, minDuration: 4, maxDuration: 15, tiers: ["720p"] }, source: "runtime-execution-contract", }, ]); expect(pending.videoModels).not.toContain("local:dreamina-cli:seedance2.0mini"); expect(ready.videoModels).toContain("local:dreamina-cli:seedance2.0mini"); expect(defaultConfig.channels.some((channel) => channel.id === "local:dreamina-cli")).toBe(false); }); test("Dreamina catalog readiness is loading immediately and shares one in-flight bootstrap", async () => { const module = await import("../src/stores/use-local-dreamina-model-store").catch(() => ({})); const createStore = ( module as { createLocalDreaminaModelStore?: (dependencies: Record) => { getState(): { state: string; models: Array<{ id: string }>; ensureReady(signal?: AbortSignal): Promise>; }; }; } ).createLocalDreaminaModelStore; expect(typeof createStore).toBe("function"); if (!createStore) return; let requests = 0; let release!: () => void; const gate = new Promise((resolve) => { release = resolve; }); const store = createStore({ getRuntimeState: () => ({ connection: "connected", modules: [{ id: "dreamina", scopes: ["dreamina:models"] }] }), getClient: () => ({ request: async () => catalogResponse() }), loadSnapshot: async () => { requests += 1; await gate; return { accountBinding: "a".repeat(64), sessionEpoch: 3, models: [ { provider: "dreamina-cli", id: "seedance2.0mini", displayName: "seedance2.0mini", modality: "video", operations: ["text-to-video"], adapterSupported: true, accountEntitlement: "unknown", currentlyObservedAvailable: "unknown", settings: { aliases: [], aspects: ["16:9"], maxReferenceImages: 9, minDuration: 4, maxDuration: 15, tiers: ["720p"] }, source: "runtime-execution-contract", }, ], }; }, }); const first = store.getState().ensureReady(); const second = store.getState().ensureReady(); expect(store.getState().state).toBe("loading"); expect(requests).toBe(1); release(); const [firstModels, secondModels] = await Promise.all([first, second]); expect(firstModels.map((item) => item.id)).toEqual(["seedance2.0mini"]); expect(secondModels).toEqual(firstModels); expect(store.getState()).toMatchObject({ state: "ready", cacheScope: `${"a".repeat(64)}:3` }); expect(requests).toBe(1); }); test("Dreamina catalog timeout releases a hung shared request so generation can recover without reload", async () => { const { createLocalDreaminaModelStore } = await import("../src/stores/use-local-dreamina-model-store"); let requests = 0; const store = createLocalDreaminaModelStore({ getRuntimeState: () => ({ connection: "connected", modules: [{ id: "dreamina", scopes: ["dreamina:models"] }] }), getClient: () => ({ request: async () => catalogResponse() }), requestTimeoutMs: 10, loadSnapshot: async (_client, signal) => { requests += 1; if (requests > 1) return catalogSnapshot("seedance-recovered", 4); if (!signal) return await new Promise>(() => undefined); return await new Promise>((_resolve, reject) => { signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true }); }); }, }); const caller = new AbortController(); const first = store.getState().ensureReady(caller.signal); caller.abort(); await expect(first).rejects.toMatchObject({ name: "AbortError" }); await Bun.sleep(20); const recovered = await Promise.race([store.getState().ensureReady(), Bun.sleep(40).then(() => "stuck" as const)]); expect(recovered).not.toBe("stuck"); expect(recovered).toMatchObject([{ id: "seedance-recovered" }]); expect(store.getState()).toMatchObject({ state: "ready", cacheScope: `${"a".repeat(64)}:4` }); expect(requests).toBe(2); }); test("Dreamina catalog forced resync ignores a late result from the previous Runtime scope", async () => { const { createLocalDreaminaModelStore } = await import("../src/stores/use-local-dreamina-model-store"); let requests = 0; let releaseOld!: (snapshot: ReturnType) => void; const oldRequest = new Promise>((resolve) => { releaseOld = resolve; }); const store = createLocalDreaminaModelStore({ getRuntimeState: () => ({ connection: "connected", modules: [{ id: "dreamina", scopes: ["dreamina:models"] }] }), getClient: () => ({ request: async () => catalogResponse() }), requestTimeoutMs: 100, loadSnapshot: async () => { requests += 1; return requests === 1 ? oldRequest : catalogSnapshot("seedance-current", 5); }, }); const oldSync = store.getState().sync(); const currentSync = store.getState().sync(); const currentResult = await Promise.race([currentSync.then(() => "ready" as const), Bun.sleep(40).then(() => "stuck" as const)]); expect(currentResult).toBe("ready"); expect(requests).toBe(2); expect(store.getState()).toMatchObject({ state: "ready", cacheScope: `${"a".repeat(64)}:5`, models: [{ id: "seedance-current" }] }); releaseOld(catalogSnapshot("seedance-stale", 3)); await oldSync; expect(store.getState()).toMatchObject({ state: "ready", cacheScope: `${"a".repeat(64)}:5`, models: [{ id: "seedance-current" }] }); }); test("effective config removes custom channels when administrators disable them", async () => { const { createModelChannel, effectiveConfigForCustomChannels, normalizeConfigSnapshot } = await import("../src/stores/use-config-store"); const config = normalizeConfigSnapshot({ config: { channels: [ createModelChannel({ id: "system-1", scope: "system", name: "系统渠道", baseUrl: "/api/ai/system/system-1", apiKey: "system", models: ["system-model"], modelCosts: [{ model: "system-model", capability: "text", billingMode: "fixed_request", unitPriceMicrocredits: 0 }], }), createModelChannel({ id: "custom-1", scope: "user", name: "自定义渠道", baseUrl: "https://example.com", apiKey: "private-key", models: ["custom-model"] }), ], }, }).config; const effective = effectiveConfigForCustomChannels(config, false); expect(effective.channels.map((channel) => channel.id)).toEqual(["system-1"]); expect(effective.models).toContain("system-1::system-model"); expect(effective.models).not.toContain("custom-1::custom-model"); expect(config.channels.map((channel) => channel.id)).toEqual(["system-1", "custom-1"]); }); function catalogResponse() { return new Response( JSON.stringify({ ok: true, provider: "dreamina-cli", accountBinding: "a".repeat(64), sessionEpoch: 3, models: [ { provider: "dreamina-cli", id: "seedance2.0mini", displayName: "seedance2.0mini", modality: "video", operations: ["text-to-video", "image-to-video", "reference-to-video"], adapterSupported: true, accountEntitlement: "unknown", currentlyObservedAvailable: "unknown", settings: { aliases: [], aspects: ["16:9"], maxReferenceImages: 9, minDuration: 4, maxDuration: 15, tiers: ["720p"] }, source: "runtime-execution-contract", }, ], }), { status: 200, headers: { "content-type": "application/json" } }, ); } function catalogSnapshot(id: string, sessionEpoch: number) { return { accountBinding: "a".repeat(64), sessionEpoch, models: [ { provider: "dreamina-cli" as const, id, displayName: id, modality: "video" as const, operations: ["text-to-video" as const], adapterSupported: true, accountEntitlement: "unknown" as const, currentlyObservedAvailable: "unknown" as const, settings: { aliases: [], aspects: ["16:9"], maxReferenceImages: 9, minDuration: 4, maxDuration: 15, tiers: ["720p"] }, source: "runtime-execution-contract" as const, }, ], }; }