// Execute the real upload handler against synthetic responses and a minimal DOM // adapter; no browser, server, credentials or business uploads are used. const {test} = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const root = path.resolve(__dirname, '../..'); const app = fs.readFileSync(path.join(root, 'arr_web/static/app.js'), 'utf8'); const i18n = fs.readFileSync(path.join(root, 'arr_web/static/i18n.js'), 'utf8'); const handler = app.slice(app.indexOf('async function handleUpload()'), app.indexOf('async function loadMonthsAndAnalytics()')); const catalog = Object.fromEntries([...i18n.matchAll(/^\s*"([^"]+)": (\[.*\]),?$/gm)].map(match => [match[1], JSON.parse(match[2])])); async function upload(receipt, locale = 0) { const nodes = new Map(); const notices = []; const progress = []; const source = Buffer.from('synthetic'); const noop = () => {}; const context = { state: {health: {processing_ready: true}, jobs: [], selectedFile: {name: 'ARR.XML', arrayBuffer: async () => source}}, $: selector => { if (!nodes.has(selector)) nodes.set(selector, {classList: {add: noop, toggle: noop}, setAttribute: noop, removeAttribute: noop}); return nodes.get(selector); }, I18N: {t: (key, values = {}) => catalog[key]?.[locale]?.replace(/\{(\w+)\}/g, (_, name) => values[name])}, api: async (url, options) => { assert.equal(url, '/api/jobs'); assert.deepEqual(options.body, source); return receipt; }, encodedFilename: value => value, clearTracePoll: noop, renderJobs: noop, renderTracePlaceholder: noop, setTraceLiveState: noop, startUploadProgress: noop, renderHistoryMonthControl: noop, validMonth: () => true, localMonth: () => '2026-07', loadJobs: async () => {}, selectJob: async () => {}, openDailyPriceReview: async () => {}, finishUploadProgress: (...args) => progress.push(args), showToast: (...args) => notices.push(args), }; vm.createContext(context); vm.runInContext(handler, context); await context.handleUpload(); assert.equal(context.state.uploadInFlight, false); assert.equal(nodes.get('#xml-file').disabled, false); assert.equal(notices.length, 1); return {notice: notices[0], progress}; } test('successful uploads show the verified emoji count in all three languages', async () => { const expected = ['已忽略 4 处表情符号', 'Ignored 4 emoji sequences.', 'ละเว้นอีโมจิ 4 รายการแล้ว']; for (let locale = 0; locale < 3; locale++) { const {notice, progress} = await upload({status: 'succeeded', ignored_emoji_count: 4}, locale); assert.equal(notice[1], false); assert(notice[0].includes(expected[locale])); assert.equal(progress[0][0], true); } }); test('review and failure keep their original status while disclosing cleanup', async () => { for (const status of ['needs_review', 'failed']) { const {notice, progress} = await upload({status, ignored_emoji_count: 1}); assert(notice[0].includes('已忽略 1 处表情符号')); assert(notice[0].includes(status === 'failed' ? '处理失败' : '价格复核')); assert.equal(notice[1], status === 'failed'); assert.equal(progress[0][0], status !== 'failed'); } }); test('no cleanup notice is invented for old, empty or invalid metadata', async () => { for (const count of [undefined, 0, -1, true, '4', 1.5]) { const {notice} = await upload({status: 'succeeded', ignored_emoji_count: count}); assert.equal(notice[0], catalog['upload.success'][0]); assert.equal(notice[1], false); } });