Files
pikasTech-unidesk/scripts/decision-center-query-contract-test.ts
T
2026-05-21 07:43:03 +00:00

115 lines
4.2 KiB
TypeScript

import { readFileSync } from "node:fs";
type JsonRecord = Record<string, unknown>;
function assertCondition(condition: unknown, message: string, detail: JsonRecord = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function source(path: string): string {
return readFileSync(path, "utf8");
}
function includesAll(text: string, snippets: string[]): boolean {
return snippets.every((snippet) => text.includes(snippet));
}
export function runDecisionCenterQueryContract(): JsonRecord {
const service = source("src/components/microservices/decision-center/src/index.ts");
const cli = source("scripts/src/decision-center.ts");
const frontend = source("src/components/frontend/src/decision-center.tsx");
assertCondition(
includesAll(service, [
'url.pathname === "/api/requirements" && method === "GET"',
"listRecords(url, { requirementOnly: true })",
"type IN ('decision', 'goal', 'external_goal', 'internal_goal', 'blocker', 'debt', 'experiment')",
"doc_no",
"doc_type",
"doc_priority",
"doc_year",
"doc_seq",
"signer",
"issued_at",
"effective_scope",
"supersedes",
"superseded_by",
]),
"requirements list route must stay on the records model, exclude meetings, and expose document fields",
);
assertCondition(
includesAll(service, [
"CASE WHEN ${includeBody}::boolean THEN body ELSE left(body, 4000) END AS body",
"return rows.map((row) => recordFromRow(row, { includeBody }));",
"body: includeBody ? body : \"\"",
]),
"record list must be body-light by default while preserving summaries",
);
assertCondition(
includesAll(service, [
"sourceFileFilterFromUrl(url)",
"url.searchParams.get(\"sourceFile\") ?? url.searchParams.get(\"sourcePath\") ?? url.searchParams.get(\"source\")",
"AND (${sourceFile || null}::text IS NULL OR source_file = ${sourceFile || null})",
"getDiaryEntry(key, { sourceFile: sourceFileFilterFromUrl(url) })",
]),
"diary date lookup must support sourceFile disambiguation for same-day entries",
);
assertCondition(
service.split("AND (${sourceFile || null}::text IS NULL OR source_file = ${sourceFile || null})").length >= 3,
"diary sourceFile filter must cover both read and date-key upsert lookup paths",
);
assertCondition(
includesAll(service, [
"CASE WHEN ${includeBody}::boolean THEN body ELSE left(body, 4000) END AS body",
"return rows.map((row) => diaryEntryFromRow(row, { includeBody }));",
]),
"diary list must be body-light by default while preserving summaries",
);
assertCondition(
includesAll(cli, [
"if (args.includes(\"--include-body\")) params.set(\"includeBody\", \"true\")",
"function diaryShowQuery(args: string[]): string",
"params.set(\"sourceFile\", sourceFile)",
"showDiary(diaryId, args.slice(3))",
"`/api/requirements${query ? `?${query}` : \"\"}`",
"parseDocumentNo(optionValue(args, [\"--doc-no\", \"--docNo\", \"--document-no\", \"--documentNo\"])",
"params.set(\"docNo\", docNo)",
"payload.docType = docType",
"payload.signer = signer",
]),
"CLI must expose bounded list opt-in, diary source disambiguation, and document fields",
);
assertCondition(
includesAll(frontend, [
"function diaryEntryLookupPath(entry: any): string",
"const key = entry?.id || entry?.date",
"if (entry?.sourceFile) params.set(\"sourceFile\", String(entry.sourceFile))",
"decisionApi(apiBaseUrl, diaryEntryLookupPath(entry))",
"if (!record?.id || record?.body) return",
"`/api/records/${encodeURIComponent(record.id)}`",
]),
"frontend must select exact diary rows and fetch full record bodies before editing list results",
);
return {
ok: true,
checks: [
"requirements-route",
"body-light-record-list-query",
"body-light-diary-list-query",
"diary-source-disambiguation",
"cli-bounded-list-diary-source-and-document-query",
"frontend-exact-diary-row-and-record-edit-body",
],
};
}
if (import.meta.main) {
process.stdout.write(`${JSON.stringify(runDecisionCenterQueryContract(), null, 2)}\n`);
}