test(ops): verify Docker persistence, browser workflows and document deployment

This commit is contained in:
OpenCode committed 2026-10-24 15:43:37 +02:00
1 parent a58516e424
commit 8fa63415f1
35 files changed
+1105

No files matched your search

+146
View File
@@ -0,0 +1,146 @@
import { test, expect } from '@playwright/test';
import { pasta } from '../fixtures';
import { exportRecipes } from '../../src/lib/import';
const headers = { origin: 'http://localhost:3100' };
test('authorization, CSRF, optimistic concurrency, validation and transactional imports', async ({
request,
}) => {
expect((await request.post('/api/recipes', { headers, data: pasta })).status()).toBe(401);
expect(
(
await request.post('/api/auth/login', {
headers: { origin: 'https://evil.invalid' },
data: { username: 'testadmin', password: process.env.TEST_PASSWORD },
})
).status(),
).toBe(403);
const login = await request.post('/api/auth/login', {
headers,
data: { username: 'testadmin', password: process.env.TEST_PASSWORD },
});
expect(login.ok()).toBe(true);
expect(login.headers()['set-cookie']).toMatch(/HttpOnly/i);
expect(login.headers()['set-cookie']).toMatch(/SameSite=strict/i);
const created = await request.post('/api/recipes', {
headers,
data: { ...pasta, id: undefined, title: 'API Rezept', published: false },
});
expect(created.status()).toBe(201);
const r = await created.json();
expect(
(
await request.put(`/api/recipes/${r.id}`, {
headers,
data: { ...r, title: 'API Rezept geändert' },
})
).status(),
).toBe(200);
expect(
(
await request.put(`/api/recipes/${r.id}`, {
headers,
data: { ...r, title: 'Veraltete Änderung' },
})
).status(),
).toBe(409);
expect(
(await request.put(`/api/recipes/${r.id}`, { headers, data: { ...r, servings: 0 } })).status(),
).toBe(422);
expect(
(
await request.post('/api/import', {
headers,
data: {
format: 'json',
preview: false,
raw: exportRecipes(
[{ ...pasta, id: undefined, title: 'Transaktion darf nicht bleiben' }, pasta],
'json',
),
},
})
).status(),
).toBe(409);
expect(
(await (await request.get('/api/recipes?admin=1')).json()).some(
(r: { title: string }) => r.title === 'Transaktion darf nicht bleiben',
),
).toBe(false);
expect(
(
await request.post('/api/import', {
headers,
data: {
format: 'xml',
preview: true,
raw: '<!DOCTYPE x [<!ENTITY e SYSTEM "file:///etc/passwd">]><x/>',
},
})
).status(),
).toBe(422);
expect(
(
await request.post('/api/media', {
headers: { ...headers, 'content-type': 'image/svg+xml' },
data: '<svg />',
})
).status(),
).toBe(415);
expect(
(
await request.post('/api/media', {
headers: { ...headers, 'content-type': 'image/png' },
data: 'not a png',
})
).status(),
).toBe(422);
await request.post('/api/auth/logout', { headers });
expect((await request.get(`/api/recipes/${r.id}`)).status()).toBe(404);
expect((await request.get('/api/export')).status()).toBe(401);
expect((await request.delete(`/api/recipes/${r.id}`, { headers })).status()).toBe(401);
});
test('password change revokes other sessions and brute force is rate limited', async ({
request,
playwright,
}) => {
const other = await playwright.request.newContext({ baseURL: 'http://localhost:3100' });
const credentials = { username: 'testadmin', password: process.env.TEST_PASSWORD };
await request.post('/api/auth/login', { headers, data: credentials });
await other.post('/api/auth/login', { headers, data: credentials });
const replacement = `${process.env.TEST_PASSWORD}-changed`;
expect(
(
await request.post('/api/auth/password', {
headers,
data: { current: process.env.TEST_PASSWORD, password: replacement },
})
).status(),
).toBe(200);
expect((await other.get('/api/export')).status()).toBe(401);
expect(
(
await request.post('/api/auth/password', {
headers,
data: { current: replacement, password: process.env.TEST_PASSWORD },
})
).status(),
).toBe(200);
for (let n = 0; n < 8; n++)
expect(
(
await other.post('/api/auth/login', {
headers,
data: { username: 'nonexistent', password: 'wrong' },
})
).status(),
).toBe(401);
expect(
(
await other.post('/api/auth/login', {
headers,
data: { username: 'nonexistent', password: 'wrong' },
})
).status(),
).toBe(429);
await other.dispose();
});