test(api): add resetDb helper + per-test isolation (proof: sessions suite)

This commit is contained in:
Bas van Rossem
2026-07-22 11:28:01 +02:00
parent 3c79e51e99
commit f872daf117
3 changed files with 40 additions and 3 deletions

View File

@@ -1,7 +1,7 @@
import type { Hono } from 'hono'; import type { Hono } from 'hono';
import { auth } from '../src/auth'; import { auth } from '../src/auth';
import { db } from '../src/db/client'; import { db } from '../src/db/client';
import { activities } from '../src/db/schema'; import { account, activities, session, user, verification, workSessions } from '../src/db/schema';
const PASSWORD = 'sterk-wachtwoord-123'; const PASSWORD = 'sterk-wachtwoord-123';
const json = { 'content-type': 'application/json' }; const json = { 'content-type': 'application/json' };
@@ -44,6 +44,18 @@ export function bearer(token: string): Record<string, string> {
return { authorization: `Bearer ${token}`, 'content-type': 'application/json' }; return { authorization: `Bearer ${token}`, 'content-type': 'application/json' };
} }
// Wipe all data rows (schema stays; migration already ran once in setup.ts).
// Lets a domain suite start from an empty DB in beforeEach instead of inheriting
// rows from earlier test files (removes the accumulate-and-scope fragility).
export async function resetDb(): Promise<void> {
await db.delete(workSessions);
await db.delete(session);
await db.delete(account);
await db.delete(verification);
await db.delete(user);
await db.delete(activities);
}
// Insert an activity straight into the DB (test setup that should not depend on authz). // Insert an activity straight into the DB (test setup that should not depend on authz).
export async function seedActivity( export async function seedActivity(
name: string, name: string,

View File

@@ -0,0 +1,21 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { db } from '../src/db/client';
import { workSessions, user } from '../src/db/schema';
import { resetDb, seedActivity, authToken } from './helpers';
import { createApp } from '../src/app';
describe('resetDb', () => {
beforeEach(resetDb);
it('clears domain and auth rows so counts start from zero', async () => {
const app = createApp();
await authToken(app, 'reset-a@example.com');
await seedActivity('Zagen');
expect((await db.select().from(user)).length).toBeGreaterThan(0);
await resetDb();
expect(await db.select().from(user)).toHaveLength(0);
expect(await db.select().from(workSessions)).toHaveLength(0);
});
});

View File

@@ -1,13 +1,15 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect, beforeEach } from 'vitest';
import { createApp } from '../src/app'; import { createApp } from '../src/app';
import { db } from '../src/db/client'; import { db } from '../src/db/client';
import { workSessions } from '../src/db/schema'; import { workSessions } from '../src/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { authToken, bearer, seedActivity } from './helpers'; import { authToken, bearer, seedActivity, resetDb } from './helpers';
const json = { 'content-type': 'application/json' }; const json = { 'content-type': 'application/json' };
describe('session lifecycle', () => { describe('session lifecycle', () => {
beforeEach(resetDb);
it('401s start/stop/discard without a token', async () => { it('401s start/stop/discard without a token', async () => {
const app = createApp(); const app = createApp();
@@ -334,6 +336,8 @@ describe('session lifecycle', () => {
}); });
describe('session reads', () => { describe('session reads', () => {
beforeEach(resetDb);
it('401s GET /api/sessions and /api/sessions/active without a token', async () => { it('401s GET /api/sessions and /api/sessions/active without a token', async () => {
const app = createApp(); const app = createApp();