Files
LWLT-AI/archive/handoff/2026-07-12/legacy-erp-handoff/tools/tests/erp_session_guard.test.js
2026-07-13 19:57:46 +08:00

224 lines
7.4 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const guard = require('../erp_session_guard');
test('login page branding alone is not treated as an active ERP session', () => {
const result = guard.classifyErpSession({
bodyText: '用户登录 USERS LOGIN 立即登录 老挝联泰',
url: 'https://ltjt.yunzhi.run/System/Mainlt.asp',
});
assert.equal(result.ok, false);
assert.equal(result.reason, 'erp_login_required');
});
test('real ERP Chinese login-timeout alert is treated as login required', () => {
const result = guard.classifyErpSession({
bodyText: '修改团队 应收团款 下单备注',
dialogMessage: '您还没有登录或登录超时,请重新登录!',
url: 'https://ltjt.yunzhi.run/System/Business/orders_add.asp?ddid=14083',
});
assert.equal(result.ok, false);
assert.equal(result.reason, 'erp_login_required');
});
test('real ERP login page with account password captcha text is treated as login required', () => {
const result = guard.classifyErpSession({
bodyText: '中文 | ภาษาไทย 账 号: 密 码: 验证码: 刷新验证码',
url: 'https://ltjt.yunzhi.run/system/loginlt.html',
});
assert.equal(result.ok, false);
assert.equal(result.reason, 'erp_login_required');
});
test('real ERP Chinese workspace text is treated as an active session', () => {
const result = guard.classifyErpSession({
bodyText: '业务操作 独立团计划表 财务操作 统计中心 合作商管理 退出',
url: 'https://ltjt.yunzhi.run/System/Business/orders.asp',
});
assert.equal(result.ok, true);
assert.equal(result.reason, 'logged_in');
});
test('new dialog cursor ignores stale login prompts before a fresh action', () => {
const dialogState = {
messages: [{ type: 'alert', message: '您还没有登录或登录超时,请重新登录!' }],
};
const cursor = guard.dialogCursor(dialogState);
dialogState.messages.push({ type: 'alert', message: '修改成功/success' });
assert.deepEqual(
guard.dialogMessagesSince(dialogState, cursor),
[{ type: 'alert', message: '修改成功/success' }]
);
assert.doesNotThrow(() => guard.assertNoNewErpLoginRequired(dialogState, cursor, 'test.action'));
});
test('new login-timeout dialog after a fresh action is rejected', () => {
const dialogState = { messages: [] };
const cursor = guard.dialogCursor(dialogState);
dialogState.messages.push({ type: 'alert', message: '您还没有登录或登录超时,请重新登录!' });
assert.throws(
() => guard.assertNoNewErpLoginRequired(dialogState, cursor, 'test.action'),
(error) => error.code === 'erp_login_required' && /test\.action/.test(error.message)
);
});
test('ERP workspace text is treated as an active session despite stale dialog text', () => {
const result = guard.classifyErpSession({
bodyText: '业务操作 独立团计划表 财务操作 退出',
dialogMessage: '您还没有登录或登录超时,请重新登录!',
url: 'https://ltjt.yunzhi.run/System/Business/orders.asp',
});
assert.equal(result.ok, true);
assert.equal(result.reason, 'logged_in');
});
test('ensureErpSessionReady retries once when target page redirects back to login after manual login', async () => {
const texts = [
'用户登录 USERS LOGIN 立即登录',
'业务操作 财务操作 退出',
'用户登录 USERS LOGIN 立即登录',
'业务操作 财务操作 退出',
'业务操作 财务操作 退出',
];
let textIndex = 0;
const calls = { goto: 0 };
const page = {
url: () => 'https://ltjt.yunzhi.run/System/Business/orders.asp',
frames: () => [{
evaluate: async () => texts[Math.min(textIndex++, texts.length - 1)],
}],
goto: async () => {
calls.goto += 1;
},
waitForTimeout: async () => {},
};
const result = await guard.ensureErpSessionReady(page, null, {
source: 'test.retryAfterTargetRedirect',
targetUrl: 'https://ltjt.yunzhi.run/System/Business/orders.asp',
timeoutMs: 1000,
pollMs: 1,
});
assert.equal(result.ok, true);
assert.equal(calls.goto, 2);
});
test('ensureErpSessionReady navigates to the target page when a logged-in main page is detected first', async () => {
const calls = { goto: 0 };
let textIndex = 0;
const texts = [
'业务操作 财务操作 统计中心 退出',
'业务操作 独立团计划表 财务操作 退出',
];
let currentUrl = 'https://ltjt.yunzhi.run/System/Mainlt.asp';
const page = {
url: () => currentUrl,
frames: () => [{
evaluate: async () => texts[Math.min(textIndex++, texts.length - 1)],
}],
goto: async (url) => {
calls.goto += 1;
currentUrl = url;
},
waitForTimeout: async () => {},
};
const result = await guard.ensureErpSessionReady(page, null, {
source: 'test.forceTargetAfterLoggedInMain',
targetUrl: 'https://ltjt.yunzhi.run/System/Business/orders.asp',
timeoutMs: 1000,
pollMs: 1,
});
assert.equal(result.ok, true);
assert.equal(calls.goto, 1);
assert.equal(currentUrl, 'https://ltjt.yunzhi.run/System/Business/orders.asp');
});
test('ensureErpSessionReady waits when the target page is still an unknown shell', async () => {
const texts = [
'loading...',
'业务操作 独立团计划表 财务操作 退出',
];
let textIndex = 0;
const page = {
url: () => 'https://ltjt.yunzhi.run/System/Business/orders.asp',
frames: () => [{
evaluate: async () => texts[Math.min(textIndex++, texts.length - 1)],
}],
goto: async () => {},
waitForTimeout: async () => {},
};
const result = await guard.ensureErpSessionReady(page, null, {
source: 'test.waitForUnknownTargetShell',
targetUrl: 'https://ltjt.yunzhi.run/System/Business/orders.asp',
timeoutMs: 1000,
pollMs: 1,
});
assert.equal(result.ok, true);
assert.ok(textIndex >= 2);
});
test('waitForErpManualLogin detects a logged-in sibling page in the same browser context', async () => {
const loginPage = {
url: () => 'https://ltjt.yunzhi.run/system/loginlt.html',
frames: () => [{
evaluate: async () => '鐢ㄦ埛鐧诲綍 USERS LOGIN 绔嬪嵆鐧诲綍',
}],
waitForTimeout: async () => {},
};
const mainPage = {
url: () => 'https://ltjt.yunzhi.run/System/Mainlt.asp',
frames: () => [{
evaluate: async () => '涓氬姟鎿嶴綔 璐㈠姟鎿嶄綔 缁熻涓績 閫€鍑?',
}],
};
loginPage.context = () => ({
pages: () => [loginPage, mainPage],
});
const result = await guard.waitForErpManualLogin(loginPage, null, {
source: 'test.detectSiblingLogin',
timeoutMs: 20,
pollMs: 1,
});
assert.equal(result.ok, true);
assert.equal(result.reason, 'logged_in');
assert.equal(result.details.detectedPageUrl, 'https://ltjt.yunzhi.run/System/Mainlt.asp');
});
test('waitForErpManualLogin reports browser closure clearly', async () => {
const page = {
url: () => 'https://ltjt.yunzhi.run/system/loginlt.html',
frames: () => [{
evaluate: async () => '鐢ㄦ埛鐧诲綍 USERS LOGIN',
}],
waitForTimeout: async () => {
throw new Error('page.waitForTimeout: Target page, context or browser has been closed');
},
};
await assert.rejects(
() => guard.waitForErpManualLogin(page, null, {
source: 'test.browserClosed',
timeoutMs: 1000,
pollMs: 1,
}),
(error) => error.code === 'erp_browser_closed_during_login'
&& /ERP Chrome was closed/.test(error.message)
&& /test\.browserClosed/.test(error.message)
);
});