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) ); });