开流前的注销检查可以和读会话、解析模型同时进行;审核改到扣点前等待,不再挡住第一行;模型版本可以短缓存;两个领域可以一起算。本机没有数据库中位数,也没有带星盘网络库的镜像,所以默认仍是关闭、缓存 0、领域逐个算。 自测:tsc 0 错;lint 0 error;相关测试失败 0、取消 0。test:db 未跑。 Co-Authored-By: Grok (agent loop)
74 lines
4.5 KiB
TypeScript
74 lines
4.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
const catalog = readFileSync(new URL("../src/lib/model-catalog.ts", import.meta.url), "utf8");
|
|
const mastraModel = readFileSync(new URL("../src/mastra/model.ts", import.meta.url), "utf8");
|
|
const routes = Object.fromEntries(
|
|
["reports", "birth-time-guide", "consult"].map((name) => [
|
|
name,
|
|
readFileSync(new URL(`../src/app/api/${name}/route.ts`, import.meta.url), "utf8"),
|
|
]),
|
|
);
|
|
|
|
test("runtime catalog is database-only and reads encrypted provider credentials", () => {
|
|
assert.match(catalog, /queryAdminRows<PublishedModelRow>/);
|
|
assert.match(catalog, /p\.encrypted_api_key/);
|
|
assert.match(catalog, /decryptModelProviderApiKey\(\{ encryptedApiKey: row\.encrypted_api_key \}\)/);
|
|
assert.match(catalog, /v\.status='published' and v\.enabled and p\.enabled/);
|
|
assert.doesNotMatch(
|
|
`${catalog}\n${mastraModel}`,
|
|
/LLM_MODELS_JSON|LLM_BASE_URL|LLM_MODEL|LLM_DEFAULT_MODEL_ID|LLM_PROVIDER_ID|MASTRA_MODEL|OPENAI_API_KEY|ANTHROPIC_API_KEY|DEEPSEEK_API_KEY|LLM_API_KEY|MODEL_PROVIDER_[A-Z0-9_]+_API_KEY|secret_ref|secretRef/,
|
|
);
|
|
});
|
|
|
|
test("database rows map to native OpenAI, native Anthropic, and pinned compatible Mastra configs", () => {
|
|
assert.match(catalog, /providerId: "openai", modelId: row\.provider_model\.replace\(\/\^openai/);
|
|
assert.match(catalog, /providerId: "anthropic", modelId: row\.provider_model\.replace\(\/\^anthropic/);
|
|
assert.match(catalog, /providerId: row\.provider_code, modelId: row\.provider_model, url:/);
|
|
assert.match(catalog, /assertAllowedModelProviderUrl\(row\.base_url \?\? ""\)/);
|
|
assert.match(catalog, /apiKey/);
|
|
assert.match(catalog, /function publicModel[\s\S]*creditCost: model\.creditCost, isDefault: model\.isDefault/);
|
|
assert.doesNotMatch(catalog, /publicModel[\s\S]{0,240}(apiKey|encrypted_api_key|base_url)/);
|
|
});
|
|
|
|
test("catalog cache is short-lived and failure stays fail-closed without env fallback", () => {
|
|
assert.match(catalog, /const cacheTtlMs = 15_000/);
|
|
assert.match(catalog, /expiresAt: Date\.now\(\) \+ cacheTtlMs/);
|
|
assert.match(catalog, /database_model_catalog_unavailable/);
|
|
assert.match(catalog, /database_model_catalog_empty/);
|
|
assert.match(catalog, /default_model_unavailable/);
|
|
assert.match(catalog, /const defaults = models\.filter\(\(model\) => model\.isDefault\)/);
|
|
assert.match(catalog, /defaults\.length === 1/);
|
|
});
|
|
|
|
test("session model resolution is async and pins published or retired database versions", () => {
|
|
assert.match(catalog, /export async function resolveSessionLanguageModel/);
|
|
assert.match(catalog, /c\.model_id=\$1 and v\.version=\$2 and v\.status in \('published','retired'\)/);
|
|
// 原值: return await readVersion(modelId, configVersion)
|
|
// 新值: const model = await readVersion(modelId, configVersion),只有非空结果写入短缓存
|
|
// 原因: 绑版本的解析与目录共用 15 秒上限,失败和空结果不缓存
|
|
assert.match(catalog, /const model = await readVersion\(modelId, configVersion\)/);
|
|
assert.match(catalog, /if \(model && ttl > 0\) cache\.set\(key, \{ expiresAt: Date\.now\(\) \+ ttl, model \}\)/);
|
|
assert.match(routes.consult, /await resolveSessionLanguageModel\(\s*chatSession\.model_id,\s*chatSession\.model_config_version/);
|
|
// 原值: route.ts 里 if (!sessionModel) 之后是 status 503
|
|
// 新值: turn-prep.ts 里 if (!sessionModel) 返回 modelUnavailable(),正文 status 503
|
|
// 原因: 模型不可用仍是开流前的 503,文案随准备步骤一起搬走
|
|
const head = readFileSync(new URL("../src/lib/consult/turn-prep.ts", import.meta.url), "utf8");
|
|
assert.match(head, /if \(!sessionModel\) return \{ ok: false, response: modelUnavailable\(\) \}/);
|
|
assert.match(head, /function modelUnavailable\(\)[\s\S]*status: 503/);
|
|
assert.doesNotMatch(routes.consult, /languageModelConfigurationMessage/);
|
|
});
|
|
|
|
test("reports and birth-time guide resolve the database catalog before creating agents", () => {
|
|
// 原值: 也断言 POST /api/onboarding 经 getOnboardingAgent 读目录。
|
|
// 新值: 不再覆盖 onboarding 路由。
|
|
// 原因: 建议问题生成接口已删除。
|
|
for (const name of ["reports", "birth-time-guide"] as const) {
|
|
assert.match(routes[name], /await loadLanguageModelCatalog\(\)/, `${name} must await the DB catalog`);
|
|
assert.match(routes[name], /catalog\.models\.find/, `${name} must select from resolved DB models`);
|
|
}
|
|
assert.match(routes.reports, /createPersonalReportAgent\(model/);
|
|
assert.match(routes["birth-time-guide"], /getBirthTimeGuideAgent\(model\)/);
|
|
});
|