66 lines
3.3 KiB
TypeScript
66 lines
3.3 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { readFile } from 'node:fs/promises';
|
|
import test from 'node:test';
|
|
import { isInboundAgentBusTask, parseAgentBusFrame } from '../src/agentbus.js';
|
|
import {
|
|
initialTaskIntakeState,
|
|
isPassengerRosterRouteId
|
|
} from '../src/task-service.js';
|
|
|
|
test('only roster routes pause at intake until a workbook arrives', () => {
|
|
assert.equal(isPassengerRosterRouteId('passenger_list_import_independent'), true);
|
|
assert.equal(isPassengerRosterRouteId('passenger_list_import_shared_child'), true);
|
|
assert.equal(isPassengerRosterRouteId('order_create_independent'), false);
|
|
assert.deepEqual(initialTaskIntakeState('passenger_list_import_independent'), {
|
|
status: 'awaiting_attachment',
|
|
stage: 'intake',
|
|
message: '已识别名单业务,等待 .xls 或 .xlsx 名单附件;附件到齐前不会开始解析或执行。'
|
|
});
|
|
assert.equal(initialTaskIntakeState('team_order_create').status, 'parse_queued');
|
|
assert.equal(initialTaskIntakeState(null).status, 'parse_queued');
|
|
});
|
|
|
|
test('attachment-only AgentBus frames are eligible inbound tasks', () => {
|
|
const frame = parseAgentBusFrame(JSON.stringify({
|
|
id: 'frame-roster-1',
|
|
type: 'event',
|
|
from: 'wechat:user',
|
|
payload: {
|
|
attachments: [{
|
|
name: 'roster.xlsx',
|
|
content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
size: 1234,
|
|
url: 'https://files.example.com/roster.xlsx'
|
|
}]
|
|
}
|
|
}));
|
|
assert.ok(frame);
|
|
assert.equal(isInboundAgentBusTask(frame), true);
|
|
});
|
|
|
|
test('roster attachment source contract gates claims and scrubs normalized plaintext', async () => {
|
|
const source = await readFile(new URL('../src/task-service.ts', import.meta.url), 'utf8');
|
|
assert.match(source, /status = 'awaiting_attachment'/);
|
|
assert.match(source, /input_attachment\.status = 'normalized'/);
|
|
assert.match(source, /名单内容:\\n\$\{canonicalTsv\}/);
|
|
assert.match(source, /business_route_id NOT IN \([\s\S]*passenger_list_import_independent[\s\S]*passenger_list_import_shared_child/);
|
|
assert.match(source, /SET status = 'superseded', normalized_text_ciphertext = NULL/);
|
|
assert.doesNotMatch(source, /raw_(?:file|workbook)_ciphertext|content_base64[^\n]*task_input_attachments/);
|
|
});
|
|
|
|
test('a newly delivered message revalidates previously rejected attachment bytes', async () => {
|
|
const source = await readFile(new URL('../src/task-service.ts', import.meta.url), 'utf8');
|
|
assert.match(source, /const shouldValidateAttachment = !prior\.rowCount[\s\S]*?=== 'rejected'/u);
|
|
assert.match(source, /previousAttachment && text\(previousAttachment\.status\) !== 'rejected'/u);
|
|
assert.match(source, /SET source = \$2[\s\S]*?status = 'normalized'[\s\S]*?WHERE id = \$1 AND status = 'rejected'/u);
|
|
assert.match(source, /i\.idempotency_key = \$2[\s\S]*?status: 'duplicate' as const/u);
|
|
});
|
|
|
|
test('manual API accepts exactly one bounded inline roster attachment', async () => {
|
|
const source = await readFile(new URL('../src/server.ts', import.meta.url), 'utf8');
|
|
assert.match(source, /attachments: z\.array\(encodedInputAttachmentSchema\)\.max\(1\)/);
|
|
assert.match(source, /content_base64: z\.string\(\)\.min\(4\)\.max\(70_000_000\)/);
|
|
assert.match(source, /decodeInputAttachments\(body\.attachments, config\)/);
|
|
assert.match(source, /if \(task\.status === 'parse_queued'\) void scheduleParseQueue\(\)/);
|
|
});
|