Repository navigation
test: cover damage report worker #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dottenbr
wants to merge
1
commit into
main
Choose a base branch
from
claude/beautiful-carson-qmvf4d
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| // Run with: npm test (node:test, no dependencies) | ||
| import { test } from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
| import worker from '../src/index.js'; | ||
|
|
||
| const ENDPOINT = 'https://www.devilsrock.de/api/schaden-melden'; | ||
|
|
||
| function makeEnv({ limiterSuccess = true, sendError = null } = {}) { | ||
| const sent = []; | ||
| return { | ||
| sent, | ||
| ALLOWED_ORIGINS: 'https://www.devilsrock.de,https://devilsrock.de', | ||
| REPORT_TO: 'to@example.com', | ||
| REPORT_FROM: 'from@example.com', | ||
| REPORT_LIMITER: { limit: async () => ({ success: limiterSuccess }) }, | ||
| EMAIL: { | ||
| send: async message => { | ||
| if (sendError) throw sendError; | ||
| sent.push(message); | ||
| }, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| function validForm(overrides = {}) { | ||
| const form = new FormData(); | ||
| const values = { | ||
| name: 'Max', | ||
| contact: 'max@example.com', | ||
| location: 'Abschnitt 3', | ||
| description: 'Baum liegt quer', | ||
| ...overrides, | ||
| }; | ||
| for (const [key, value] of Object.entries(values)) form.set(key, value); | ||
| return form; | ||
| } | ||
|
|
||
| function post(form, headers = {}) { | ||
| return new Request(ENDPOINT, { | ||
| method: 'POST', | ||
| body: form, | ||
| headers: { Accept: 'application/json', ...headers }, | ||
| }); | ||
| } | ||
|
|
||
| test('unknown path returns 404', async () => { | ||
| const res = await worker.fetch(new Request('https://www.devilsrock.de/other'), makeEnv()); | ||
| assert.equal(res.status, 404); | ||
| }); | ||
|
|
||
| test('non-POST returns 405 with Allow header', async () => { | ||
| const res = await worker.fetch(new Request(ENDPOINT), makeEnv()); | ||
| assert.equal(res.status, 405); | ||
| assert.equal(res.headers.get('Allow'), 'POST'); | ||
| }); | ||
|
|
||
| test('foreign origin is rejected', async () => { | ||
| const env = makeEnv(); | ||
| const res = await worker.fetch(post(validForm(), { Origin: 'https://evil.example' }), env); | ||
| assert.equal(res.status, 403); | ||
| assert.equal(env.sent.length, 0); | ||
| }); | ||
|
|
||
| test('rate limited request returns 429', async () => { | ||
| const env = makeEnv({ limiterSuccess: false }); | ||
| const res = await worker.fetch(post(validForm()), env); | ||
| assert.equal(res.status, 429); | ||
| assert.equal(env.sent.length, 0); | ||
| }); | ||
|
|
||
| test('missing required field returns 400', async () => { | ||
| const env = makeEnv(); | ||
| const res = await worker.fetch(post(validForm({ location: ' ' })), env); | ||
| assert.equal(res.status, 400); | ||
| assert.equal((await res.json()).ok, false); | ||
| assert.equal(env.sent.length, 0); | ||
| }); | ||
|
|
||
| test('honeypot reports success without sending mail', async () => { | ||
| const env = makeEnv(); | ||
| const res = await worker.fetch(post(validForm({ website: 'http://spam' })), env); | ||
| assert.equal(res.status, 200); | ||
| assert.deepEqual(await res.json(), { ok: true }); | ||
| assert.equal(env.sent.length, 0); | ||
| }); | ||
|
|
||
| test('valid report is emailed with reply-to and escaped HTML', async () => { | ||
| const env = makeEnv(); | ||
| const res = await worker.fetch( | ||
| post(validForm({ description: '<script>alert(1)</script>', location: 'A\r\nBcc: x' }), { | ||
| Origin: 'https://www.devilsrock.de', | ||
| }), | ||
| env, | ||
| ); | ||
| assert.equal(res.status, 200); | ||
| assert.equal(env.sent.length, 1); | ||
| const mail = env.sent[0]; | ||
| assert.equal(mail.to, 'to@example.com'); | ||
| assert.equal(mail.replyTo, 'max@example.com'); | ||
| assert.doesNotMatch(mail.subject, /[\r\n]/); | ||
| assert.match(mail.html, /<script>/); | ||
| assert.doesNotMatch(mail.html, /<script>/); | ||
| assert.equal(mail.attachments, undefined); | ||
| }); | ||
|
|
||
| test('contact that is not an email sets no reply-to', async () => { | ||
| const env = makeEnv(); | ||
| await worker.fetch(post(validForm({ contact: '0171 1234567' })), env); | ||
| assert.equal('replyTo' in env.sent[0], false); | ||
| }); | ||
|
|
||
| test('fields are truncated to their limits', async () => { | ||
| const env = makeEnv(); | ||
| await worker.fetch(post(validForm({ name: 'n'.repeat(500) })), env); | ||
| assert.match(env.sent[0].text, new RegExp(`^Name: n{100}\\n`)); | ||
| }); | ||
|
|
||
| test('photos: keeps caps at 3 files, skips non-images and oversize files', async () => { | ||
| const env = makeEnv(); | ||
| const form = validForm(); | ||
| const img = name => new File([new Uint8Array(10)], name, { type: 'image/jpeg' }); | ||
| form.append('photos', img('a b.jpg')); | ||
| form.append('photos', new File(['x'], 'doc.txt', { type: 'text/plain' })); | ||
| form.append('photos', img('c.jpg')); | ||
| form.append('photos', img('d.jpg')); | ||
| form.append('photos', img('e.jpg')); | ||
| const res = await worker.fetch(post(form), env); | ||
| const body = await res.json(); | ||
| // the cap counts every submitted file, including the skipped text file | ||
| assert.equal(body.photos, 2); | ||
| assert.deepEqual(body.skipped, ['doc.txt', 'd.jpg', 'e.jpg']); | ||
| assert.equal(env.sent[0].attachments[0].filename, 'a_b.jpg'); | ||
| assert.match(env.sent[0].text, /Nicht angehängt/); | ||
|
|
||
| const big = validForm(); | ||
| big.append('photos', new File([new Uint8Array(4 * 1024 * 1024)], 'big.jpg', { type: 'image/jpeg' })); | ||
| const bigRes = await worker.fetch(post(big), makeEnv()); | ||
| assert.deepEqual((await bigRes.json()).skipped, ['big.jpg']); | ||
| }); | ||
|
|
||
| test('email failure returns 502', async () => { | ||
| const original = console.error; | ||
| console.error = () => {}; | ||
| try { | ||
| const res = await worker.fetch(post(validForm()), makeEnv({ sendError: new Error('boom') })); | ||
| assert.equal(res.status, 502); | ||
| } finally { | ||
| console.error = original; | ||
| } | ||
| }); | ||
|
|
||
| test('non-JSON clients are redirected back to the page', async () => { | ||
| const env = makeEnv(); | ||
| const ok = await worker.fetch(post(validForm(), { Accept: 'text/html' }), env); | ||
| assert.equal(ok.status, 303); | ||
| assert.equal(ok.headers.get('Location'), 'https://www.devilsrock.de/schaden-melden/#gesendet'); | ||
| const bad = await worker.fetch(post(validForm({ name: '' }), { Accept: 'text/html' }), env); | ||
| assert.equal(bad.headers.get('Location'), 'https://www.devilsrock.de/schaden-melden/#fehler'); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existing
.github/workflows/check.ymlworkflow still runs onlynpm run checkandnode --check worker/src/index.js, so this newly addednpm testcommand is never executed for pushes or pull requests. Consequently, worker behavior regressions—or broken tests themselves—can merge while CI remains green; add annpm teststep to that workflow.Useful? React with 👍 / 👎.