session-functional.mjs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import assert from 'node:assert/strict';
  2. import { readFile } from 'node:fs/promises';
  3. import { pathToFileURL } from 'node:url';
  4. import { chromium } from 'playwright';
  5. // 通过登录 HTTP 响应取得待撤销会话,测试过程中不访问 Vue 状态,不输出凭据。
  6. export async function checkSessions(page, baseUrl, accounts) {
  7. const platformAccount = JSON.parse(await readFile(new URL('../../.work/platform-accounts.json', import.meta.url), 'utf8'));
  8. const results = [];
  9. async function route(path) {
  10. await page.evaluate(value => { location.hash = `/${value}`; }, path);
  11. await page.waitForURL(new RegExp(`#/?${path}$`));
  12. }
  13. async function login(product, account) {
  14. const panel = page.locator('.login-panel');
  15. await panel.waitFor();
  16. if (product === 'merchant') await panel.getByLabel('商家标识').fill(accounts.tenant_id);
  17. await panel.getByLabel('员工账户').fill(account);
  18. await panel.getByLabel('密码', { exact: true }).fill(product === 'platform' ? platformAccount.password : accounts.password);
  19. const received = product === 'merchant' ? page.waitForResponse(response => response.url().endsWith('/openapi/v1/auth/sessions') && response.request().method() === 'POST') : null;
  20. await panel.getByRole('button', { name: '登录', exact: true }).click();
  21. const token = received ? (await (await received).json()).access_token : null;
  22. await panel.waitFor({ state: 'hidden' });
  23. return token;
  24. }
  25. async function logout() {
  26. await page.locator('.admin-session').getByRole('button', { name: '退出登录', exact: true }).click();
  27. await page.getByRole('dialog').getByRole('button', { name: '确认操作', exact: true }).click();
  28. await page.locator('.login-panel').waitFor();
  29. }
  30. async function revoke(token) {
  31. const response = await page.context().request.delete(`${baseUrl}/openapi/v1/auth/sessions/current`, { headers: { Authorization: `Bearer ${token}` } });
  32. assert.equal(response.status(), 204, '独立客户端撤销真实会话');
  33. }
  34. await page.goto(`${baseUrl}/#/merchant/service/quick-replies`);
  35. await page.waitForURL(/#\/?merchant\/login$/);
  36. assert.equal(await page.locator('.admin-sidebar').count(), 0);
  37. const panel = page.locator('.login-panel');
  38. await panel.getByLabel('商家标识').fill(accounts.tenant_id);
  39. await panel.getByLabel('员工账户').fill('customer01');
  40. await panel.getByLabel('密码', { exact: true }).fill(accounts.password);
  41. await panel.getByRole('button', { name: '登录', exact: true }).click();
  42. await panel.getByRole('alert').waitFor();
  43. assert.match(await panel.getByRole('alert').innerText(), /无权/);
  44. assert.equal(await panel.getByLabel('密码', { exact: true }).inputValue(), '');
  45. results.push('消费者账户无法登录商家后台');
  46. await login('merchant', 'staff01');
  47. await page.waitForURL(/#\/?merchant\/service\/quick-replies$/);
  48. await page.locator('.admin-title').getByRole('heading', { name: '快捷回复' }).waitFor();
  49. results.push('未登录访问保护页面返回员工原授权目标');
  50. await route('merchant/approval/review');
  51. await page.getByRole('heading', { name: '页面不可访问', exact: true }).waitFor();
  52. assert.equal(await page.getByRole('button', { name: '提交审批决定', exact: true }).count(), 0);
  53. results.push('客服通过直接地址访问主管审批被阻止');
  54. await route('platform/login');
  55. await login('platform', 'platform-admin');
  56. await logout();
  57. await route('merchant/service/quick-replies');
  58. await page.locator('.admin-title').getByRole('heading', { name: '快捷回复' }).waitFor();
  59. results.push('退出平台后台后商家后台会话保持');
  60. await route('platform/login');
  61. await login('platform', 'platform-admin');
  62. await route('merchant/service/quick-replies');
  63. await logout();
  64. await route('platform/dashboard');
  65. await page.locator('.admin-title').getByRole('heading', { name: '平台概览' }).waitFor();
  66. results.push('退出商家后台后平台后台会话保持');
  67. await route('merchant/login');
  68. let token = await login('merchant', 'admin01');
  69. await route('merchant/access');
  70. await page.getByLabel('员工账户', { exact: true }).fill('待保存的管理员邀请');
  71. await revoke(token);
  72. await route('merchant/orders');
  73. await page.waitForURL(/#\/?merchant\/login$/);
  74. assert.equal(await page.locator('.admin-sidebar').count(), 0);
  75. token = await login('merchant', 'admin01');
  76. await page.waitForURL(/#\/?merchant\/orders$/);
  77. await route('merchant/access');
  78. assert.equal(await page.getByLabel('员工账户', { exact: true }).inputValue(), '待保存的管理员邀请');
  79. results.push('真实会话撤销触发重新登录,同身份恢复未提交表单');
  80. await revoke(token);
  81. await route('merchant/orders');
  82. await page.waitForURL(/#\/?merchant\/login$/);
  83. await login('merchant', 'finance02');
  84. await page.locator('.admin-title').getByRole('heading', { name: '经营概览' }).waitFor();
  85. assert.equal(await page.locator('.admin-sidebar').getByRole('button', { name: '店铺与员工', exact: true }).count(), 0);
  86. assert.equal(await page.locator('.admin-sidebar').getByRole('button', { name: '知识中心', exact: true }).count(), 0);
  87. assert.equal(await page.locator('.admin-sidebar').getByRole('button', { name: '售后规则', exact: true }).count(), 0);
  88. await route('merchant/access');
  89. await page.getByRole('heading', { name: '页面不可访问', exact: true }).waitFor();
  90. assert.equal(await page.getByLabel('员工账户', { exact: true }).count(), 0);
  91. assert.equal(await page.getByText('待保存的管理员邀请', { exact: true }).count(), 0);
  92. results.push('费用账户隐藏配置菜单,直接访问配置页面被阻止');
  93. return results;
  94. }
  95. if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  96. const accounts = JSON.parse(await readFile(new URL('../../.work/frontend-accounts.json', import.meta.url), 'utf8'));
  97. const browser = await chromium.launch({ headless: true, executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE || 'C:/Users/Administrator/AppData/Local/ms-playwright/chromium-1228/chrome-win64/chrome.exe' });
  98. const page = await browser.newPage();
  99. page.setDefaultTimeout(12000);
  100. try { console.log(JSON.stringify(await checkSessions(page, process.env.FRONTEND_URL || 'http://127.0.0.1:18762', accounts), null, 2)); }
  101. catch (error) { console.error('会话检查页面:', page.url()); console.error(await page.locator('body').innerText()); throw error; }
  102. finally { await browser.close(); }
  103. }