mirror of
https://github.com/mgeeky/decode-spam-headers.git
synced 2026-02-22 05:23:31 +01:00
MAESTRO: verify rate limit flow e2e
This commit is contained in:
@@ -32,7 +32,7 @@ This phase performs final integration, accessibility audit, responsive testing,
|
|||||||
- [x] Complete flow works end-to-end: paste headers → configure tests → analyse → view report → export. Notes: replaced Playwright example spec with end-to-end flow test (paste + configure + analyse + report + export), adjusted Playwright webServer ports/CORS for local 3100 runs, ran `npx playwright test e2e/example.spec.ts --project=chromium`.
|
- [x] Complete flow works end-to-end: paste headers → configure tests → analyse → view report → export. Notes: replaced Playwright example spec with end-to-end flow test (paste + configure + analyse + report + export), adjusted Playwright webServer ports/CORS for local 3100 runs, ran `npx playwright test e2e/example.spec.ts --project=chromium`.
|
||||||
- [x] File drop flow works: drop EML → auto-populate → analyse → report. Notes: extract header block from dropped EML before populating input; updated FileDropZone tests and ran `npx vitest run src/__tests__/FileDropZone.test.tsx`.
|
- [x] File drop flow works: drop EML → auto-populate → analyse → report. Notes: extract header block from dropped EML before populating input; updated FileDropZone tests and ran `npx vitest run src/__tests__/FileDropZone.test.tsx`.
|
||||||
- [x] Cache flow works: analyse → reload → see cached results → clear cache. Notes: added Playwright cache flow coverage (reload + clear) in e2e/example.spec.ts and ran `npx playwright test e2e/example.spec.ts --project=chromium`.
|
- [x] Cache flow works: analyse → reload → see cached results → clear cache. Notes: added Playwright cache flow coverage (reload + clear) in e2e/example.spec.ts and ran `npx playwright test e2e/example.spec.ts --project=chromium`.
|
||||||
- [ ] Rate limiting flow works: exceed limit → CAPTCHA modal → solve → retry succeeds
|
- [x] Rate limiting flow works: exceed limit → CAPTCHA modal → solve → retry succeeds. Notes: added Playwright rate limiting flow spec that mocks 429 response + CAPTCHA verify, asserts retry includes bypass token and succeeds; ran `npx playwright test e2e/rate-limiting.spec.ts --project=chromium`.
|
||||||
- [ ] `pytest backend/tests/` passes with ≥80% coverage on new modules
|
- [ ] `pytest backend/tests/` passes with ≥80% coverage on new modules
|
||||||
- [ ] `npx vitest run --coverage` passes with ≥80% coverage on new components
|
- [ ] `npx vitest run --coverage` passes with ≥80% coverage on new components
|
||||||
- [ ] `ruff check backend/` — zero errors
|
- [ ] `ruff check backend/` — zero errors
|
||||||
|
|||||||
104
frontend/e2e/rate-limiting.spec.ts
Normal file
104
frontend/e2e/rate-limiting.spec.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const headersPath = path.resolve(__dirname, "../../backend/tests/fixtures/sample_headers.txt");
|
||||||
|
const rateLimit = 3;
|
||||||
|
const bypassToken = "playwright-bypass-token";
|
||||||
|
const captchaChallenge = {
|
||||||
|
challengeToken: "playwright-challenge-token",
|
||||||
|
imageBase64:
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFgwJ/l0pNqQAAAABJRU5ErkJggg==",
|
||||||
|
};
|
||||||
|
|
||||||
|
test("rate limiting shows captcha and retries successfully", async ({ page }) => {
|
||||||
|
const headers = await fs.readFile(headersPath, "utf8");
|
||||||
|
let analyseCount = 0;
|
||||||
|
|
||||||
|
await page.route("**/api/analyse", async (route) => {
|
||||||
|
analyseCount += 1;
|
||||||
|
if (analyseCount === rateLimit + 1) {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 429,
|
||||||
|
contentType: "application/json",
|
||||||
|
headers: {
|
||||||
|
"access-control-allow-origin": "http://localhost:3100",
|
||||||
|
"access-control-allow-credentials": "true",
|
||||||
|
"retry-after": "60",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
error: "Too many requests",
|
||||||
|
retryAfter: 60,
|
||||||
|
captchaChallenge,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await route.continue();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route("**/api/captcha/verify", async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/json",
|
||||||
|
headers: {
|
||||||
|
"access-control-allow-origin": "http://localhost:3100",
|
||||||
|
"access-control-allow-credentials": "true",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ success: true, bypassToken }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto("http://localhost:3100");
|
||||||
|
|
||||||
|
const headerInput = page.getByRole("textbox", { name: "Header Input" });
|
||||||
|
await headerInput.fill(headers);
|
||||||
|
|
||||||
|
const analyseButton = page.getByRole("button", { name: "Analyse Headers" });
|
||||||
|
|
||||||
|
const runAnalysis = async () => {
|
||||||
|
const responsePromise = page.waitForResponse(
|
||||||
|
(response) => response.url().includes("/api/analyse") && response.status() === 200,
|
||||||
|
);
|
||||||
|
await expect(analyseButton).toBeEnabled({ timeout: 30000 });
|
||||||
|
await analyseButton.click();
|
||||||
|
await responsePromise;
|
||||||
|
await expect(analyseButton).toBeEnabled({ timeout: 30000 });
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt < rateLimit; attempt += 1) {
|
||||||
|
await runAnalysis();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rateLimitResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) => response.url().includes("/api/analyse") && response.status() === 429,
|
||||||
|
),
|
||||||
|
analyseButton.click(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(rateLimitResponse.status()).toBe(429);
|
||||||
|
|
||||||
|
const captchaModal = page.getByTestId("captcha-challenge");
|
||||||
|
await expect(captchaModal).toBeVisible();
|
||||||
|
|
||||||
|
const retryResponsePromise = page.waitForResponse((response) => {
|
||||||
|
if (!response.url().includes("/api/analyse")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (response.status() !== 200) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bypassHeader = response.request().headers()["x-captcha-bypass-token"];
|
||||||
|
return bypassHeader === bypassToken;
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByTestId("captcha-input").fill("12345");
|
||||||
|
await page.getByTestId("captcha-submit").click();
|
||||||
|
|
||||||
|
const retryResponse = await retryResponsePromise;
|
||||||
|
expect(retryResponse.status()).toBe(200);
|
||||||
|
|
||||||
|
await expect(captchaModal).toBeHidden({ timeout: 30000 });
|
||||||
|
await expect(analyseButton).toBeEnabled({ timeout: 30000 });
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user