platform-session.mjs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import assert from 'node:assert/strict';
  2. import { mkdir, readFile, writeFile } from 'node:fs/promises';
  3. import { chromium } from 'playwright';
  4. // 使用真实会话撤销和独立 HTTP 配额修改检查草稿恢复,生成模型调用次数为 0。
  5. const credentials = JSON.parse(await readFile(new URL('../../.work/platform-accounts.json', import.meta.url), 'utf8'));
  6. const base = (process.env.FRONTEND_URL || 'http://127.0.0.1:18762').replace(/\/$/, '');
  7. 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' });
  8. const page = await browser.newPage();
  9. page.setDefaultTimeout(15000);
  10. const errors = [];
  11. page.on('pageerror', error => errors.push(error.message));
  12. const content = page.locator('.platform-pages');
  13. const panel = page.locator('.login-panel');
  14. const navigation = page.getByRole('navigation', { name: '系统导航' });
  15. const checks = [];
  16. let externalToken;
  17. async function login() {
  18. await panel.getByLabel('员工账户', { exact: true }).fill(credentials.username);
  19. await panel.getByLabel('密码', { exact: true }).fill(credentials.password);
  20. const response = page.waitForResponse(item => item.url().endsWith('/platform/auth/sessions') && item.request().method() === 'POST');
  21. await panel.getByRole('button', { name: '登录', exact: true }).click();
  22. const authenticated = await response;
  23. assert.equal(authenticated.status(), 200);
  24. await panel.waitFor({ state: 'hidden' });
  25. await content.getByRole('status').waitFor({ state: 'hidden' });
  26. return (await authenticated.json()).access_token;
  27. }
  28. async function open(name, field) {
  29. await navigation.getByRole('button', { name, exact: true }).click();
  30. await content.getByLabel(field, { exact: true }).waitFor();
  31. }
  32. async function call(method, path, body, token) {
  33. const response = await page.context().request.fetch(`${base}/openapi/v1/platform${path}`, {
  34. method, data: body,
  35. headers: { Authorization: `Bearer ${token}`, 'Idempotency-Key': crypto.randomUUID() },
  36. });
  37. assert.ok(response.ok(), `${method} ${path}: ${response.status()}`);
  38. return response.status() === 204 ? null : response.json();
  39. }
  40. try {
  41. await page.goto(`${base}/#/platform/tenants`);
  42. const token = await login();
  43. const name = `平台会话验证 ${Date.now()}`;
  44. const tenant = await call('POST', '/tenants', { name, contact: '平台功能检查' }, token);
  45. await open('配额管理', '修改原因');
  46. await content.locator('.el-select__wrapper').click();
  47. await page.getByRole('option', { name, exact: true }).click();
  48. await content.getByLabel('交互并发上限', { exact: true }).fill('3');
  49. await content.getByLabel('后台并发上限', { exact: true }).fill('1');
  50. await content.getByLabel('每日 token 上限', { exact: true }).fill('3000');
  51. await content.getByLabel('修改原因', { exact: true }).fill('待本人检查的额度草稿');
  52. await open('商家管理', '商家名称');
  53. await content.getByLabel('商家名称', { exact: true }).fill('尚未提交的商家名称');
  54. await content.getByLabel('接入负责人', { exact: true }).fill('尚未提交的负责人');
  55. await call('DELETE', '/auth/sessions/current', undefined, token);
  56. await navigation.getByRole('button', { name: '配额管理', exact: true }).click();
  57. await panel.waitFor();
  58. assert.equal(await navigation.count(), 0);
  59. checks.push('服务端撤销平台会话后隐藏管理内容并要求登录');
  60. const externalLogin = await page.context().request.post(`${base}/openapi/v1/platform/auth/sessions`, {
  61. data: { username: credentials.username, password: credentials.password },
  62. });
  63. assert.equal(externalLogin.status(), 200);
  64. externalToken = (await externalLogin.json()).access_token;
  65. const previous = (await call('GET', '/state', undefined, externalToken)).capacity[tenant.id];
  66. const updated = await call('PUT', `/tenants/${tenant.id}/capacity`, {
  67. interactive: 2, reserved: 0, background: 0, tokens: 1200, reason: '独立请求更新配置版本',
  68. expected_version: previous.version, expected_token_version: previous.tokenVersion,
  69. }, externalToken);
  70. const writes = [];
  71. page.on('request', request => {
  72. if (request.method() === 'PUT' && request.url().endsWith(`/tenants/${tenant.id}/capacity`)) writes.push(request.postDataJSON());
  73. });
  74. await login();
  75. await content.getByLabel('修改原因', { exact: true }).waitFor();
  76. assert.equal(await content.getByLabel('交互并发上限', { exact: true }).inputValue(), '3');
  77. assert.equal(await content.getByLabel('每日 token 上限', { exact: true }).inputValue(), '3000');
  78. assert.equal(await content.getByLabel('修改原因', { exact: true }).inputValue(), '待本人检查的额度草稿');
  79. assert.equal(writes.length, 0);
  80. checks.push('同身份登录保留额度草稿且没有自动提交');
  81. await open('商家管理', '商家名称');
  82. assert.equal(await content.getByLabel('商家名称', { exact: true }).inputValue(), '尚未提交的商家名称');
  83. assert.equal(await content.getByLabel('接入负责人', { exact: true }).inputValue(), '尚未提交的负责人');
  84. checks.push('同身份登录恢复商家登记草稿');
  85. await open('配额管理', '修改原因');
  86. const saved = page.waitForResponse(response => response.request().method() === 'PUT' && response.url().endsWith(`/tenants/${tenant.id}/capacity`));
  87. await content.getByRole('button', { name: '检查并保存额度', exact: true }).click();
  88. assert.equal((await saved).status(), 200);
  89. assert.equal(writes.length, 1);
  90. assert.equal(writes[0].expected_version, updated.version);
  91. assert.equal(writes[0].expected_token_version, updated.tokenVersion);
  92. await content.getByLabel('修改原因', { exact: true }).waitFor();
  93. const persisted = (await call('GET', '/state', undefined, externalToken)).capacity[tenant.id];
  94. assert.equal(persisted.tokens, 3000);
  95. assert.equal(persisted.interactive, 3);
  96. checks.push('本人再次提交使用重新读取的配置版本并保存真实数据');
  97. await page.getByRole('button', { name: '退出登录', exact: true }).click();
  98. await page.getByRole('dialog').getByRole('button', { name: '确认操作', exact: true }).click();
  99. await panel.waitFor();
  100. await login();
  101. await open('商家管理', '商家名称');
  102. assert.equal(await content.getByLabel('商家名称', { exact: true }).inputValue(), '');
  103. assert.equal(await content.getByLabel('接入负责人', { exact: true }).inputValue(), '');
  104. checks.push('主动退出清理平台未提交草稿');
  105. assert.deepEqual(errors, []);
  106. await mkdir(new URL('../../.work/frontend-verification/', import.meta.url), { recursive: true });
  107. await writeFile(new URL('../../.work/frontend-verification/platform-session.json', import.meta.url), JSON.stringify({ date: new Date().toISOString(), checks, browserErrors: errors, generatedModelCalls: 0 }, null, 2));
  108. console.log(JSON.stringify({ checks, browserErrors: errors.length, generatedModelCalls: 0 }, null, 2));
  109. } finally {
  110. if (externalToken) await call('DELETE', '/auth/sessions/current', undefined, externalToken);
  111. await browser.close();
  112. }