test(ops): verify Docker persistence, browser workflows and document deployment
This commit is contained in:
1 parent
a58516e424
commit
8fa63415f1
35 files changed
+1105
No files matched your search
@@ -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();
|
||||
});
|
||||
Reference in new issue
Block a user