变更问题修复

This commit is contained in:
andy committed 2026-09-14 16:34:36 +08:00
1 parent 6de4422733
commit 9625110f44
62 files changed
+18116 -95

No files matched your search

+76 -2
View File
@@ -6,6 +6,7 @@ import { createRequire } from 'node:module';
import { validateStandardOperation, validateProgramOperation } from '../LianSyn-platform/external-agent-client.mjs';
import Ajv from 'ajv/dist/2020.js';
import addFormats from 'ajv-formats';
import { loadPageActionRealm } from './page-action-test-realm.mjs';
const plans = createRequire(import.meta.url)('../chrome-extension/ltjt-order-assistant/operation-plans.js');
const ajv = new Ajv({ strict: false }); addFormats(ajv);
const parsedSchema = ajv.compile(JSON.parse(readFileSync(new URL('../schemas/agent_operation.schema.json', import.meta.url))));
@@ -18,7 +19,7 @@ const adapter = readFileSync(new URL('../chrome-extension/ltjt-order-assistant/l
assistant.testOperationBlockers = operationBlockers; assistant.testExpected = arrangementExpectedFields;
assistant.testResource = arrangementResourceProof; assistant.testNative = applyNativeSettlementSelection;
assistant.preflightLifecycleOperation = preflightLifecycleOperation;`);
function fixture(type, { index = type === 'guide' ? 0 : 1, duplicate = false, zeroQuantity = false, onSelection = () => {}, rows } = {}) {
function fixture(type, { index = type === 'guide' ? 0 : 1, duplicate = false, zeroQuantity = false, onSelection = () => {}, rows, omitDateMatcher = false } = {}) {
let now = 10000, selections = 0;
const fields = new Map(), form = { elements: [], id: 'InfoForm1', isConnected: true };
const document = { querySelector: () => form, querySelectorAll: () => [], readyState: 'complete' };
@@ -56,7 +57,7 @@ function fixture(type, { index = type === 'guide' ? 0 : 1, duplicate = false, ze
const sandbox = { window, document, Event: window.Event, URL, URLSearchParams, TextEncoder,
Date: class extends Date { static now() { return now; } }, setTimeout(cb,ms) { now += ms; cb(); } };
window.setTimeout = sandbox.setTimeout;
vm.runInNewContext(inpage,sandbox); vm.runInNewContext(adapter,sandbox);
loadPageActionRealm(window, sandbox, { 'inpage.js': inpage, 'lifecycle-adapters.js': adapter }, omitDateMatcher ? ['operation-plans.js'] : []);
const changes = { [type === 'guide' ? 'resource' : 'supplier']: { name: '测试新资源', keyword: '测试新资源' }, ...(['vehicle','transport'].includes(type) ? { quantity: 2 } : {}) };
const selection = type === 'guide' ? undefined : type === 'vehicle' ? { start_date: '2026-09-15', end_date: '2026-09-16' } : { date: '2026-09-15' };
const input = { action: `arrangement_${type}`, order_nature: 'formal', submit_mode: 'dry_run', data: { existing_refs: { identifier: 'LW-TEST-UPDATE', kind: 'shared_plan' }, arrangement: { mode: 'update', ...(selection ? { selection } : {}), changes } } };
@@ -66,6 +67,20 @@ function fixture(type, { index = type === 'guide' ? 0 : 1, duplicate = false, ze
return { api, form, fields, input, selections: () => selections };
}
const types = ['guide','vehicle','transport','other'];
test('dated updates report missing MAIN-world dependency without changing native fields; guide stays independent', () => {
for (const type of types) {
const f = fixture(type, { omitDateMatcher: true });
const before = [...f.fields].map(([key, control]) => [key, control.value]);
const result = f.api.testResolve(f.form, f.input);
if (type === 'guide') assert.equal(result.ok, true, JSON.stringify(result));
else {
assert.equal(result.ok, false);
assert.deepEqual(Array.from(result.blockers), ['arrangement_date_matcher_unavailable']);
}
assert.equal(f.selections(), 0);
assert.deepEqual([...f.fields].map(([key, control]) => [key, control.value]), before);
}
});
for (const type of types) test(`${type} update resolves the original record and preserves dates and unspecified fields`, async () => {
for (const index of type === 'guide' ? [0] : [0,1,10]) {
const f = fixture(type,{index});
@@ -395,3 +410,62 @@ test('native transport selection rechecks a filter even after preflight and neve
assert.deepEqual([...f.fields.values()].map(c => c.value), before);
}
});
for (const type of ['vehicle','transport','other']) test(`${type} month/day dates pass parsed contracts and resolve to a full-year execution snapshot`, () => {
const f = fixture(type);
f.input.data.arrangement.selection = type === 'vehicle' ? { start_date:'09-15',end_date:'09-16' } : {date:'09-15', ...(type === 'other' ? {filing_date:'09-14'} : {})};
assert.equal(parsedSchema(f.input), true, JSON.stringify(parsedSchema.errors));
assert.deepEqual(validateProgramOperation(f.input), []);
assert.equal(plans.validateDispatchOperation(f.input).blockers.length, 0);
const resolved = f.api.testResolve(f.form, f.input);
assert.equal(resolved.ok, true, JSON.stringify(resolved));
assert.equal(executionSchema(resolved.operation), true, JSON.stringify(executionSchema.errors));
assert.equal(resolved.operation.data.arrangement[type === 'vehicle' ? 'start_date' : 'date'], '2026-09-15');
assert.equal(resolved.operation.data.arrangement.selection, undefined);
f.fields.get('riqi0').value = '2027-9-15'; f.fields.get('riqis0').value = '2027-9-16';
assert.equal(f.api.testResolve(f.form, f.input).ok, false, 'two years are ambiguous');
f.input.data.arrangement.selection[type === 'vehicle' ? 'start_date' : 'date'] = '2026-09-15';
assert.equal(f.api.testResolve(f.form, f.input).ok, true);
if (type === 'other') {
f.input.data.arrangement.selection.filing_date = '2027-09-14';
assert.equal(f.api.testResolve(f.form, f.input).ok, false, 'explicit filing year must match');
}
});
test('parsed date-selector validators agree on invalid calendars while execution still requires a full year', () => {
for (const invalid of ['02-30','13-01','2026-02-29','9-15','bad']) {
const f = fixture('transport'); f.input.data.arrangement.selection.date = invalid;
assert.equal(parsedSchema(f.input), false, invalid);
assert.ok(validateProgramOperation(f.input).length, invalid);
assert.ok(plans.validateDispatchOperation(f.input).blockers.length, invalid);
}
assert.equal(plans.matchesOriginalArrangementDate('2028-02-29', '02-29'), true);
assert.equal(plans.matchesOriginalArrangementDate('2027-02-29', '02-29'), false);
const f = fixture('transport'); const resolved = f.api.testResolve(f.form, f.input).operation;
resolved.data.arrangement.date = '09-15';
assert.equal(executionSchema(resolved), false);
assert.ok(validateStandardOperation(resolved).length);
});
test('ambiguous original dates are surfaced in the route failure message without native ids', () => {
const source = readFileSync(new URL('../chrome-extension/ltjt-order-assistant/background.js', import.meta.url), 'utf8');
const start = source.indexOf('function lifecycleRouteFailure(');
const end = source.indexOf('\nasync function executeLifecycleOperation', start);
const failure = vm.runInNewContext(`(${source.slice(start,end).trim()})`);
const unavailable = failure({action:'arrangement_hotel'}, {blockers:['arrangement_date_matcher_unavailable']});
assert.equal(unavailable.errorCode, 'arrangement_date_matcher_unavailable');
assert.match(unavailable.message, /插件日期匹配组件未加载/);
assert.doesNotMatch(unavailable.message, /没有找到|核对原安排/);
const absent = failure({action:'arrangement_hotel'}, {blockers:['arrangement_update_target_candidate_count:0']});
assert.equal(absent.errorCode, 'arrangement_update_target_not_unique');
assert.match(absent.message, /没有找到/);
const result = failure({action:'arrangement_hotel'}, {blockers:['arrangement_update_target_candidate_count:2'], preflight:{resource_resolution:{candidates:[
{date:'2026-09-04',end_date:'2026-09-05',resource:'原酒店',item:'TWN'},
{date:'2027-09-04',end_date:'2027-09-05',resource:'原酒店',item:'TWN'}
]}}});
assert.match(result.message,/2026-09-04 至 2026-09-05/);
assert.match(result.message,/2027-09-04 至 2027-09-05/);
assert.match(result.message,/完整年月日重新提交/);
assert.match(result.message,/未写入 ERP/);
assert.match(inpage,/resource_resolution: arrangementResolution.evidence/);
});