import assert from 'node:assert/strict'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { chromium } from 'playwright'; // 使用真实会话撤销和独立 HTTP 配额修改检查草稿恢复,生成模型调用次数为 0。 const credentials = JSON.parse(await readFile(new URL('../../.work/platform-accounts.json', import.meta.url), 'utf8')); const base = (process.env.FRONTEND_URL || 'http://127.0.0.1:18762').replace(/\/$/, ''); 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' }); const page = await browser.newPage(); page.setDefaultTimeout(15000); const errors = []; page.on('pageerror', error => errors.push(error.message)); const content = page.locator('.platform-pages'); const panel = page.locator('.login-panel'); const navigation = page.getByRole('navigation', { name: '系统导航' }); const checks = []; let externalToken; async function login() { await panel.getByLabel('员工账户', { exact: true }).fill(credentials.username); await panel.getByLabel('密码', { exact: true }).fill(credentials.password); const response = page.waitForResponse(item => item.url().endsWith('/platform/auth/sessions') && item.request().method() === 'POST'); await panel.getByRole('button', { name: '登录', exact: true }).click(); const authenticated = await response; assert.equal(authenticated.status(), 200); await panel.waitFor({ state: 'hidden' }); await content.getByRole('status').waitFor({ state: 'hidden' }); return (await authenticated.json()).access_token; } async function open(name, field) { await navigation.getByRole('button', { name, exact: true }).click(); await content.getByLabel(field, { exact: true }).waitFor(); } async function call(method, path, body, token) { const response = await page.context().request.fetch(`${base}/openapi/v1/platform${path}`, { method, data: body, headers: { Authorization: `Bearer ${token}`, 'Idempotency-Key': crypto.randomUUID() }, }); assert.ok(response.ok(), `${method} ${path}: ${response.status()}`); return response.status() === 204 ? null : response.json(); } try { await page.goto(`${base}/#/platform/tenants`); const token = await login(); const name = `平台会话验证 ${Date.now()}`; const tenant = await call('POST', '/tenants', { name, contact: '平台功能检查' }, token); await open('配额管理', '修改原因'); await content.locator('.el-select__wrapper').click(); await page.getByRole('option', { name, exact: true }).click(); await content.getByLabel('交互并发上限', { exact: true }).fill('3'); await content.getByLabel('后台并发上限', { exact: true }).fill('1'); await content.getByLabel('每日 token 上限', { exact: true }).fill('3000'); await content.getByLabel('修改原因', { exact: true }).fill('待本人检查的额度草稿'); await open('商家管理', '商家名称'); await content.getByLabel('商家名称', { exact: true }).fill('尚未提交的商家名称'); await content.getByLabel('接入负责人', { exact: true }).fill('尚未提交的负责人'); await call('DELETE', '/auth/sessions/current', undefined, token); await navigation.getByRole('button', { name: '配额管理', exact: true }).click(); await panel.waitFor(); assert.equal(await navigation.count(), 0); checks.push('服务端撤销平台会话后隐藏管理内容并要求登录'); const externalLogin = await page.context().request.post(`${base}/openapi/v1/platform/auth/sessions`, { data: { username: credentials.username, password: credentials.password }, }); assert.equal(externalLogin.status(), 200); externalToken = (await externalLogin.json()).access_token; const previous = (await call('GET', '/state', undefined, externalToken)).capacity[tenant.id]; const updated = await call('PUT', `/tenants/${tenant.id}/capacity`, { interactive: 2, reserved: 0, background: 0, tokens: 1200, reason: '独立请求更新配置版本', expected_version: previous.version, expected_token_version: previous.tokenVersion, }, externalToken); const writes = []; page.on('request', request => { if (request.method() === 'PUT' && request.url().endsWith(`/tenants/${tenant.id}/capacity`)) writes.push(request.postDataJSON()); }); await login(); await content.getByLabel('修改原因', { exact: true }).waitFor(); assert.equal(await content.getByLabel('交互并发上限', { exact: true }).inputValue(), '3'); assert.equal(await content.getByLabel('每日 token 上限', { exact: true }).inputValue(), '3000'); assert.equal(await content.getByLabel('修改原因', { exact: true }).inputValue(), '待本人检查的额度草稿'); assert.equal(writes.length, 0); checks.push('同身份登录保留额度草稿且没有自动提交'); await open('商家管理', '商家名称'); assert.equal(await content.getByLabel('商家名称', { exact: true }).inputValue(), '尚未提交的商家名称'); assert.equal(await content.getByLabel('接入负责人', { exact: true }).inputValue(), '尚未提交的负责人'); checks.push('同身份登录恢复商家登记草稿'); await open('配额管理', '修改原因'); const saved = page.waitForResponse(response => response.request().method() === 'PUT' && response.url().endsWith(`/tenants/${tenant.id}/capacity`)); await content.getByRole('button', { name: '检查并保存额度', exact: true }).click(); assert.equal((await saved).status(), 200); assert.equal(writes.length, 1); assert.equal(writes[0].expected_version, updated.version); assert.equal(writes[0].expected_token_version, updated.tokenVersion); await content.getByLabel('修改原因', { exact: true }).waitFor(); const persisted = (await call('GET', '/state', undefined, externalToken)).capacity[tenant.id]; assert.equal(persisted.tokens, 3000); assert.equal(persisted.interactive, 3); checks.push('本人再次提交使用重新读取的配置版本并保存真实数据'); await page.getByRole('button', { name: '退出登录', exact: true }).click(); await page.getByRole('dialog').getByRole('button', { name: '确认操作', exact: true }).click(); await panel.waitFor(); await login(); await open('商家管理', '商家名称'); assert.equal(await content.getByLabel('商家名称', { exact: true }).inputValue(), ''); assert.equal(await content.getByLabel('接入负责人', { exact: true }).inputValue(), ''); checks.push('主动退出清理平台未提交草稿'); assert.deepEqual(errors, []); await mkdir(new URL('../../.work/frontend-verification/', import.meta.url), { recursive: true }); 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)); console.log(JSON.stringify({ checks, browserErrors: errors.length, generatedModelCalls: 0 }, null, 2)); } finally { if (externalToken) await call('DELETE', '/auth/sessions/current', undefined, externalToken); await browser.close(); }