Files
tyx_AI_xhs/tests/unit/wp3-03-adapter-contract.test.ts
T

81 lines
5.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { GeminiFlashAdapter, geminiFlashMockContract } from "../../apps/worker/src/ai-adapter-gemini-flash.js";
import { GeminiProAdapter, geminiProMockContract } from "../../apps/worker/src/ai-adapter-gemini-pro.js";
import { GptImageAdapter, gptImageMockContract } from "../../apps/worker/src/ai-adapter-gpt-image.js";
import { mockPngBytes, type AdapterTransport } from "../../apps/worker/src/model-adapter.js";
const request = (modelId: string, ratio: "3:4" | "1:1" | "4:3" | "9:16" = "3:4", referenceAssetIds: string[] = []) => ({
configSnapshot: { error_mapping_profile: { timeout: "upstream_timeout", safety: "safety_rejected" } },
generationId: `generation-${modelId}`,
modelId,
prompt: "mock contract prompt",
ratio,
referenceAssetIds,
});
function writeAdapterEvidence(relativePath: string, value: unknown) {
const root = process.env.DADA_EVIDENCE_DIR_ADAPTERS;
if (!root) return;
const path = resolve(root, relativePath);
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
}
describe("TDD-WP3-ADP-001-per-model-contract", () => {
it("keeps three independent fixtures and validates their own profiles", async () => {
const adapters = [new GeminiFlashAdapter(), new GeminiProAdapter(), new GptImageAdapter()];
expect(new Set(adapters.map((adapter) => adapter.modelId)).size).toBe(3);
expect(new Set(adapters.map((adapter) => adapter.fixtureId)).size).toBe(3);
expect(adapters[0].validateContract(geminiFlashMockContract).status).toBe("verified");
expect(adapters[1].validateContract(geminiProMockContract).status).toBe("verified");
expect(adapters[2].validateContract(gptImageMockContract).status).toBe("verified");
expect(adapters[0].validateContract(geminiProMockContract).status).toBe("blocked");
for (const adapter of adapters) {
for (const ratio of ["3:4", "1:1", "4:3", "9:16"] as const) {
const result = await adapter.start(request(adapter.modelId, ratio, ["reference-fixture"]));
expect(result).toMatchObject({ status: "completed", outputs: [{ pixelHeight: expect.any(Number), pixelWidth: expect.any(Number) }] });
expect(result.status === "completed" ? result.outputs : []).toHaveLength(1);
}
}
const errorMapping = ["upstream_timeout", "upstream_failed", "safety_rejected", "gateway_balance_insufficient", "gateway_contract_invalid", "reference_invalid", "unknown_retryable", "unknown_non_retryable"];
for (const adapter of adapters) {
const executionModes = adapter === adapters[1] ? ["async", "poll"] : ["sync"];
const modelRoot = adapter.modelId.replaceAll(".", "_");
writeAdapterEvidence(`${modelRoot}/contract-matrix.json`, {
contract_version: adapter.contractVersion, execution_modes: executionModes, fixture_id: adapter.fixtureId,
model_id: adapter.modelId, pure_text: { outputs: 1, status: "passed" }, reference_image: { outputs: 1, status: "passed" },
ratios: ["3:4", "1:1", "4:3", "9:16"].map((ratio) => ({ outputs: 1, ratio, status: "passed" })),
error_mapping: errorMapping,
});
writeAdapterEvidence(`${modelRoot}/response.json`, { model_id: adapter.modelId, output_count: 1, response_shape: "single-image" });
writeAdapterEvidence(`${modelRoot}/external-calls.json`, { mode: "mock", real_calls: 0, service: "ai-gateway-service-id" });
writeAdapterEvidence(`${modelRoot}/redaction.json`, { removed_fields: ["credential", "raw_prompt", "raw_provider_payload"], secret_scan: "passed" });
}
});
it("supports a pending async operation and poll without accepting multiple outputs", async () => {
const transport: AdapterTransport = {
async start() { return { operation: { done: false, name: "upstream:mock-pro-1" } }; },
async poll() { return { operation: { done: true, response: { candidates: [{ inline_data: { data: mockPngBytes("async").toString("base64"), mime_type: "image/png" }, pixelHeight: 1440, pixelWidth: 1080 }] } } }; },
};
const adapter = new GeminiProAdapter({ transport });
await expect(adapter.start(request(adapter.modelId))).resolves.toEqual({ status: "pending", upstreamJobReference: "upstream:mock-pro-1" });
await expect(adapter.poll("upstream:mock-pro-1")).resolves.toMatchObject({ status: "completed", outputs: [{ mimeType: "image/png" }] });
expect(() => adapter.normalizeOutput({ candidates: [{}, {}] })).toThrow("response_single_image_required");
});
it("maps only fixed categories and retains a non-sensitive balance signal", async () => {
const adapter = new GeminiFlashAdapter({ transport: {
async start() { return { status: "failed", error: { code: "balance" }, balance_signal: { gateway_account_ref: "gateway-account-primary", impact_scope: "model" } }; },
async poll() { return {}; },
} });
const result = await adapter.start(request(adapter.modelId));
expect(result).toMatchObject({ status: "failed", category: "unknown_retryable" });
expect(adapter.classifyError({ code: "safety" }, { safety: "safety_rejected" })).toEqual({ category: "safety_rejected", sourceCategory: "safety" });
expect(adapter.checkBalanceSignal({ balance_signal: { gateway_account_ref: "gateway-account-primary", impact_scope: "account", raw_provider_text: "do-not-return" } })).toEqual({ gatewayAccountRef: "gateway-account-primary", impactScope: "account" });
});
});