mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 19:02:17 +03:00
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. O ponto que sustenta a PR é o `models.dev` virar overlay de preço em vez de fonte de catálogo. Um catálogo estático que sobrevive à conta já ter listado ids mais novos é o tipo de defeito que só aparece quando o modelo novo é justamente o que se quer usar. Nota de integração: `activeSyncedCatalog.ts` colidiu com o #12934 (união dos `customModels` do picker no catálogo de despacho). Como você extraiu o bloco original para `loadConnectionCatalog`, os dois se compõem: a união dos irmãos agy/antigravity primeiro, o `unionCustomModels` por cima. Revalidei com `custom-models-live-catalog-12597`, `live-model-catalog-reconciliation-8926`, `sync-models-degraded-cached-catalog-9683`, `models-dev-catalog-read-gate`, `discovery-class`, `reactive-model-sync` e `l1-oauth-autosync-default` juntos — 48/48 — mais typecheck:core limpo. Sobre o `autoSync` padrão em Claude/Codex/Copilot com scheduler de 6h: passei isso pelo dono antes de mergear e a decisão foi manter como está.
204 lines
6.6 KiB
TypeScript
204 lines
6.6 KiB
TypeScript
/**
|
|
* Test 11 — C-layer read gate.
|
|
*
|
|
* models.dev is a pricing overlay, not a catalog source. `models_dev_pricing`
|
|
* may be full (including IDs this account never synced). The /v1/models row
|
|
* set must not grow from those keys. Forbidden: filtering the pricing cache
|
|
* to make this pass.
|
|
*
|
|
* Source guard is mandatory. The catalog row-set assertion runs when
|
|
* getUnifiedModelsResponse is reachable under DATA_DIR isolation; if the
|
|
* auth wall returns 401, the skip reason is documented in this file and the
|
|
* source guard still covers "does not fabricate catalog rows".
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-models-dev-read-gate-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "models-dev-read-gate-secret";
|
|
process.env.REQUIRE_API_KEY = "false";
|
|
|
|
const SYNCED_MODEL_ID = "claude-opus-4-6";
|
|
const UNVERIFIED_MODEL_ID = "unverified-market-id-not-synced";
|
|
const SYNCED_INPUT = 15;
|
|
const SYNCED_OUTPUT = 75;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const providersDb = await import("../../src/lib/db/providers.ts");
|
|
const modelsDb = await import("../../src/lib/db/models.ts");
|
|
const modelsDev = await import("../../src/lib/modelsDevSync.ts");
|
|
const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts");
|
|
|
|
type CatalogRow = {
|
|
id: string;
|
|
owned_by?: string;
|
|
root?: string | null;
|
|
pricing?: Record<string, number> | null;
|
|
};
|
|
|
|
function readModelsDevSyncSources(): string[] {
|
|
const files = ["src/lib/modelsDevSync.ts"];
|
|
const dir = "src/lib/modelsDevSync";
|
|
if (fs.existsSync(dir)) {
|
|
for (const name of fs.readdirSync(dir)) {
|
|
if (name.endsWith(".ts")) files.push(path.join(dir, name));
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
function rowMatchesUnverified(row: CatalogRow): boolean {
|
|
const id = row.id ?? "";
|
|
const root = row.root ?? "";
|
|
return (
|
|
id === UNVERIFIED_MODEL_ID ||
|
|
id.endsWith(`/${UNVERIFIED_MODEL_ID}`) ||
|
|
id.endsWith(UNVERIFIED_MODEL_ID) ||
|
|
root === UNVERIFIED_MODEL_ID
|
|
);
|
|
}
|
|
|
|
function rowMatchesSynced(row: CatalogRow): boolean {
|
|
const id = row.id ?? "";
|
|
const root = row.root ?? "";
|
|
return (
|
|
id === SYNCED_MODEL_ID ||
|
|
id.endsWith(`/${SYNCED_MODEL_ID}`) ||
|
|
root === SYNCED_MODEL_ID
|
|
);
|
|
}
|
|
|
|
async function resetStorage() {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
v1ModelsCatalog.__resetCatalogBuilderRunsForTest();
|
|
}
|
|
|
|
async function seedClaudeSyncedAndFullPricingCache() {
|
|
const connection = await providersDb.createProviderConnection({
|
|
provider: "claude",
|
|
authType: "oauth",
|
|
name: "claude-read-gate",
|
|
apiKey: "",
|
|
accessToken: "claude-read-gate-access-token",
|
|
isActive: true,
|
|
testStatus: "active",
|
|
providerSpecificData: {},
|
|
});
|
|
|
|
await modelsDb.replaceSyncedAvailableModelsForConnection(
|
|
"claude",
|
|
(connection as { id: string }).id,
|
|
[{ id: SYNCED_MODEL_ID, name: "Claude Opus 4.6", source: "imported" }]
|
|
);
|
|
|
|
// Full overlay: a live-synced id AND a market id this connection never
|
|
// discovered. Do not filter this map to make the catalog assertion pass.
|
|
modelsDev.saveModelsDevPricing({
|
|
claude: {
|
|
[SYNCED_MODEL_ID]: { input: SYNCED_INPUT, output: SYNCED_OUTPUT },
|
|
[UNVERIFIED_MODEL_ID]: { input: 1, output: 2 },
|
|
},
|
|
});
|
|
}
|
|
|
|
test.beforeEach(async () => {
|
|
await resetStorage();
|
|
});
|
|
|
|
test.after(async () => {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
});
|
|
|
|
test("test 11: modelsDevSync does not insert synced_models rows", () => {
|
|
for (const file of readModelsDevSyncSources()) {
|
|
const src = fs.readFileSync(file, "utf8");
|
|
assert.doesNotMatch(
|
|
src,
|
|
/replaceSyncedAvailableModels/,
|
|
`${file} must not write synced catalog rows from models.dev`
|
|
);
|
|
assert.doesNotMatch(
|
|
src,
|
|
/insertCustomModel/,
|
|
`${file} must not insert custom models from models.dev`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("test 11: models.dev pricing cache may contain unverified ids (read gate, not write-side intersection)", async () => {
|
|
await seedClaudeSyncedAndFullPricingCache();
|
|
|
|
const pricing = modelsDev.getModelsDevPricing();
|
|
assert.ok(pricing.claude, "pricing overlay must keep the claude provider key");
|
|
assert.equal(pricing.claude[SYNCED_MODEL_ID]?.input, SYNCED_INPUT);
|
|
assert.equal(pricing.claude[UNVERIFIED_MODEL_ID]?.input, 1);
|
|
assert.equal(
|
|
pricing.claude[UNVERIFIED_MODEL_ID]?.output,
|
|
2,
|
|
"unverified market ids stay in models_dev_pricing; do not filter cache keys"
|
|
);
|
|
});
|
|
|
|
test("test 11: getUnifiedModelsResponse does not list unverified models.dev ids", async (t) => {
|
|
await seedClaudeSyncedAndFullPricingCache();
|
|
|
|
const pricing = modelsDev.getModelsDevPricing();
|
|
assert.ok(
|
|
pricing.claude?.[UNVERIFIED_MODEL_ID],
|
|
"precondition: pricing overlay still contains the unsynced market id"
|
|
);
|
|
|
|
const response = await v1ModelsCatalog.getUnifiedModelsResponse(
|
|
new Request("http://127.0.0.1/v1/models")
|
|
);
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
// Auth wall: keep the source guard (test above) as the "does not fabricate
|
|
// catalog rows" coverage. Do not weaken that guard, and do not filter
|
|
// models_dev_pricing keys to paper over a missing row-set assertion.
|
|
t.skip(
|
|
`catalog row-set skipped: getUnifiedModelsResponse returned ${response.status} (auth wall). ` +
|
|
"Source guard still covers modelsDevSync not writing synced_models / custom_models."
|
|
);
|
|
return;
|
|
}
|
|
|
|
assert.equal(
|
|
response.status,
|
|
200,
|
|
`expected catalog 200, got ${response.status}: ${(await response.clone().text()).slice(0, 500)}`
|
|
);
|
|
|
|
const body = (await response.json()) as { data: CatalogRow[] };
|
|
assert.ok(Array.isArray(body.data), "catalog body.data must be an array");
|
|
|
|
const leaked = body.data.filter(rowMatchesUnverified);
|
|
assert.deepEqual(
|
|
leaked.map((row) => row.id),
|
|
[],
|
|
`catalog must not list unverified models.dev id ${UNVERIFIED_MODEL_ID}`
|
|
);
|
|
|
|
const syncedRows = body.data.filter(rowMatchesSynced);
|
|
assert.ok(
|
|
syncedRows.length > 0,
|
|
`catalog must still list the live-synced id ${SYNCED_MODEL_ID}`
|
|
);
|
|
|
|
for (const row of syncedRows) {
|
|
if (row.pricing == null) continue;
|
|
const keys = Object.keys(row.pricing);
|
|
assert.ok(
|
|
keys.length > 0,
|
|
`synced row ${row.id} advertised pricing but the object was empty`
|
|
);
|
|
}
|
|
});
|