168 lines
7.9 KiB
JavaScript
168 lines
7.9 KiB
JavaScript
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/);
|
|
});
|