feat: ship deterministic parser and lifecycle release
This commit is contained in:
1 parent
208c434b31
commit
7b5d855b09
176 files changed
+20248
-1362
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
BUSINESS_ROUTES,
|
||||
allowedParserModesForRoute,
|
||||
effectiveParserModeForRoute,
|
||||
isParserModeAllowedForRoute,
|
||||
type ParserMode
|
||||
} from '../src/business-routes.js';
|
||||
import { loadConfig } from '../src/config.js';
|
||||
import { resolveParserModeSnapshot, TaskError, TaskService } from '../src/task-service.js';
|
||||
|
||||
const rosterRouteIds = [
|
||||
'passenger_list_import_independent',
|
||||
'passenger_list_import_shared_child'
|
||||
] as const;
|
||||
const parserModes: ParserMode[] = ['ai', 'shadow', 'auto', 'program'];
|
||||
|
||||
test('only the two roster routes are hard Program-only in the machine registry', () => {
|
||||
const programOnly = BUSINESS_ROUTES.filter((route) => route.parserPolicy === 'program_only');
|
||||
assert.deepEqual(programOnly.map((route) => route.routeId), rosterRouteIds);
|
||||
|
||||
for (const route of BUSINESS_ROUTES) {
|
||||
const expectedModes = rosterRouteIds.includes(route.routeId as typeof rosterRouteIds[number])
|
||||
? ['program']
|
||||
: parserModes;
|
||||
assert.deepEqual(allowedParserModesForRoute(route), expectedModes, route.routeId);
|
||||
for (const mode of parserModes) {
|
||||
assert.equal(
|
||||
isParserModeAllowedForRoute(route, mode),
|
||||
expectedModes.includes(mode),
|
||||
`${route.routeId}:${mode}`
|
||||
);
|
||||
assert.equal(
|
||||
effectiveParserModeForRoute(route, mode),
|
||||
expectedModes.includes(mode) ? mode : 'program',
|
||||
`${route.routeId}:${mode}:effective`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('roster task snapshots stay Program with missing or hostile stored settings', async () => {
|
||||
const routeText = (routeId: typeof rosterRouteIds[number]) => {
|
||||
const route = BUSINESS_ROUTES.find((candidate) => candidate.routeId === routeId);
|
||||
assert.ok(route);
|
||||
return `${route.directive}\n单号:TEST-ORDER`;
|
||||
};
|
||||
|
||||
for (const routeId of rosterRouteIds) {
|
||||
for (const storedMode of [null, ...parserModes] as const) {
|
||||
const client = {
|
||||
async query() {
|
||||
return storedMode === null
|
||||
? { rowCount: 0, rows: [] }
|
||||
: { rowCount: 1, rows: [{ mode: storedMode, revision: 7 }] };
|
||||
}
|
||||
};
|
||||
const snapshot = await resolveParserModeSnapshot(client as never, 'test-org', routeText(routeId));
|
||||
assert.equal(snapshot.routeId, routeId);
|
||||
assert.equal(snapshot.mode, 'program');
|
||||
assert.equal(snapshot.engineAffinity, null);
|
||||
assert.equal(snapshot.revision, storedMode === null ? 0 : 7);
|
||||
}
|
||||
}
|
||||
|
||||
const configurableRoute = BUSINESS_ROUTES.find((route) => route.routeId === 'team_order_create');
|
||||
assert.ok(configurableRoute);
|
||||
const configurable = await resolveParserModeSnapshot({
|
||||
async query() {
|
||||
return { rowCount: 1, rows: [{ mode: 'shadow', revision: 4 }] };
|
||||
}
|
||||
} as never, 'test-org', configurableRoute.directive);
|
||||
assert.equal(configurable.mode, 'shadow');
|
||||
assert.equal(configurable.revision, 4);
|
||||
});
|
||||
|
||||
test('the public route mutation rejects AI, Shadow, and Auto before touching settings storage', async () => {
|
||||
const service = new TaskService(loadConfig({
|
||||
NODE_ENV: 'test',
|
||||
FIELD_ENCRYPTION_KEY: Buffer.alloc(32, 17).toString('base64'),
|
||||
DATABASE_URL: 'postgresql://invalid:invalid@127.0.0.1:1/invalid'
|
||||
}));
|
||||
const context = { organizationId: 'test-org', userId: 'test-user', requestId: 'test-request' };
|
||||
for (const routeId of rosterRouteIds) {
|
||||
for (const mode of parserModes.filter((candidate) => candidate !== 'program')) {
|
||||
await assert.rejects(
|
||||
service.setParserRoutingMode(context, routeId, mode, 0),
|
||||
(error: unknown) => error instanceof TaskError
|
||||
&& error.code === 'parser_mode_not_supported'
|
||||
&& error.statusCode === 409
|
||||
&& error.details.parser_policy === 'program_only'
|
||||
&& Array.isArray(error.details.allowed_modes)
|
||||
&& error.details.allowed_modes.join(',') === 'program',
|
||||
`${routeId}:${mode}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('TaskService exposes and enforces the registry parser policy at every AI override boundary', async () => {
|
||||
const source = await readFile(new URL('../src/task-service.ts', import.meta.url), 'utf8');
|
||||
assert.match(source, /allowed_modes:/);
|
||||
assert.match(source, /parser_policy:/);
|
||||
assert.match(source, /isParserModeAllowedForRoute\(route, mode\)/);
|
||||
assert.match(source, /parser_mode_not_supported/);
|
||||
assert.match(source, /effectiveParserModeForRoute\(route, 'ai'\)/);
|
||||
assert.match(source, /ai_reparse_not_supported/);
|
||||
assert.match(source, /enforceFixedParserModeForClaim/);
|
||||
});
|
||||
Reference in new issue
Block a user