login-rate-limit.mjs 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940
  1. import assert from 'node:assert/strict';
  2. import { readFile, writeFile, mkdir } from 'node:fs/promises';
  3. import { chromium } from 'playwright';
  4. const accounts = JSON.parse(await readFile(new URL('../../.work/frontend-accounts.json', import.meta.url), 'utf8'));
  5. const base = process.env.FRONTEND_URL || 'http://127.0.0.1:18763';
  6. const browser = await chromium.launch({ headless: true, executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE });
  7. const page = await browser.newPage();
  8. const errors = [];
  9. page.on('pageerror', error => errors.push(error.message));
  10. try {
  11. await page.goto(`${base}/#/merchant/login`);
  12. const panel = page.locator('.login-panel');
  13. const username = `rate-check-${crypto.randomUUID()}`;
  14. // 使用独立的不存在账户触发真实 Redis 登录限流,总计限制为 11 次请求。
  15. for (let attempt = 0; attempt < 10; attempt++) {
  16. const response = await page.request.post(`${base}/openapi/v1/auth/sessions`, {
  17. data: { tenant_id: accounts.tenant_id, username, password: 'invalid-credentials' },
  18. });
  19. assert.equal(response.status(), 401, '未授权账户返回统一登录失败');
  20. }
  21. await panel.getByLabel('商家标识').fill(accounts.tenant_id);
  22. await panel.getByLabel('员工账户').fill(username);
  23. await panel.getByLabel('密码', { exact: true }).fill('invalid-credentials');
  24. const received = page.waitForResponse(response => response.request().method() === 'POST' && response.url().endsWith('/auth/sessions'));
  25. await panel.getByRole('button', { name: '登录', exact: true }).click();
  26. const response = await received;
  27. assert.equal(response.status(), 429);
  28. assert.equal(response.headers()['retry-after'], '60');
  29. await panel.getByRole('alert').filter({ hasText: '请等待 60 秒后重试' }).waitFor();
  30. assert.equal(await panel.getByLabel('密码', { exact: true }).inputValue(), '');
  31. assert.equal(await panel.getByLabel('员工账户').inputValue(), username);
  32. assert.equal(await page.locator('.admin-shell').count(), 0);
  33. assert.deepEqual(errors, []);
  34. await mkdir(new URL('../../.work/frontend-verification/', import.meta.url), { recursive: true });
  35. await writeFile(new URL('../../.work/frontend-verification/login-rate-limit.json', import.meta.url), JSON.stringify({ date: new Date().toISOString(), requests: 11, checks: ['真实账户限流', '服务端等待时间展示', '失败后清空密码并保留账户', '受保护内容隐藏'], browserErrors: errors, generatedModelCalls: 0 }, null, 2));
  36. console.log('登录限流真实接口与页面检查通过,共 11 次请求。');
  37. } finally {
  38. await browser.close();
  39. }