import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; import vm from 'node:vm'; async function read(relativePath) { return readFile(new URL(`../${relativePath}`, import.meta.url), 'utf8'); } function loadPopupBusinessUrlMatcher(popupSource) { const context = { URL, chrome: {}, document: { addEventListener() {}, querySelector() { return null; } }, window: { setTimeout() {} } }; vm.runInNewContext(popupSource, context, { filename: 'popup.js' }); return context.isBusinessSystemUrl; } function loadKeepaliveStateNormalizer(backgroundSource) { const start = backgroundSource.indexOf('function normalizeErpKeepaliveState'); const end = backgroundSource.indexOf('async function readErpKeepaliveState', start); assert.ok(start >= 0 && end > start, 'background must expose the keepalive state normalizer'); const context = {}; vm.runInNewContext( `${backgroundSource.slice(start, end)}\nthis.normalizeErpKeepaliveState = normalizeErpKeepaliveState;`, context, { filename: 'background-keepalive-state.js' } ); return context.normalizeErpKeepaliveState; } test('production control-plane origin is recognized and bridge injection is permitted', async () => { const [manifestText, background, popup] = await Promise.all([ read('chrome-extension/ltjt-order-assistant/manifest.json'), read('chrome-extension/ltjt-order-assistant/background.js'), read('chrome-extension/ltjt-order-assistant/popup.js') ]); const manifest = JSON.parse(manifestText); const isBusinessSystemUrl = loadPopupBusinessUrlMatcher(popup); assert.equal(isBusinessSystemUrl('https://lwlt.nianxx.cn/'), true); assert.equal(isBusinessSystemUrl('https://lwlt.nianxx.cn/history?status=running'), true); assert.equal(isBusinessSystemUrl('https://lwlt.nianxx.cn.example.com/'), false); assert.ok(manifest.host_permissions.includes('https://lwlt.nianxx.cn/*')); assert.ok(manifest.content_scripts.some(({ matches }) => matches.includes('https://lwlt.nianxx.cn/*'))); assert.match(background, /PRODUCTION_PLATFORM_ORIGIN = 'https:\/\/lwlt\.nianxx\.cn'/); assert.match(background, /parsed\.origin === PRODUCTION_PLATFORM_ORIGIN/); assert.match(background, /'https:\/\/lwlt\.nianxx\.cn\/\*'/); }); test('ERP session keepalive is scheduled through alarms with a conservative default', async () => { const [manifestText, background, mappingText] = await Promise.all([ read('chrome-extension/ltjt-order-assistant/manifest.json'), read('chrome-extension/ltjt-order-assistant/background.js'), read('mappings/lifecycle.mapping.json') ]); const manifest = JSON.parse(manifestText); const mapping = JSON.parse(mappingText); assert.ok(manifest.permissions.includes('alarms')); assert.match(background, /ERP_KEEPALIVE_ALARM_NAME = 'ltjt-erp-session-keepalive'/); assert.match(background, /ERP_KEEPALIVE_PERIOD_MINUTES = 5/); assert.match(background, /delayInMinutes: ERP_KEEPALIVE_INITIAL_DELAY_MINUTES/); assert.match(background, /chrome\.alarms\.onAlarm\.addListener/); assert.match(background, /ERP_LINK_RECOVERY_COOLDOWN_MS = 2 \* 60_000/); assert.equal(mapping.erp_session_keepalive.enabled_by_default, true); assert.equal(mapping.erp_session_keepalive.period_minutes, 5); assert.equal(mapping.erp_session_keepalive.initial_delay_minutes, 1); assert.equal(mapping.erp_session_keepalive.exception_recovery.enabled_by_default, true); assert.equal(mapping.erp_session_keepalive.exception_recovery.cooldown_minutes, 2); }); test('ERP keepalive is an existing-tab read-only request and never a page reload or task write', async () => { const background = await read('chrome-extension/ltjt-order-assistant/background.js'); const probeBody = background.slice( background.indexOf('async function executeErpKeepaliveProbe'), background.indexOf('async function performErpLinkRecovery') ); const keepaliveBody = background.slice( background.indexOf('async function runErpSessionKeepalive'), background.indexOf('async function ensureErpSessionKeepaliveAlarm') ); assert.match(keepaliveBody, /findExistingErpTabForKeepalive/); assert.match(keepaliveBody, /active_erp_execution/); assert.match(keepaliveBody, /executeErpKeepaliveProbe/); assert.match(keepaliveBody, /requestErpLinkRecovery/); assert.match(probeBody, /fetch\(keepaliveUrl, \{/); assert.match(probeBody, /method: 'GET'/); assert.match(probeBody, /credentials: 'include'/); assert.match(probeBody, /cache: 'no-store'/); assert.match(probeBody, /response\.text\(\)/); assert.doesNotMatch(keepaliveBody, /chrome\.tabs\.create/); assert.doesNotMatch(keepaliveBody, /chrome\.tabs\.reload/); assert.doesNotMatch(keepaliveBody, /SubmitInfoForm|captureAjaxSubmit|DoInfo/); }); test('ERP exception recovery refreshes the existing ERP tab once, then performs a read-only probe', async () => { const background = await read('chrome-extension/ltjt-order-assistant/background.js'); const recoveryBody = background.slice( background.indexOf('async function performErpLinkRecovery'), background.indexOf('function requestErpLinkRecovery') ); assert.match(recoveryBody, /findExistingErpTabForKeepalive\(\{ allowLoading: true \}\)/); assert.match(recoveryBody, /hasActiveErpExecution/); assert.match(recoveryBody, /chrome\.tabs\.reload\(tab\.id, \{ bypassCache: true \}\)/); assert.match(recoveryBody, /waitForTabLoad\(tab\.id, ERP_LINK_RECOVERY_TIMEOUT_MS\)/); assert.match(recoveryBody, /executeErpKeepaliveProbe\(tab\.id\)/); assert.match(recoveryBody, /recovery_count: state\.recovery_count \+ 1/); assert.doesNotMatch(recoveryBody, /chrome\.tabs\.create/); assert.doesNotMatch(recoveryBody, /captureAjaxSubmit|SubmitInfoForm|DoInfo/); }); test('an interrupted service worker recovery cannot leave ERP dispatch permanently blocked', async () => { const background = await read('chrome-extension/ltjt-order-assistant/background.js'); const normalizeErpKeepaliveState = loadKeepaliveStateNormalizer(background); const persistedRunning = { last_status: 'running', recovery_status: 'running', recovery_attempt_at: '2026-08-29T12:00:00.000Z', recovery_completed_at: '', recovery_error: '' }; const active = normalizeErpKeepaliveState(persistedRunning, true); assert.equal(active.last_status, 'running'); assert.equal(active.recovery_status, 'running'); const interrupted = normalizeErpKeepaliveState(persistedRunning, false); assert.equal(interrupted.last_status, 'interrupted'); assert.equal(interrupted.recovery_status, 'interrupted'); assert.equal(interrupted.recovery_error, 'erp_link_recovery_interrupted'); const keepaliveBody = background.slice( background.indexOf('async function runErpSessionKeepalive'), background.indexOf('async function ensureErpSessionKeepaliveAlarm') ); assert.match(keepaliveBody, /success && !erpLinkRecoveryInFlight/); assert.match(keepaliveBody, /recovery_status: 'idle'/); }); test('keepalive setting and last probe state are visible without changing the business operation contract', async () => { const [background, bridge, popup, popupHtml] = await Promise.all([ read('chrome-extension/ltjt-order-assistant/background.js'), read('chrome-extension/ltjt-order-assistant/business-bridge.js'), read('chrome-extension/ltjt-order-assistant/popup.js'), read('chrome-extension/ltjt-order-assistant/popup.html') ]); assert.match(background, /erp_keepalive: keepalive/); assert.match(background, /last_status: status/); assert.match(background, /erp_link_recovery/); assert.match(background, /recovery_status/); assert.match(bridge, /erpKeepaliveEnabled/); assert.match(bridge, /erp_keepalive_enabled/); assert.match(popupHtml, /id="erpKeepaliveToggle"/); assert.match(popup, /readKeepaliveEnabled/); assert.match(popup, /keepaliveStatusText/); assert.match(popup, /正在刷新现有 ERP 页面并进行只读复测/); assert.match(popup, /erpKeepaliveEnabled: event\.currentTarget\.checked/); });