93 lines
6.4 KiB
TypeScript
93 lines
6.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { parseProgramInput } from '../src/program-parser.js';
|
|
import { BUSINESS_ROUTES } from '../src/business-routes.js';
|
|
|
|
const receivedAt = '2026-09-11T08:50:12Z';
|
|
const group = '\n团号:LW-TEST-UPDATE';
|
|
const cases = [
|
|
{ type: 'guide', input: `安排导游变更${group}\n导游搜索:测试新导游\n备注:接机`, selection: undefined, changes: { resource: { name: '测试新导游', keyword: '测试新导游' }, remark: '接机' } },
|
|
{ type: 'vehicle', input: `安排用车变更${group}\n用车日期:2026-09-15到2026-09-16\n结算单位搜索:测试新车队\n数量:2`, selection: { start_date: '2026-09-15', end_date: '2026-09-16' }, changes: { supplier: { name: '测试新车队', keyword: '测试新车队' }, quantity: 2 } },
|
|
{ type: 'transport', input: `安排大交通变更${group}\n交通日期:2026-09-15\n结算单位搜索:测试航空\n数量:8`, selection: { date: '2026-09-15' }, changes: { supplier: { name: '测试航空', keyword: '测试航空' }, quantity: 8 } },
|
|
{ type: 'other', input: `安排其他/备案变更${group}\n业务日期:2026-09-15\n结算单位搜索:测试备案单位\n备案号:TEST-2\n备案日期:2026-09-14\n入境口岸:测试口岸`, selection: { date: '2026-09-15', filing_date: '2026-09-14' }, changes: { supplier: { name: '测试备案单位', keyword: '测试备案单位' }, filing: { number: 'TEST-2', entry_port: '测试口岸' } } }
|
|
];
|
|
|
|
test('vehicle type is an optional candidate filter on create and update, never an original date or direct item edit', async () => {
|
|
for (const directive of ['安排用车', '安排用车变更']) {
|
|
const input = `${directive}${group}\n用车日期:2026-09-15到2026-09-16\n结算单位搜索:测试新车队\n数量:2`;
|
|
for (const label of ['用车类型', '车型']) {
|
|
const result = await parseProgramInput({ rawText: `${input}\n${label}:15座 VIP`, receivedAt });
|
|
assert.equal(result.status, 'agent_parse_passed', JSON.stringify(result));
|
|
const value = (result.operation?.data as any).arrangement;
|
|
assert.equal(value.vehicle_type, '15座 VIP');
|
|
assert.equal(value.item, undefined);
|
|
assert.equal(value.changes?.item, undefined);
|
|
assert.equal(value.selection?.vehicle_type, undefined);
|
|
if (directive.endsWith('变更')) assert.deepEqual(value.selection, { start_date: '2026-09-15', end_date: '2026-09-16' });
|
|
}
|
|
const omitted = await parseProgramInput({ rawText: input, receivedAt });
|
|
assert.equal(omitted.status, 'agent_parse_passed');
|
|
assert.equal((omitted.operation?.data as any).arrangement.vehicle_type, undefined);
|
|
const conflict = await parseProgramInput({ rawText: `${input}\n用车类型:15座\n车型:45座`, receivedAt });
|
|
assert.notEqual(conflict.status, 'agent_parse_passed');
|
|
}
|
|
});
|
|
|
|
for (const c of cases) test(`${c.type} update separates original dates from replacements`, async () => {
|
|
const result = await parseProgramInput({ rawText: c.input, receivedAt });
|
|
assert.equal(result.status, 'agent_parse_passed', JSON.stringify(result));
|
|
assert.equal(result.business_route_id, `arrangement_${c.type}_update`);
|
|
const value = (result.operation?.data as any).arrangement;
|
|
assert.deepEqual(value.changes, c.changes);
|
|
assert.deepEqual(value.selection, c.selection);
|
|
assert.equal(value.mode, 'update');
|
|
assert.equal(value.changes.date, undefined);
|
|
assert.equal(value.changes.start_date, undefined);
|
|
assert.equal(value.changes.end_date, undefined);
|
|
assert.equal(value.changes.filing?.date, undefined);
|
|
assert.equal(BUSINESS_ROUTES.find(r => r.routeId === result.business_route_id)?.parserPolicy, 'program_only');
|
|
});
|
|
|
|
test('updates require an original date and new resource; quantity remains optional only for other', async () => {
|
|
for (const c of cases) {
|
|
for (const rawText of [c.input.replace(/(?:导游搜索|结算单位搜索):[^\n]+\n?/u, ''), ...(c.type === 'guide' ? [] : [c.input.replace(/(?:用车日期|交通日期|业务日期):[^\n]+\n?/u, '')])]) {
|
|
const result = await parseProgramInput({ rawText, receivedAt });
|
|
assert.notEqual(result.status, 'agent_parse_passed', rawText);
|
|
assert.equal(result.operation, null);
|
|
}
|
|
}
|
|
for (const alias of ['安排其他变更', '安排备案变更']) {
|
|
const result = await parseProgramInput({ rawText: cases[3].input.replace('安排其他/备案变更', alias), receivedAt });
|
|
assert.equal(result.status, 'agent_parse_passed', JSON.stringify(result));
|
|
assert.equal((result.operation?.data as any).arrangement.changes.quantity, undefined);
|
|
}
|
|
for (const c of cases.slice(1, 3)) {
|
|
const result = await parseProgramInput({ rawText: c.input.replace(/数量:\d+/u, '数量:0'), receivedAt });
|
|
assert.notEqual(result.status, 'agent_parse_passed');
|
|
}
|
|
});
|
|
|
|
test('ticket description is optional on transport create/update and stays separate from linked item and original dates', async () => {
|
|
for (const directive of ['安排大交通', '安排大交通变更']) {
|
|
const input = `${directive}${group}\n交通日期:2026-09-15\n结算单位搜索:测试票务\n数量:2`;
|
|
for (const label of ['车票说明', '车票说明搜索']) {
|
|
const result = await parseProgramInput({ rawText: `${input}\n${label}:D88 二等座`, receivedAt });
|
|
assert.equal(result.status, 'agent_parse_passed', JSON.stringify(result));
|
|
const value = (result.operation?.data as any).arrangement;
|
|
assert.equal(value.ticket_description, 'D88 二等座');
|
|
assert.equal(value.item, undefined);
|
|
assert.equal(value.changes?.item, undefined);
|
|
assert.equal(value.selection?.ticket_description, undefined);
|
|
if (directive.endsWith('变更')) assert.deepEqual(value.selection, { date: '2026-09-15' });
|
|
}
|
|
const omitted = await parseProgramInput({ rawText: input, receivedAt });
|
|
assert.equal(omitted.status, 'agent_parse_passed');
|
|
assert.equal((omitted.operation?.data as any).arrangement.ticket_description, undefined);
|
|
const legacy = await parseProgramInput({ rawText: `${input}\n车票/控位说明:旧兼容内容`, receivedAt });
|
|
assert.equal(legacy.status, 'agent_parse_passed', JSON.stringify(legacy));
|
|
assert.equal((legacy.operation?.data as any).arrangement.ticket_description, undefined);
|
|
const conflict = await parseProgramInput({ rawText: `${input}\n车票说明:D88\n车票说明搜索:D87`, receivedAt });
|
|
assert.notEqual(conflict.status, 'agent_parse_passed');
|
|
}
|
|
});
|