import assert from 'node:assert/strict'; import test from 'node:test'; import { buildLeaderTaskSummary, formatShanghaiTimestamp, isLeaderTaskSummaryStatus, LEADER_SUMMARY_MAX_MESSAGE_BYTES } from '../src/leadership-task-summary.js'; import { loadConfig } from '../src/config.js'; import { encryptText } from '../src/crypto.js'; import { readLeaderSummaryOriginalText } from '../src/leader-notification-service.js'; const base = { taskId: 'TASK-20260907-001', businessRouteId: 'arrangement_hotel_create', assigneeUsername: 'employee-a', createdAt: '2026-09-07T00:05:06.000Z' }; const originalHotelInput = [ '安排酒店', '团号:LW-260915A-B', '入住日期:2026-09-16', '离店日期:2026-09-18', '酒店搜索:示例酒店 twn', '间数:9', '备注:请保留原话、全角标点及 两个空格。' ].join('\n'); test('notification shows the original multiline input and omits the generated task-number field', () => { const summary = buildLeaderTaskSummary({ ...base, status: 'completed', originalText: originalHotelInput }); assert.equal(summary?.messageText, [ '【员工任务摘要】', '员工:employee-a', '业务:安排酒店', '状态:已完成', '提交:2026-09-07 08:05:06', '', '原始输入:', originalHotelInput, '', '结果:业务处理已完成,结果已记录。' ].join('\n')); assert.doesNotMatch(summary?.messageText || '', /TASK-20260907-001|^任务:/mu); }); test('original input is available even when parsing failed or the business route is unknown', () => { for (const status of ['parse_failed', 'blocked', 'cancelled', 'saved_unverified', 'dry_run']) { const summary = buildLeaderTaskSummary({ ...base, businessRouteId: null, status, originalText: '这段输入未能解析:日期稍后补充。\n第二行仍须展示。' }); assert.match(summary?.messageText || '', /业务:其他任务/); assert.match(summary?.messageText || '', /原始输入:\n这段输入未能解析:日期稍后补充。\n第二行仍须展示。/); assert.doesNotMatch(summary?.messageText || '', /TASK-20260907-001|^任务:/mu); } }); test('result-update messages retain the original input and full result', () => { const summary = buildLeaderTaskSummary({ ...base, status: 'completed', hadNeedsReview: true, originalText: originalHotelInput, successReceipt: { group_number: 'LW-260915A-B' } }); assert.equal(summary?.milestone, 'resolved'); assert.ok(summary?.messageText.startsWith('【员工任务摘要·结果更新】')); assert.ok(summary?.messageText.includes(originalHotelInput)); assert.ok(summary?.messageText.endsWith('结果:业务处理已完成。团号:LW-260915A-B')); }); test('input formatting preserves punctuation and tabs, normalizes newlines and removes nonprinting controls', () => { const summary = buildLeaderTaskSummary({ ...base, status: 'completed', originalText: ' 安排酒店\r\n酒店:A酒店\tTWN\r间数:9\u0000\u0007 ' }); assert.ok(summary?.messageText.includes('原始输入:\n安排酒店\n酒店:A酒店\tTWN\n间数:9\n\n结果:')); assert.doesNotMatch(summary?.messageText || '', /\r|\u0000|\u0007/); }); test('missing input has an explicit fallback without inventing input from a receipt', () => { for (const originalText of [undefined, null, '', ' \n\t ']) { const summary = buildLeaderTaskSummary({ ...base, status: 'failed', originalText, successReceipt: { original_text: '不能作为原始输入的回执内容' } }); assert.match(summary?.messageText || '', /原始输入:\n原始输入暂不可用,请在平台查看。/); assert.doesNotMatch(summary?.messageText || '', /不能作为原始输入的回执内容/); } }); test('UTF-8 budget retains fitting inputs and visibly truncates only overflowing input', () => { const options = { ...base, status: 'completed', originalText: 'x' }; const fixedBytes = Buffer.byteLength(buildLeaderTaskSummary(options)!.messageText, 'utf8') - 1; const available = LEADER_SUMMARY_MAX_MESSAGE_BYTES - fixedBytes; const fittingInput = 'a'.repeat(available); const fitting = buildLeaderTaskSummary({ ...options, originalText: fittingInput })!; assert.equal(Buffer.byteLength(fitting.messageText, 'utf8'), LEADER_SUMMARY_MAX_MESSAGE_BYTES); assert.ok(fitting.messageText.includes(fittingInput)); assert.doesNotMatch(fitting.messageText, /原文过长/); const overflowing = buildLeaderTaskSummary({ ...options, originalText: fittingInput + 'a' })!; assert.ok(Buffer.byteLength(overflowing.messageText, 'utf8') <= LEADER_SUMMARY_MAX_MESSAGE_BYTES); assert.match(overflowing.messageText, /原文过长,剩余内容请在平台查看/); assert.ok(overflowing.messageText.endsWith('结果:业务处理已完成,结果已记录。')); }); test('very long Chinese and emoji input cannot break Unicode or displace the outcome', () => { const summary = buildLeaderTaskSummary({ ...base, assigneeUsername: '员'.repeat(200), status: 'completed', hadNeedsReview: true, originalText: '酒店🏨中文\n'.repeat(20_000), successReceipt: { group_numbers: Array.from({ length: 10 }, (_, index) => `GROUP-${index}-${'A'.repeat(60)}`) } })!; assert.ok(Buffer.byteLength(summary.messageText, 'utf8') <= LEADER_SUMMARY_MAX_MESSAGE_BYTES); assert.equal(Buffer.from(summary.messageText).toString('utf8'), summary.messageText); assert.doesNotMatch(summary.messageText, /\uFFFD/); assert.match(summary.messageText, /原文过长,剩余内容请在平台查看/); assert.ok(summary.messageText.endsWith(`结果:${summary.resultText}`)); }); test('worker reads the same encrypted original input for manual and AgentBus tasks', () => { const config = loadConfig({ NODE_ENV: 'test', FIELD_ENCRYPTION_KEY: Buffer.alloc(32, 17).toString('base64') }); const ciphertext = encryptText(config, originalHotelInput); const originalText = readLeaderSummaryOriginalText(config, ciphertext); assert.equal(originalText, originalHotelInput); const summary = buildLeaderTaskSummary({ ...base, status: 'completed', originalText }); assert.ok(summary?.messageText.includes(originalHotelInput)); assert.ok(!summary?.messageText.includes(ciphertext)); }); test('missing, invalid or unreadable ciphertext does not prevent a notification', () => { const config = loadConfig({ NODE_ENV: 'test', FIELD_ENCRYPTION_KEY: Buffer.alloc(32, 17).toString('base64') }); const otherConfig = loadConfig({ NODE_ENV: 'test', FIELD_ENCRYPTION_KEY: Buffer.alloc(32, 18).toString('base64') }); for (const ciphertext of [null, undefined, '', {}, 'not-encrypted', encryptText(otherConfig, originalHotelInput)]) { const originalText = readLeaderSummaryOriginalText(config, ciphertext); assert.equal(originalText, null); const summary = buildLeaderTaskSummary({ ...base, status: 'completed', originalText }); assert.match(summary?.messageText || '', /原始输入暂不可用/); assert.ok(summary?.messageText.endsWith('结果:业务处理已完成,结果已记录。')); } }); test('leader summary projects only stable outcomes', () => { assert.equal(buildLeaderTaskSummary({ ...base, status: 'running' }), null); assert.equal(buildLeaderTaskSummary({ ...base, status: 'awaiting_confirmation' }), null); assert.equal(isLeaderTaskSummaryStatus('completed'), true); assert.equal(isLeaderTaskSummaryStatus('execution_uncertain'), true); assert.equal(isLeaderTaskSummaryStatus('running'), false); }); test('completed leader summary exposes only whitelisted business identifiers', () => { const result = buildLeaderTaskSummary({ ...base, status: 'completed', successReceipt: { group_numbers: ['LW-260907A-B', '张三', '13800138000'], order_number: 'D12345', customer_name: '绝密客户', passenger_names: ['游客甲', '游客乙'], phone: '13900139000', url: 'https://secret.example/token', technical_error: 'stack trace' } }); assert.ok(result); assert.equal(result.milestone, 'final'); assert.equal(result.deliveryStatus, 'completed'); assert.match(result.messageText, /employee-a/); assert.match(result.messageText, /安排酒店/); assert.match(result.messageText, /LW-260907A-B/); assert.match(result.messageText, /D12345/); assert.match(result.messageText, /2026-09-07 08:05:06/); assert.doesNotMatch(result.messageText, /绝密客户|游客甲|13800138000|13900139000|secret\.example|stack trace|张三/); }); test('failure, uncertainty, cancellation and dry-run use stable safe wording', () => { const failed = buildLeaderTaskSummary({ ...base, status: 'operation_blocked' }); const uncertain = buildLeaderTaskSummary({ ...base, status: 'saved_unverified' }); const cancelled = buildLeaderTaskSummary({ ...base, status: 'cancelled' }); const dryRun = buildLeaderTaskSummary({ ...base, status: 'dry_run' }); assert.equal(failed?.resultText, '本次工作未完成,请在平台查看业务结果。'); assert.equal(uncertain?.milestone, 'needs_review'); assert.match(uncertain?.messageText || '', /需人工核验|请勿重复提交/); assert.equal(cancelled?.deliveryStatus, 'cancelled'); assert.match(dryRun?.resultText || '', /尚未正式提交/); }); test('a stable result after needs-review produces one result-update milestone', () => { const result = buildLeaderTaskSummary({ ...base, status: 'completed', hadNeedsReview: true, successReceipt: { group_number: 'LW-260907A-B' } }); assert.equal(result?.milestone, 'resolved'); assert.match(result?.messageText || '', /员工任务摘要·结果更新/); }); test('Shanghai timestamp formatting is deterministic and invalid-safe', () => { assert.equal(formatShanghaiTimestamp(base.createdAt), '2026-09-07 08:05:06'); assert.equal(formatShanghaiTimestamp('not-a-date'), '时间未记录'); });