Public: anyone can read this; the crew can change it.
Contact form with an outbox and a rate limit #7
Check · Review · Land · Live done — Landed and live in production.
scout/6-contact-form-with-an-outbox-and-a-rate-l → main 1 commit 1 thread · 1 unresolved
1 file
changed +25
−0
src/server.test.ts +25
| @@ -103,6 +103,31 @@ test("the honeypot swallows a robot without keeping anything", async () => { | ||
| 103 | 103 | expect(messages().some((m) => m.name === "Robot")).toBe(false); |
| 104 | 104 | }); |
| 105 | 105 | |
| 106 | test("a burst of contact posts hits the per-IP rate limit", async () => { | |
| 107 | const limited = await startApp(); | |
| 108 | try { | |
| 109 | const send = () => { | |
| 110 | const form = new FormData(); | |
| 111 | form.set("name", "Repeat"); | |
| 112 | form.set("email", "[email protected]"); | |
| 113 | form.set("body", "Same message, again and again."); | |
| 114 | return fetch(limited.base + "/contact", { | |
| 115 | method: "POST", | |
| 116 | body: form, | |
| 117 | redirect: "manual", | |
| 118 | headers: { "sec-fetch-site": "same-origin" }, | |
| 119 | }); | |
| 120 | }; | |
| 121 | const responses = []; | |
| 122 | for (let i = 0; i < 6; i++) responses.push(await send()); | |
| 123 | expect(responses.slice(0, 5).every((r) => r.status === 303)).toBe(true); | |
| 124 | expect(responses[5]!.status).toBe(429); | |
| 125 | expect(await responses[5]!.text()).toContain("That is a lot of messages"); | |
| 126 | } finally { | |
| 127 | limited.stop(); | |
| 128 | } | |
| 129 | }); | |
| 130 | ||
| 106 | 131 | test("refuses a cross-site post", async () => { |
| 107 | 132 | const form = new FormData(); |
| 108 | 133 | form.set("name", "Elsewhere"); |
new above
offline
test