feat(api): role-aware session helper + admin-only activity writes

This commit is contained in:
Bas van Rossem
2026-06-17 17:43:37 +02:00
parent c73fa0f898
commit f2cc0973c7
6 changed files with 78 additions and 48 deletions

View File

@@ -24,9 +24,27 @@ describe('activities routes', () => {
expect(res.status).toBe(401);
});
it('forbids a worker from creating an activity (403)', async () => {
const app = createApp();
const token = await authToken(app, 'act-worker-create@example.com'); // default worker
const res = await app.request('/api/activities', {
method: 'POST',
headers: bearer(token),
body: JSON.stringify({ name: 'Frezen', insole_types: ['Kurk'] }),
});
expect(res.status).toBe(403);
});
it('lets a worker read activities (200)', async () => {
const app = createApp();
const token = await authToken(app, 'act-worker-read@example.com');
const res = await app.request('/api/activities', { headers: bearer(token) });
expect(res.status).toBe(200);
});
it('creates an activity and lists it', async () => {
const app = createApp();
const token = await authToken(app, 'act-create@example.com');
const token = await authToken(app, 'act-create@example.com', 'admin');
const createRes = await app.request('/api/activities', {
method: 'POST',
@@ -49,7 +67,7 @@ describe('activities routes', () => {
it('defaults insole_types to all three when omitted', async () => {
const app = createApp();
const token = await authToken(app, 'act-default@example.com');
const token = await authToken(app, 'act-default@example.com', 'admin');
const res = await app.request('/api/activities', {
method: 'POST',
@@ -63,7 +81,7 @@ describe('activities routes', () => {
it('filters by ?insole_type', async () => {
const app = createApp();
const token = await authToken(app, 'act-filter@example.com');
const token = await authToken(app, 'act-filter@example.com', 'admin');
const printRes = await app.request('/api/activities', {
method: 'POST',
@@ -87,7 +105,7 @@ describe('activities routes', () => {
it('400s POST with an empty name', async () => {
const app = createApp();
const token = await authToken(app, 'act-emptyname@example.com');
const token = await authToken(app, 'act-emptyname@example.com', 'admin');
const res = await app.request('/api/activities', {
method: 'POST',
@@ -99,7 +117,7 @@ describe('activities routes', () => {
it('updates an activity', async () => {
const app = createApp();
const token = await authToken(app, 'act-update@example.com');
const token = await authToken(app, 'act-update@example.com', 'admin');
const createRes = await app.request('/api/activities', {
method: 'POST',
@@ -122,7 +140,7 @@ describe('activities routes', () => {
it('404s PUT for a missing id', async () => {
const app = createApp();
const token = await authToken(app, 'act-put404@example.com');
const token = await authToken(app, 'act-put404@example.com', 'admin');
const res = await app.request('/api/activities/999999', {
method: 'PUT',
@@ -134,7 +152,7 @@ describe('activities routes', () => {
it('deletes an activity and its sessions', async () => {
const app = createApp();
const token = await authToken(app, 'act-delete@example.com');
const token = await authToken(app, 'act-delete@example.com', 'admin');
const createRes = await app.request('/api/activities', {
method: 'POST',

View File

@@ -5,17 +5,7 @@ import { db } from '../src/db/client';
import { workSessions } from '../src/db/schema';
import { eq } from 'drizzle-orm';
import { quote, formatDuration } from '../src/lib/csv';
import { authToken, bearer } from './helpers';
async function createActivity(app: Hono, token: string, name: string): Promise<number> {
const res = await app.request('/api/activities', {
method: 'POST',
headers: bearer(token),
body: JSON.stringify({ name, insole_types: ['Kurk', 'Berk', '3D'] }),
});
const body = await res.json();
return body.id as number;
}
import { authToken, bearer, seedActivity } from './helpers';
// Start a session, backdate its start_time by `durationSeconds`, then stop it,
// producing a completed session with an exact duration.
@@ -50,7 +40,7 @@ describe('csv export', () => {
it('exports completed sessions as CSV with the legacy header', async () => {
const app = createApp();
const token = await authToken(app, 'export-basic@example.com');
const activityId = await createActivity(app, token, 'Frezen');
const activityId = await seedActivity('Frezen');
await completedSession(app, token, activityId, 'Kurk', 90);
const res = await app.request('/api/export', { headers: bearer(token) });
@@ -75,7 +65,7 @@ describe('csv export', () => {
const app = createApp();
const tokenA = await authToken(app, 'export-scopeA@example.com');
const tokenB = await authToken(app, 'export-scopeB@example.com');
const activityId = await createActivity(app, tokenA, 'Slijpen');
const activityId = await seedActivity('Slijpen');
// User A: one completed session.
await completedSession(app, tokenA, activityId, 'Kurk', 30);