Files
multi-simadmin/packages/operation-registry/test/acceptance-gate.test.ts
T
chick f11877f13e feat(contracts): share notification, organization and device capability models
Move the Hub channel table, organization tags and device capability definitions
into the workspace packages so the control plane and the console validate the
same contract, and refresh the frozen upstream evidence for the new paths.
2026-09-05 18:52:54 +08:00

480 lines
19 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { upstream58e2204Operations } from '../src/upstream-58e2204.ts';
import {
operationAcceptance58e2204,
operationAcceptanceOverrides58e2204,
acceptancePolicyCatalog,
controlPlaneAcceptance,
fixtureDisposition58e2204,
renderOperationAcceptanceMatrix,
surfaceGroups58e2204,
policyGroups58e2204,
scenarioGroups58e2204,
availabilityGroups58e2204,
} from '../src/acceptance-58e2204.ts';
import { DENY_REASONS, selectReadonlyOperations } from '../../test-fixtures/scripts/collector.ts';
const fixtureManifestPath = fileURLToPath(
new URL('../../test-fixtures/src/manifest.json', import.meta.url),
);
const matrixPath = fileURLToPath(
new URL('../../../docs/product/operation-acceptance-matrix.md', import.meta.url),
);
const acceptanceSourcePath = fileURLToPath(
new URL('../src/acceptance-58e2204.ts', import.meta.url),
);
const iaPath = fileURLToPath(
new URL('../../../docs/product/information-architecture.md', import.meta.url),
);
const manifest = JSON.parse(await readFile(fixtureManifestPath, 'utf8'));
const byId = new Map(upstream58e2204Operations.map((o: any) => [o.operationId, o]));
const canonicalRoutes = new Set([
'/fleet',
'/instances/new',
'/instances/:id/overview',
'/instances/:id/cellular',
'/instances/:id/device-network',
'/instances/:id/messages',
'/instances/:id/calls',
'/instances/:id/esim',
'/instances/:id/notifications',
'/instances/:id/automation',
'/instances/:id/ota',
'/settings/instances/:id',
]);
const assertExactPartition = (groups: readonly { ids: readonly string[] }[]) => {
const ids = groups.flatMap((g) => [...g.ids]);
assert.equal(ids.length, 117);
assert.equal(new Set(ids).size, 117);
assert.deepEqual([...ids].sort(), [...byId.keys()].sort());
};
test('RED→GREEN: surface/policy/scenario/availability are literal exact operation partitions', () => {
for (const groups of [
surfaceGroups58e2204,
policyGroups58e2204,
scenarioGroups58e2204,
availabilityGroups58e2204,
])
assertExactPartition(groups);
assert.ok(surfaceGroups58e2204.filter((g) => g.surfaceId.startsWith('calls/')).length >= 5);
assert.ok(surfaceGroups58e2204.filter((g) => g.surfaceId.startsWith('cellular/')).length >= 5);
for (const group of surfaceGroups58e2204)
for (const id of group.ids) {
const row = operationAcceptance58e2204.find((x) => x.operationId === id)!;
assert.equal(row.surfaceId, group.surfaceId);
assert.equal(row.primaryRoute, group.primaryRoute);
}
const tuples = new Set(policyGroups58e2204.map((g) => Object.values(g.policies).join('/')));
assert.ok(policyGroups58e2204.length >= 8);
assert.ok(tuples.size >= 8);
const policy = (id: string) => policyGroups58e2204.find((g) => g.ids.includes(id))!;
assert.match(policy('getSmsList').groupId, /list/);
assert.match(policy('getHealth').groupId, /detail/);
assert.match(policy('getNetworkOperatorsScan').groupId, /scan/);
assert.match(policy('postData').groupId, /direct/);
assert.match(policy('postAuthLogin').groupId, /auth/);
assert.match(policy('postSmsBatchDelete').groupId, /destructive/);
assert.match(policy('postOtaApply').groupId, /ota/);
assert.ok(scenarioGroups58e2204.length >= 8);
const scenario = (id: string) => scenarioGroups58e2204.find((g) => g.ids.includes(id))!.scenarios;
assert.equal(scenario('getHealth').empty.applicable, false);
assert.equal(scenario('getSmsList').empty.applicable, true);
assert.equal(scenario('getStats').partial.applicable, true);
assert.equal(scenario('getAuthStatus').partial.applicable, false);
assert.equal(scenario('getNetworkOperatorsScan').empty.applicable, true);
assert.equal(scenario('getNetworkOperatorsScan').partial.applicable, true);
assert.equal(scenario('getNetworkOperatorsScan')['policy-forbidden'].applicable, true);
assert.equal(scenario('getNetworkOperatorsScan')['unknown-result'].applicable, false);
for (const id of [
'postSmsBatchDelete',
'postNotificationsQueueRetryAll',
'postNotificationsQueueClear',
])
assert.equal(scenario(id).partial.applicable, true, id);
for (const id of [
'postSystemReboot',
'postServiceRestart',
'deleteCallHistoryId',
'postBandLock',
'postOtaApply',
'postOtaUpload',
'postOtaOnlinePrepare',
'postOtaLatestRelease',
'postOtaCancel',
'postData',
])
assert.equal(scenario(id).partial.applicable, false, id);
for (const id of ['postData', 'postSmsBatchDelete']) {
assert.equal(scenario(id)['policy-forbidden'].applicable, true);
assert.equal(scenario(id)['unknown-result'].applicable, true);
}
for (const id of ['getHealth', 'getSmsList']) {
assert.equal(scenario(id)['policy-forbidden'].applicable, false);
assert.equal(scenario(id)['unknown-result'].applicable, false);
}
for (const group of scenarioGroups58e2204)
assert.equal(group.scenarios['owner-switch'].applicable, true);
assert.ok(availabilityGroups58e2204.length >= 4);
for (const group of availabilityGroups58e2204) assert.ok(group.reason.length > 20);
});
test('RED→GREEN: acceptance ledger is exact, Registry-bound and IA-owned', () => {
assert.equal(operationAcceptance58e2204.length, 117);
assert.equal(new Set(operationAcceptance58e2204.map((x) => x.operationId)).size, 117);
assert.deepEqual(
[...operationAcceptance58e2204.map((x) => x.operationId)].sort(),
[...byId.keys()].sort(),
);
for (const row of operationAcceptance58e2204) {
const op: any = byId.get(row.operationId);
assert.ok(op);
assert.equal(row.method, op.method);
assert.equal(row.pathTemplate, op.pathTemplate);
assert.equal(row.upstreamDomain, op.upstreamDomain);
assert.equal(row.riskLevel, op.riskLevel);
assert.equal(row.confirmationUX, op.confirmationPolicy);
assert.ok(canonicalRoutes.has(row.primaryRoute), row.operationId);
assert.ok(row.surfaceId);
assert.ok(row.uiStrategy);
assert.match(row.availability, /^(planned|unsupported-version|deferred-with-reason)$/);
if (row.availability === 'deferred-with-reason') assert.ok(row.availabilityReason);
assert.ok(
row.requiredStates.request.length &&
row.requiredStates.freshness.length &&
row.requiredStates.support.length,
);
assert.ok(row.requiredStates.scenarios.length || row.requiredStates.naRationale);
assert.ok(row.evidenceIds.length);
}
});
test('RED→GREEN: exact overrides and policy-bound acceptance are auditable', () => {
assert.deepEqual(
Object.keys(operationAcceptanceOverrides58e2204).sort(),
[...byId.keys()].sort(),
);
const policyFields = [
'preconditionPolicyId',
'stalePolicyId',
'unsupportedPolicyId',
'retryPolicyId',
'resultPolicyId',
];
for (const row of operationAcceptance58e2204) {
const override: any = (operationAcceptanceOverrides58e2204 as any)[row.operationId];
assert.ok(override);
assert.ok(row.availabilityReason?.length > 20, row.operationId);
assert.ok(row.versionEvidencePolicy?.length > 20, row.operationId);
assert.equal(row.surfaceId, override.surfaceId);
assert.equal(row.uiStrategy, override.uiStrategy);
for (const scenario of [
'empty',
'partial',
'policy-forbidden',
'unknown-result',
'owner-switch',
]) {
assert.equal(typeof row.scenarioAcceptance[scenario].applicable, 'boolean');
assert.ok(row.scenarioAcceptance[scenario].rationale.length > 8);
}
for (const field of policyFields) {
assert.equal((row as any)[field], override[field]);
assert.ok(
(acceptancePolicyCatalog as any)[field][(row as any)[field]],
`${row.operationId}:${field}`,
);
}
}
});
test('real acceptance is an explicit availability decision coherent with fixture disposition', async () => {
const fixture = new Map(fixtureDisposition58e2204.map((x) => [x.operationId, x]));
const valid = new Set([
'REAL_READ',
'REAL_READ_DEFERRED',
'REAL_WRITE_LATER',
'SIMULATED_HIGH_RISK',
'CONTRACT_ONLY',
]);
for (const group of availabilityGroups58e2204) {
assert.ok(valid.has(group.realAcceptance), group.groupId);
for (const id of group.ids) {
const row = operationAcceptance58e2204.find((x) => x.operationId === id)!;
assert.equal(row.realAcceptance, group.realAcceptance, id);
const status = fixture.get(id)!.runtimeFixtureStatus;
if (status === 'captured-readonly') assert.equal(group.realAcceptance, 'REAL_READ', id);
if (status === 'denied-readonly-candidate')
assert.equal(group.realAcceptance, 'REAL_READ_DEFERRED', id);
if (group.realAcceptance === 'REAL_READ') assert.equal(status, 'captured-readonly', id);
if (group.realAcceptance === 'REAL_READ_DEFERRED')
assert.equal(status, 'denied-readonly-candidate', id);
}
}
const source = await readFile(acceptanceSourcePath, 'utf8');
assert.doesNotMatch(source, /function\s+realAcceptance\s*\(/);
assert.doesNotMatch(source, /realAcceptanceIntent/);
assert.equal(
operationAcceptance58e2204.filter((x) => x.realAcceptance === 'REAL_READ').length,
39,
);
assert.equal(
operationAcceptance58e2204.filter((x) => x.realAcceptance === 'REAL_READ_DEFERRED').length,
11,
);
});
test('Phase 1 bootstrap gate defers Fleet component/E2E evidence to the Phase 5 implementation gate', async () => {
const ia = await readFile(iaPath, 'utf8');
const bootstrap = ia.match(
/### Phase 0 → Phase 1 workspace\/contract bootstrap gate([\s\S]*?)(?=### Phase 5 implementation gate)/,
)?.[1];
const phase5 = ia.match(/### Phase 5 implementation gate([\s\S]*)/)?.[1];
assert.ok(bootstrap);
assert.ok(phase5);
assert.doesNotMatch(bootstrap!, /Fleet[^\n]*组件\/E2E/);
assert.match(phase5!, /Fleet[^\n]*组件\/E2E/);
assert.match(phase5!, /\[ \][^\n]*Fleet/);
assert.match(bootstrap!, /\[x\][^\n]*最终独立规格与质量\/安全复审均已通过/);
assert.doesNotMatch(bootstrap!, /PENDING final independent review/);
assert.match(ia, /E2E[^\n]*N\/A[^\n]*Phase 5/);
});
test('control-plane flows are independent, structured, and preserve safety invariants', () => {
const ids = [
'instance-create',
'instance-update',
'instance-delete',
'secret-set',
'secret-preserve',
'secret-clear',
'config-import-preview',
'config-import-confirm',
'credential-verify',
'saved-secret-login',
'temporary-secret-login',
'logout',
'401-recovery',
'auth-setup',
'auth-password-change',
'auth-settings-read',
'auth-settings-write',
'job-cancel',
'job-retry',
'audit-export',
'system-settings-update',
];
assert.deepEqual(controlPlaneAcceptance.map((x) => x.flowId).sort(), ids.sort());
for (const flow of controlPlaneAcceptance)
for (const field of [
'route',
'risk',
'riskSubtype',
'confirmation',
'preconditions',
'result',
'failureRecovery',
'secretPolicy',
'evidence',
])
assert.ok(String((flow as any)[field]).length > 1, `${flow.flowId}:${field}`);
assert.match(
controlPlaneAcceptance.find((x) => x.flowId === 'instance-delete')!.result,
/new jobId.*two-phase/i,
);
for (const id of ['secret-set', 'secret-preserve', 'secret-clear'])
assert.match(
controlPlaneAcceptance.find((x) => x.flowId === id)!.secretPolicy,
/never.*value/i,
);
for (const id of ['saved-secret-login', 'temporary-secret-login'])
assert.match(
controlPlaneAcceptance.find((x) => x.flowId === id)!.failureRecovery,
/no automatic replay/i,
);
assert.match(
controlPlaneAcceptance.find((x) => x.flowId === 'job-retry')!.result,
/new jobId.*lineage/i,
);
});
test('product risk prose has no conflicting action summaries', async () => {
const root = fileURLToPath(new URL('../../../docs/product/', import.meta.url));
const names = [
'project-charter.md',
'personas-and-workflows.md',
'information-architecture.md',
'current-system-audit.md',
];
const docs = (await Promise.all(names.map((n) => readFile(`${root}${n}`, 'utf8')))).join('\n');
for (const expected of [
/notifications config[^\n]*R2[^\n]*Job/i,
/automation config[^\n]*R2[^\n]*Job/i,
/eSIM[^\n]*download[^\n]*R1[^\n]*direct/i,
/WLAN connect[^\n]*R1[^\n]*forget[^\n]*R2/i,
/DDNS config[^\n]*R2[^\n]*Job/i,
/baseband restart[^\n]*R3[^\n]*status[^\n]*R0/i,
])
assert.match(docs, expected);
assert.doesNotMatch(docs, /Notifications[^\n]*config R1/i);
assert.doesNotMatch(docs, /Automation[^\n]*config R1/i);
});
test('R2/R3, dedicated auth, write strategy, and split bulk/fleet policy are gated', () => {
for (const row of operationAcceptance58e2204) {
const op: any = byId.get(row.operationId);
if (['R2', 'R3'].includes(row.riskLevel)) {
assert.equal(
row.executionMode,
op.executionPolicy === 'dedicatedFlow' ? 'dedicated-flow' : 'preparation-job',
);
assert.match(row.preconditions, /fresh preflight/i);
assert.match(row.confirmationUX, /explicit|strong/);
assert.match(row.resultDestination, /jobs\/:jobId/);
assert.match(row.retryRecovery, /new Job lineage/i);
}
if (row.riskLevel === 'R3') assert.equal(op.capability, 'job');
if (row.method !== 'GET') {
assert.notEqual(row.uiStrategy, 'read-panel');
assert.notEqual(row.realAcceptance, 'REAL_READ');
assert.equal(row.fleetBatchable, false);
}
}
const auth = operationAcceptance58e2204.filter((x) =>
[
'postAuthSetup',
'postAuthPassword',
'postAuthSettings',
'postAuthLogin',
'postAuthLogout',
].includes(x.operationId),
);
assert.equal(auth.length, 5);
assert.ok(
auth.every(
(x) => x.uiStrategy === 'dedicated-auth-flow' && x.executionMode === 'dedicated-flow',
),
);
for (const id of ['postAuthLogin', 'postAuthLogout']) {
const row = auth.find((x) => x.operationId === id)!;
assert.equal(row.sessionSubtype, 'session-sensitive');
assert.match(row.retryRecovery, /no automatic replay/i);
assert.match(row.resultDestination, /metadata-only audit/i);
}
const sms = operationAcceptance58e2204.find((x) => x.operationId === 'postSmsBatchDelete')!;
assert.equal(sms.resourceBulk, true);
assert.equal(sms.fleetBatchable, false);
for (const id of ['postNotificationsQueueRetryAll', 'postNotificationsQueueClear'])
assert.equal(
operationAcceptance58e2204.find((x) => x.operationId === id)!.resourceBulk,
true,
id,
);
for (const id of [
'postSmsClear',
'postCallHistoryClear',
'postNotificationsLogsClear',
'postAutomationLogsClear',
])
assert.equal(
operationAcceptance58e2204.find((x) => x.operationId === id)!.resourceBulk,
false,
id,
);
const health = operationAcceptance58e2204.find((x) => x.operationId === 'getHealth')!;
assert.equal(health.fleetBatchable, true);
assert.equal(health.partialAggregationPolicy, 'per-item');
});
test('fixture disposition is authoritative and exactly follows collector plus 78-file manifest', () => {
assert.equal(fixtureDisposition58e2204.length, 117);
assert.equal(new Set(fixtureDisposition58e2204.map((x) => x.operationId)).size, 117);
const selected = selectReadonlyOperations(upstream58e2204Operations as any[]);
assert.equal(selected.selected.length, 39);
assert.equal(selected.denied.length, 11);
assert.equal(Object.keys(DENY_REASONS).length, 11);
const selectedIds = new Set(selected.selected.map((x: any) => x.operationId));
const denied = new Map(selected.denied.map((x: any) => [x.operationId, x.denyReason]));
const filesById = new Map<string, any[]>();
for (const file of manifest.files) {
const match = /instance-[12]--([^.]+)\.json$/.exec(file.path);
assert.ok(match);
const list = filesById.get(match[1]) ?? [];
list.push(file);
filesById.set(match[1], list);
}
for (const d of fixtureDisposition58e2204) {
if (selectedIds.has(d.operationId)) {
assert.equal(d.runtimeFixtureStatus, 'captured-readonly');
assert.equal(d.fixtureCount, 2);
assert.equal(filesById.get(d.operationId)?.length, 2);
assert.deepEqual(d.aliases, ['instance-1', 'instance-2']);
assert.ok(d.observedCategories.length);
} else if (denied.has(d.operationId)) {
assert.equal(d.runtimeFixtureStatus, 'denied-readonly-candidate');
assert.equal(d.reason, denied.get(d.operationId));
assert.equal(d.fixtureCount, 0);
} else {
assert.match(d.runtimeFixtureStatus, /^not-eligible-readonly-capture/);
assert.equal(d.fixtureCount, 0);
}
}
assert.equal(manifest.realFixtureCount, 78);
});
test('product risk prose defers to Registry and known conflicts stay corrected', async () => {
const productRoot = fileURLToPath(new URL('../../../docs/product/', import.meta.url));
const workflows = await readFile(`${productRoot}personas-and-workflows.md`, 'utf8');
const charter = await readFile(`${productRoot}project-charter.md`, 'utf8');
assert.doesNotMatch(workflows, /下载 profile[^\n]*R2 Job/);
assert.match(workflows, /WLAN connect 为 R1/);
assert.match(workflows, /notifications config 为 R2 Job/);
assert.match(workflows, /fleetBatchable=true/);
assert.match(charter, /resourceBulk.*fleetBatchable/);
for (const id of [
'postEsimProfiles',
'postEsimProfilesIccidEnable',
'postDeviceNetworkWlanConnect',
'postNotificationsConfig',
'postAutomationConfig',
'postDeviceNetworkDdnsConfig',
'postBasebandRestart',
]) {
assert.ok(
operationAcceptance58e2204.find((x) => x.operationId === id),
id,
);
}
});
test('generated matrix is synchronized and contains each operation exactly once', async () => {
const doc = await readFile(matrixPath, 'utf8');
assert.equal(doc, renderOperationAcceptanceMatrix());
const ids = [...doc.matchAll(/^\| `([^`]+)` \|/gm)].map((x) => x[1]).filter((id) => byId.has(id));
assert.equal(ids.length, 117);
assert.deepEqual(ids.sort(), [...byId.keys()].sort());
});
test('matrix renders metadata-only safe fixture categories for every operation', () => {
const doc = renderOperationAcceptanceMatrix();
const safeCategories = new Set(['success', 'unsupported', 'auth-required']);
const fixture = new Map(fixtureDisposition58e2204.map((x) => [x.operationId, x]));
const rows = [
...doc.matchAll(/^\| `([^`]+)` \|.*?\| ([^|]*categories=([^;|]+);[^|]*) \|/gm),
].filter((match) => byId.has(match[1]));
assert.equal(rows.length, 117);
for (const [, id, metadata, rendered] of rows) {
const disposition = fixture.get(id)!;
const expected = disposition.observedCategories.length
? [...disposition.observedCategories].sort().join(',')
: 'none';
assert.equal(rendered.trim(), expected, id);
for (const category of disposition.observedCategories)
assert.ok(safeCategories.has(category), `${id}:${category}`);
assert.doesNotMatch(metadata, /"(?:response|body|sourceInstanceAlias)"\s*:|https?:\/\//i, id);
}
assert.match(doc, /metadata-only/i);
});