feat(api): orderable activities + admin reorder endpoint

This commit is contained in:
Bas van Rossem
2026-06-17 20:58:06 +02:00
parent 974ecb120d
commit 56e0162230
2 changed files with 155 additions and 4 deletions

View File

@@ -150,6 +150,120 @@ describe('activities routes', () => {
expect(res.status).toBe(404);
});
it('orders GET by sort_order then name', async () => {
const app = createApp();
const token = await authToken(app, 'act-order@example.com', 'admin');
// Create three activities; they append with increasing sort_order.
const names = ['Order-C', 'Order-A', 'Order-B'];
const created: Array<{ id: number; name: string }> = [];
for (const name of names) {
const res = await app.request('/api/activities', {
method: 'POST',
headers: bearer(token),
body: JSON.stringify({ name, insole_types: ['Kurk'] }),
});
created.push(await res.json());
}
const listRes = await app.request('/api/activities', { headers: bearer(token) });
const list: Array<{ id: number; sort_order: number }> = await listRes.json();
// Filtered to just our three, in returned order.
const ours = list.filter((a) => created.some((c) => c.id === a.id));
expect(ours.map((a) => a.id)).toEqual(created.map((c) => c.id));
// sort_order is non-decreasing across the whole list.
for (let i = 1; i < list.length; i++) {
expect(list[i].sort_order).toBeGreaterThanOrEqual(list[i - 1].sort_order);
}
});
it('appends a new activity with a higher sort_order than existing ones', async () => {
const app = createApp();
const token = await authToken(app, 'act-append@example.com', 'admin');
const firstRes = await app.request('/api/activities', {
method: 'POST',
headers: bearer(token),
body: JSON.stringify({ name: 'Append-1', insole_types: ['Kurk'] }),
});
const first = await firstRes.json();
const secondRes = await app.request('/api/activities', {
method: 'POST',
headers: bearer(token),
body: JSON.stringify({ name: 'Append-2', insole_types: ['Kurk'] }),
});
const second = await secondRes.json();
expect(second.sort_order).toBeGreaterThan(first.sort_order);
});
it('lets an admin reorder activities (PUT /api/activities/reorder)', async () => {
const app = createApp();
const token = await authToken(app, 'act-reorder@example.com', 'admin');
// Existing activities (possibly seeded by other tests) plus our two.
const aRes = await app.request('/api/activities', {
method: 'POST',
headers: bearer(token),
body: JSON.stringify({ name: 'Reorder-A', insole_types: ['Kurk'] }),
});
const a = await aRes.json();
const bRes = await app.request('/api/activities', {
method: 'POST',
headers: bearer(token),
body: JSON.stringify({ name: 'Reorder-B', insole_types: ['Kurk'] }),
});
const b = await bRes.json();
// Build the full ordered id list, then swap a and b so b comes first.
const beforeRes = await app.request('/api/activities', { headers: bearer(token) });
const before: Array<{ id: number }> = await beforeRes.json();
const ids = before.map((r) => r.id);
const ia = ids.indexOf(a.id);
const ib = ids.indexOf(b.id);
[ids[ia], ids[ib]] = [ids[ib], ids[ia]];
const reorderRes = await app.request('/api/activities/reorder', {
method: 'PUT',
headers: bearer(token),
body: JSON.stringify({ ids }),
});
expect(reorderRes.status).toBe(200);
const afterRes = await app.request('/api/activities', { headers: bearer(token) });
const after: Array<{ id: number }> = await afterRes.json();
expect(after.map((r) => r.id)).toEqual(ids);
// In particular, b now precedes a.
expect(after.findIndex((r) => r.id === b.id)).toBeLessThan(
after.findIndex((r) => r.id === a.id)
);
});
it('forbids a worker from reordering activities (403)', async () => {
const app = createApp();
const token = await authToken(app, 'act-reorder-worker@example.com');
const res = await app.request('/api/activities/reorder', {
method: 'PUT',
headers: bearer(token),
body: JSON.stringify({ ids: [1] }),
});
expect(res.status).toBe(403);
});
it('400s reorder when ids do not match the full set', async () => {
const app = createApp();
const token = await authToken(app, 'act-reorder-badids@example.com', 'admin');
const res = await app.request('/api/activities/reorder', {
method: 'PUT',
headers: bearer(token),
body: JSON.stringify({ ids: [999999] }),
});
expect(res.status).toBe(400);
});
it('deletes an activity and its sessions', async () => {
const app = createApp();
const token = await authToken(app, 'act-delete@example.com', 'admin');