329 lines
22 KiB
TypeScript
329 lines
22 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createHash } from 'node:crypto';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { upstream58e2204Operations } from '../src/upstream-58e2204.ts';
|
|
|
|
const fixturePath = fileURLToPath(new URL('./fixtures/main-routes-58e2204.json', import.meta.url));
|
|
const fixture = JSON.parse(await readFile(fixturePath, 'utf8'));
|
|
const key = (operation: { method: string; path?: string; pathTemplate?: string }) =>
|
|
`${operation.method} ${operation.pathTemplate ?? operation.path}`;
|
|
const evidenceFixturePath = fileURLToPath(new URL('./fixtures/source-evidence-58e2204.json', import.meta.url));
|
|
const evidenceFixture = JSON.parse(await readFile(evidenceFixturePath, 'utf8'));
|
|
const snapshotRoot = fileURLToPath(new URL('./fixtures/upstream-58e2204/', import.meta.url));
|
|
const snapshotManifest = JSON.parse(await readFile(`${snapshotRoot}/manifest.json`, 'utf8'));
|
|
const requiredFields = [
|
|
'operationId', 'upstreamDomain', 'method', 'pathTemplate', 'handler', 'capability', 'riskLevel',
|
|
'requestSchemaEvidence', 'responseSchemaEvidence', 'requestContentType', 'timeoutMs',
|
|
'idempotency', 'confirmationPolicy', 'batchable', 'syncSafe', 'sensitiveFields',
|
|
'auditedAtCommit', 'compatibilityAdapter', 'uiOwner', 'sourceEvidence', 'responseFixtureStatus',
|
|
] as const;
|
|
|
|
const expectedDomains = new Set([
|
|
'instances-auth', 'device-system', 'sim', 'cellular', 'radio-lock', 'data-connection',
|
|
'device-network', 'workmode-esim', 'messages', 'calls', 'notifications', 'automation', 'ota',
|
|
]);
|
|
const expectedOwners = new Set([
|
|
'instances-new', 'settings-instance', 'fleet', 'overview', 'cellular', 'device-network',
|
|
'messages', 'calls', 'esim', 'notifications', 'automation', 'ota',
|
|
]);
|
|
const operationKey = (operation: { method: string; pathTemplate: string; handler: string }) =>
|
|
`${operation.method} ${operation.pathTemplate} ${operation.handler}`;
|
|
|
|
test('sensitive fields are independently snapshot-rebuildable model-chain anchors', async () => {
|
|
const directionClaim = (operation: any, direction: string) =>
|
|
['request', 'path', 'query'].includes(direction) ? operation.requestEvidence : operation.responseEvidence;
|
|
for (const operation of upstream58e2204Operations as any[]) for (const field of operation.sensitiveFields) {
|
|
const evidence = field.evidence;
|
|
assert.ok(evidence, `${operation.operationId}: ${field.path} missing evidence`);
|
|
assert.match(evidence.sourceSha256, /^[a-f0-9]{64}$/);
|
|
assert.ok(evidence.sourceFile && evidence.symbol && (evidence.fieldPath || evidence.modelField || evidence.dynamicKey));
|
|
const source = await readFile(`${snapshotRoot}/${evidence.sourceFile}`, 'utf8');
|
|
const slice = source.split('\n').slice(evidence.startLine - 1, evidence.endLine).join('\n');
|
|
assert.equal(createHash('sha256').update(slice).digest('hex'), evidence.sourceSha256, `${operation.operationId}: ${field.path}`);
|
|
assert.ok(directionClaim(operation, field.direction), `${operation.operationId}: direction`);
|
|
if (evidence.handlerToken) assert.ok(slice.includes(evidence.handlerToken));
|
|
else if (evidence.dynamicKey) assert.match(slice, new RegExp(`["']${evidence.dynamicKey}["']`));
|
|
else {
|
|
assert.match(slice, new RegExp(`(?:struct|enum)\\s+${evidence.symbol}\\b`));
|
|
assert.match(slice, new RegExp(`\\b${evidence.modelField}\\s*:`));
|
|
assert.equal(evidence.modelField, field.path.replace(/\[\*\]/g, '').split('.').at(-1));
|
|
for (const link of evidence.containerPath ?? []) {
|
|
const linkSource = await readFile(`${snapshotRoot}/${link.sourceFile}`, 'utf8');
|
|
const linkSlice = linkSource.split('\n').slice(link.startLine - 1, link.endLine).join('\n');
|
|
assert.equal(createHash('sha256').update(linkSlice).digest('hex'), link.sourceSha256);
|
|
assert.match(linkSlice, new RegExp(`struct\\s+${link.symbol}\\b`));
|
|
assert.match(linkSlice, new RegExp(`\\b${link.field}\\s*:\\s*(?:Vec<)?${link.targetSymbol}`));
|
|
}
|
|
}
|
|
}
|
|
const get = (method: string, path: string) => (upstream58e2204Operations as any[]).find(o => o.method === method && o.pathTemplate === path);
|
|
assert.ok(!get('GET','/api/sim').sensitiveFields.some((f: any) => f.path.endsWith('.imei')));
|
|
assert.deepEqual(get('GET','/api/device-network/wlan/status').sensitiveFields.map((f: any) => f.path), [
|
|
'$.response.data.ssid', '$.response.data.ipv4_addresses', '$.response.data.ipv6_addresses']);
|
|
assert.deepEqual(get('GET','/api/device-network/wlan/profiles').sensitiveFields.map((f: any) => f.path), ['$.response.data.profiles[*].ssid']);
|
|
});
|
|
|
|
test('Phase 0.2 uses structured, snapshot-rebuildable evidence and directional redaction contracts', () => {
|
|
for (const operation of upstream58e2204Operations as any[]) {
|
|
for (const name of ['requestEvidence', 'responseEvidence']) {
|
|
const claim = operation[name];
|
|
assert.ok(claim && typeof claim === 'object', `${operation.operationId}: ${name}`);
|
|
assert.match(claim.kind, /^(none|path|query|json|multipart|bytes|explicit-return|api-response|dynamic-json|opaque)$/);
|
|
assert.match(claim.sourceSha256, /^[a-f0-9]{64}$/);
|
|
assert.ok(claim.sourceFile && Number.isInteger(claim.startLine) && Number.isInteger(claim.endLine));
|
|
assert.ok(Array.isArray(claim.symbols));
|
|
assert.ok(Array.isArray(claim.modelEvidence));
|
|
}
|
|
for (const field of operation.sensitiveFields) {
|
|
assert.match(field.direction, /^(request|response|path|query|session)$/);
|
|
assert.match(field.path, /^\$\.(?:body|response|path|query|session)/);
|
|
assert.ok(field.reason);
|
|
assert.match(field.redactionMode, /^(drop|mask)$/);
|
|
assert.ok(field.sourceAnchor?.symbol || field.sourceAnchor?.token);
|
|
}
|
|
}
|
|
const get = (method: string, path: string) => (upstream58e2204Operations as any[]).find(o => o.method === method && o.pathTemplate === path);
|
|
assert.ok(get('GET', '/api/sms/list').responseEvidence.symbols.some((s: any) => s.symbol === 'SmsListResponse'));
|
|
assert.ok(get('GET', '/api/device').responseEvidence.symbols.some((s: any) => s.symbol === 'DeviceInfoResponse'));
|
|
assert.ok(get('GET', '/api/network').responseEvidence.symbols.some((s: any) => s.symbol === 'NetworkInfoResponse'));
|
|
assert.ok(get('POST', '/api/ota/upload').responseEvidence.symbols.some((s: any) => s.symbol === 'OtaUploadResponse'));
|
|
});
|
|
|
|
test('structured claims are rebuilt from the independent snapshot, with exact braces and symbols', async () => {
|
|
for (const operation of upstream58e2204Operations as any[]) for (const claim of [operation.requestEvidence, operation.responseEvidence]) {
|
|
const source = await readFile(`${snapshotRoot}/${claim.sourceFile}`, 'utf8');
|
|
const slice = source.split('\n').slice(claim.startLine - 1, claim.endLine).join('\n');
|
|
assert.equal(createHash('sha256').update(slice).digest('hex'), claim.sourceSha256, operation.operationId);
|
|
const fn = operation.handler.split('::').at(-1); const signature = new RegExp(`(?:pub\\s+)?async\\s+fn\\s+${fn}\\b`).exec(source);
|
|
assert.ok(signature); const open = source.indexOf('{', signature!.index); let depth = 0, close = -1;
|
|
for (let i = open; i < source.length; i++) { if (source[i] === '{') depth++; else if (source[i] === '}' && --depth === 0) { close = i; break; } }
|
|
assert.equal(source.slice(0, close + 1).split('\n').length, claim.endLine, `${operation.operationId}: exact end`);
|
|
for (const symbol of claim.symbols) assert.ok(slice.includes(symbol.symbol) || claim.modelEvidence.some((m: any) => m.symbol === symbol.symbol), `${operation.operationId}: ${symbol.symbol}`);
|
|
for (const model of claim.modelEvidence) {
|
|
const modelSource = await readFile(`${snapshotRoot}/${model.sourceFile}`, 'utf8');
|
|
const modelSlice = modelSource.split('\n').slice(model.startLine - 1, model.endLine).join('\n');
|
|
assert.equal(createHash('sha256').update(modelSlice).digest('hex'), model.sourceSha256, model.symbol);
|
|
assert.match(modelSlice, new RegExp(`(?:struct|enum)\\s+${model.symbol}\\b`));
|
|
for (const field of model.fields) assert.match(modelSlice, new RegExp(`\\b${field}\\s*:`));
|
|
}
|
|
if (claim.kind === 'dynamic-json') assert.match(slice, /json!|Json\s*\(|ApiResponse::/);
|
|
}
|
|
});
|
|
|
|
test('Axum route chains are independently parsed from frozen main.rs', async () => {
|
|
const source = await readFile(`${snapshotRoot}/backend/src/main.rs`, 'utf8'); const parsed: string[] = [];
|
|
for (let from = 0; ;) {
|
|
const start = source.indexOf('.route(', from); if (start < 0) break; let depth = 0, quote = false, end = -1;
|
|
for (let i = start + 6; i < source.length; i++) { const c = source[i]; if (c === '"' && source[i - 1] !== '\\') quote = !quote; if (!quote && c === '(') depth++; else if (!quote && c === ')' && --depth === 0) { end = i; break; } }
|
|
const chain = source.slice(start, end + 1); from = end + 1; const path = /\.route\(\s*"([^"]+)"/.exec(chain)?.[1]; if (!path) continue;
|
|
for (const match of chain.matchAll(/\.(get|post|delete)\(([\w:]+)\)|\b(get|post|delete)\(([\w:]+)\)/g)) { const method = (match[1] ?? match[3]).toUpperCase(); const handler = match[2] ?? match[4]; if (handler !== 'options_handler') parsed.push(`${method} ${path} ${handler}`); }
|
|
}
|
|
const expected = fixture.routes.map((r: any) => `${r.method} ${r.path} ${r.handler}`).sort();
|
|
assert.deepEqual(parsed.sort(), expected); assert.deepEqual(parsed.sort(), (upstream58e2204Operations as any[]).map(operationKey).sort());
|
|
});
|
|
|
|
test('Bruno files are independently parsed and agree with structured request claims', async () => {
|
|
for (const evidence of evidenceFixture.operations) for (const bruno of evidence.brunoEvidence ?? []) {
|
|
const text = await readFile(`${snapshotRoot}/${bruno.file}`, 'utf8'); const method = /\b(get|post|delete)\s*\{/.exec(text)?.[1].toUpperCase();
|
|
const url = /url:\s*(?:\{\{baseUrl\}\}|https?:\/\/[^/\s]+)(\/[^\s]+)/.exec(text)?.[1]; assert.equal(method, bruno.method); assert.equal(url, bruno.path);
|
|
const bodyMode = /body:(json|multipart|text)/.exec(text)?.[1] ?? 'none'; assert.equal(bodyMode, bruno.bodyMode, bruno.file);
|
|
const operation: any = (upstream58e2204Operations as any[]).find(o => o.method === method && (o.pathTemplate === url || new RegExp(`^${o.pathTemplate.replace(/\{[^}]+\}/g, '[^/]+')}$`).test(url!))); assert.ok(operation, bruno.file);
|
|
if (bodyMode === 'json' && bruno.bodyFields.length) assert.equal(operation.requestEvidence.kind, 'json', bruno.file);
|
|
if (bodyMode === 'json' && !bruno.bodyFields.length) assert.ok(['none','path','query','json'].includes(operation.requestEvidence.kind), `${bruno.file}: empty Bruno JSON has no contract fields`);
|
|
for (const field of bruno.bodyFields) assert.match(text, new RegExp(`\\b${field}\\b`));
|
|
}
|
|
});
|
|
|
|
test('frozen fixture has the audited upstream route totals', () => {
|
|
assert.equal(fixture.upstreamCommit, '58e220411d6599609f0eeda01eb7016e9212f970');
|
|
assert.equal(fixture.routes.length, 117);
|
|
assert.equal(new Set(fixture.routes.map(key)).size, 117);
|
|
assert.equal(new Set(fixture.routes.map((route: { path: string }) => route.path)).size, 100);
|
|
assert.deepEqual(
|
|
Object.fromEntries(['GET', 'POST', 'DELETE'].map(method => [method, fixture.routes.filter((r: { method: string }) => r.method === method).length])),
|
|
{ GET: 50, POST: 62, DELETE: 5 },
|
|
);
|
|
});
|
|
|
|
test('registry is exactly route-parity complete with independent fixture', () => {
|
|
assert.equal(upstream58e2204Operations.length, 117);
|
|
const fixtureTriples = fixture.routes.map((r: any) => `${r.method} ${r.path} ${r.handler}`).sort();
|
|
assert.deepEqual(upstream58e2204Operations.map(operationKey).sort(), fixtureTriples);
|
|
assert.equal(new Set(upstream58e2204Operations.map(operation => operation.pathTemplate)).size, 100);
|
|
assert.equal(new Set(upstream58e2204Operations.map(operation => operation.operationId)).size, 117);
|
|
for (const operation of upstream58e2204Operations) {
|
|
const route = fixture.routes.find((r: any) => `${r.method} ${r.path} ${r.handler}` === operationKey(operation));
|
|
assert.ok(route, operationKey(operation));
|
|
assert.ok(operation.sourceEvidence.includes(route.source), `${operation.operationId}: exact main.rs evidence`);
|
|
}
|
|
});
|
|
|
|
test('frozen static-contract evidence is complete and bound to routes', () => {
|
|
assert.equal(evidenceFixture.upstreamCommit, fixture.upstreamCommit);
|
|
assert.equal(evidenceFixture.operations.length, 117);
|
|
assert.deepEqual(evidenceFixture.operations.map((e: any) => `${e.method} ${e.path} ${e.handler}`).sort(),
|
|
fixture.routes.map((r: any) => `${r.method} ${r.path} ${r.handler}`).sort());
|
|
for (const evidence of evidenceFixture.operations) {
|
|
assert.match(evidence.handlerEvidence, /^backend\/src\/(?:handlers|auth|notification_queue)\.rs:\d+-\d+$/);
|
|
assert.match(evidence.sha256, /^[a-f0-9]{64}$/);
|
|
assert.ok(evidence.requestSummary && evidence.responseSummary);
|
|
}
|
|
});
|
|
|
|
test('source evidence is independently reproducible from frozen whole-file snapshots', async () => {
|
|
assert.equal(snapshotManifest.upstreamCommit, fixture.upstreamCommit);
|
|
assert.equal(snapshotManifest.brunoFileCount, 82);
|
|
for (const [file, expected] of Object.entries(snapshotManifest.files)) {
|
|
const bytes = await readFile(`${snapshotRoot}/${file}`);
|
|
assert.equal(createHash('sha256').update(bytes).digest('hex'), expected, file);
|
|
}
|
|
for (const evidence of evidenceFixture.operations) {
|
|
const range = evidence.handlerRange;
|
|
assert.ok(range && Number.isInteger(range.startLine) && Number.isInteger(range.endLine), evidence.handler);
|
|
const source = await readFile(`${snapshotRoot}/${range.file}`, 'utf8');
|
|
const slice = source.split('\n').slice(range.startLine - 1, range.endLine).join('\n');
|
|
assert.equal(createHash('sha256').update(slice).digest('hex'), evidence.sha256, evidence.handler);
|
|
assert.match(slice, new RegExp(`async\\s+fn\\s+${evidence.handler.split('::').at(-1)}\\b`));
|
|
assert.equal((slice.match(/{/g) ?? []).length, (slice.match(/}/g) ?? []).length, evidence.handler);
|
|
assert.ok(evidence.extractorTokens.every((token: string) => slice.replace(/\\s+/g, '').includes(token.replace(/\\s+/g, ''))), evidence.handler);
|
|
assert.ok(evidence.responseKind && evidence.responseTypeEvidence !== 'Rust return impl IntoResponse', evidence.handler);
|
|
}
|
|
});
|
|
|
|
test('high-value response, confirmation, sensitive-field and Bruno contracts are fixed', () => {
|
|
const operations: any[] = upstream58e2204Operations as any;
|
|
const byPath = (method: string, path: string) => operations.find(o => o.method === method && o.pathTemplate === path)!;
|
|
assert.match(byPath('GET', '/api/device').responseSchemaEvidence, /ApiResponse<DeviceInfoResponse>/);
|
|
assert.match(byPath('GET', '/api/network').responseSchemaEvidence, /ApiResponse<NetworkInfoResponse>/);
|
|
assert.match(byPath('POST', '/api/ota/upload').responseSchemaEvidence, /ApiResponse<OtaUploadResponse>/);
|
|
for (const operation of operations) {
|
|
if (operation.riskLevel === 'R2') assert.ok(['explicit', 'strong'].includes(operation.confirmationPolicy), operation.operationId);
|
|
if (operation.riskLevel === 'R3') assert.equal(operation.confirmationPolicy, 'strong', operation.operationId);
|
|
}
|
|
const expectedSensitive: Record<string, string[]> = {
|
|
'POST /api/esim/profiles': ['$.body.matching_id', '$.body.confirmation_code', '$.body.imei'],
|
|
'POST /api/sms/send': ['$.body.phone_number', '$.body.content'],
|
|
'GET /api/sms/conversation': ['$.query.phone_number'],
|
|
'POST /api/call/dial': ['$.body.phone_number'],
|
|
'POST /api/auth/setup': ['$.body.password'],
|
|
'POST /api/auth/login': ['$.body.password'],
|
|
'POST /api/auth/password': ['$.body.new_password'],
|
|
'POST /api/device-network/wlan/connect': ['$.body.ssid', '$.body.password'],
|
|
};
|
|
for (const [route, fields] of Object.entries(expectedSensitive)) {
|
|
const [method, path] = route.split(' ');
|
|
for (const field of fields) assert.ok(byPath(method, path).sensitiveFields.some((claim: any) => claim.path === field), `${route}: ${field}`);
|
|
}
|
|
for (const evidence of evidenceFixture.operations) {
|
|
assert.match(evidence.evidenceLevel, /^route\+handler(?:\+model)?(?:\+bruno)?$/);
|
|
if (evidence.brunoEvidence) assert.ok(evidence.brunoEvidence.length > 0);
|
|
}
|
|
});
|
|
|
|
test('every operation carries auditable orchestration metadata', () => {
|
|
for (const operation of upstream58e2204Operations) {
|
|
for (const field of requiredFields) assert.ok(field in operation, `${operation.operationId}: missing ${field}`);
|
|
assert.ok(expectedDomains.has(operation.upstreamDomain), `${operation.operationId}: unknown domain`);
|
|
assert.ok(expectedOwners.has(operation.uiOwner), `${operation.operationId}: unknown owner`);
|
|
assert.match(operation.operationId, /^[a-z][A-Za-z0-9]*$/);
|
|
assert.ok(['R0', 'R1', 'R2', 'R3'].includes(operation.riskLevel));
|
|
assert.ok(Number.isInteger(operation.timeoutMs) && operation.timeoutMs > 0);
|
|
assert.ok(Array.isArray(operation.sensitiveFields));
|
|
assert.ok(Array.isArray(operation.sourceEvidence) && operation.sourceEvidence.length > 0);
|
|
assert.ok(operation.sourceEvidence.some(evidence => /^backend\/src\/main\.rs:\d+-\d+$/.test(evidence)));
|
|
assert.ok(operation.handler && operation.requestSchemaEvidence && operation.responseSchemaEvidence);
|
|
for (const field of ['requestSchemaEvidence', 'responseSchemaEvidence', 'requestContentType', 'idempotency'] as const) {
|
|
assert.doesNotMatch(operation[field], /TODO_EVIDENCE|not frozen/i, `${operation.operationId}: ${field}`);
|
|
}
|
|
assert.ok(['safe', 'idempotent', 'non-idempotent', 'conditional'].includes(operation.idempotency));
|
|
assert.equal(operation.auditedAtCommit, fixture.upstreamCommit);
|
|
assert.equal(operation.compatibilityAdapter, 'none-pinned-commit');
|
|
if (operation.riskLevel === 'R2') assert.equal(operation.syncSafe, false);
|
|
if (operation.riskLevel === 'R3') {
|
|
assert.equal(operation.capability, 'job');
|
|
assert.equal(operation.syncSafe, false);
|
|
assert.ok(['explicit', 'strong'].includes(operation.confirmationPolicy));
|
|
}
|
|
if (operation.batchable && operation.riskLevel === 'R2') assert.equal(operation.capability, 'job');
|
|
}
|
|
});
|
|
|
|
test('dangerous authentication endpoints use dedicated flows, never generic proxy', () => {
|
|
const dangerous = upstream58e2204Operations.filter(operation =>
|
|
['/api/auth/setup', '/api/auth/password'].includes(operation.pathTemplate));
|
|
assert.equal(dangerous.length, 2);
|
|
for (const operation of dangerous) {
|
|
assert.equal(operation.riskLevel, 'R3');
|
|
assert.equal(operation.executionPolicy, 'dedicatedFlow');
|
|
}
|
|
const loginLogout = upstream58e2204Operations.filter(operation =>
|
|
['/api/auth/login', '/api/auth/logout'].includes(operation.pathTemplate));
|
|
assert.equal(loginLogout.length, 2);
|
|
assert.ok(loginLogout.every(operation => operation.riskLevel === 'R1' && operation.sessionSensitive));
|
|
const authSettingsPost = upstream58e2204Operations.find(operation => key(operation) === 'POST /api/auth/settings');
|
|
assert.ok(authSettingsPost && ['R2', 'R3'].includes(authSettingsPost.riskLevel));
|
|
assert.equal(authSettingsPost?.syncSafe, false);
|
|
});
|
|
|
|
test('product risk floors and auth replay policy are enforced', () => {
|
|
const byPath = (method: string, path: string) => upstream58e2204Operations.find(o => o.method === method && o.pathTemplate === path)!;
|
|
const r3 = [
|
|
['DELETE','/api/esim/profiles/{iccid}'], ['DELETE','/api/call/history/{id}'], ['POST','/api/call/history/clear'],
|
|
['POST','/api/sms/batch-delete'], ['DELETE','/api/sms/conversation/{phone_number}'], ['DELETE','/api/sms/message/{id}'], ['POST','/api/sms/clear'],
|
|
['POST','/api/notifications/logs/clear'], ['POST','/api/notifications/queue/clear'], ['DELETE','/api/notifications/queue/{id}'],
|
|
['POST','/api/automation/logs/clear'],
|
|
];
|
|
for (const [method, path] of r3) {
|
|
const operation = byPath(method, path);
|
|
assert.equal(operation.riskLevel, 'R3', `${method} ${path}`);
|
|
assert.equal(operation.capability, 'job');
|
|
assert.ok(['explicit', 'strong'].includes(operation.confirmationPolicy));
|
|
}
|
|
const r2 = [
|
|
['POST','/api/sms/send'], ['POST','/api/notifications/test/{channel}'],
|
|
['POST','/api/notifications/queue/retry-all'], ['POST','/api/notifications/queue/{id}/retry'],
|
|
['POST','/api/automation/test/{task_id}'],
|
|
];
|
|
for (const [method, path] of r2) {
|
|
const operation = byPath(method, path);
|
|
assert.ok(['R2', 'R3'].includes(operation.riskLevel), `${method} ${path}`);
|
|
assert.equal(operation.capability, 'job');
|
|
assert.equal(operation.syncSafe, false);
|
|
}
|
|
for (const path of ['/api/auth/login', '/api/auth/logout']) {
|
|
const operation = byPath('POST', path);
|
|
assert.equal(operation.executionPolicy, 'dedicatedFlow');
|
|
assert.equal(operation.sessionSensitive, true);
|
|
assert.equal(operation.automaticReplay, 'forbidden');
|
|
}
|
|
assert.equal(byPath('POST', '/api/auth/settings').executionPolicy, 'dedicatedFlow');
|
|
});
|
|
|
|
test('evidence matrix lists every registry operation', async () => {
|
|
const documentPath = fileURLToPath(new URL('../../../docs/api/simadmin-upstream-58e2204.md', import.meta.url));
|
|
const document = await readFile(documentPath, 'utf8');
|
|
const rows = document.split('\n').filter(line => /^\| `(?:GET|POST|DELETE)` \|/.test(line));
|
|
assert.equal(rows.length, 117);
|
|
for (const operation of upstream58e2204Operations) {
|
|
const row = rows.find(row => row.includes(`\`${operation.method}\``) && row.includes(`\`${operation.pathTemplate}\``) && row.includes(`\`${operation.operationId}\``));
|
|
assert.ok(row, key(operation));
|
|
const evidence = evidenceFixture.operations.find((e: any) => `${e.method} ${e.path} ${e.handler}` === operationKey(operation));
|
|
assert.ok(evidence, operation.operationId);
|
|
for (const value of [operation.upstreamDomain, operation.uiOwner, operation.handler, operation.requestContentType,
|
|
operation.idempotency, operation.riskLevel, operation.capability, evidence.sha256.slice(0, 16)]) {
|
|
assert.ok(row.includes(value), `${operation.operationId}: document drift for ${value}`);
|
|
}
|
|
}
|
|
assert.doesNotMatch(document, /TODO_EVIDENCE|not frozen/i);
|
|
assert.match(document, /Phase 0\.3[^\n]*真实响应样本/);
|
|
assert.match(document, /dynamic-json/);
|
|
assert.match(document, /58e220411d6599609f0eeda01eb7016e9212f970/);
|
|
for (const operation of upstream58e2204Operations as any[]) for (const field of operation.sensitiveFields) {
|
|
const exactRow = `| ${operation.method} | \`${operation.pathTemplate}\` | ${field.direction} | \`${field.path}\` | ${field.redactionMode} | ${field.reason} |`;
|
|
assert.ok(document.includes(exactRow), `${operation.operationId}: sensitive ledger drift ${field.path}`);
|
|
}
|
|
});
|