Files
electric-cherry/tests/unit/moderation-case-routes.test.ts
josh 7f2205e9c4
CI / checks (push) Successful in 6m49s
Expand moderation case workflows
2026-10-17 16:19:33 -04:00

121 lines
4.8 KiB
TypeScript

import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import type { AuthUser } from '../../src/lib/auth.js';
import { newFixture, type Fixture } from '../helpers/fixture.js';
let fixture: Fixture;
beforeEach(async () => {
vi.resetModules();
fixture = await newFixture([]);
const { db } = await import('../../src/lib/server/db/index.js');
for (const role of ['admin', 'moderator', 'super_viewer', 'operator', 'viewer']) {
db()
.prepare(
`INSERT INTO auth_users
(id,username,display_name,password_hash,role,created_at,updated_at)
VALUES(?,?,?,'unused',?,0,0)`
)
.run(role, role, role, role);
}
db()
.prepare(
`INSERT INTO moderation_cases
(id,game,guid,title,status,actor_id,actor,created_at,updated_at)
VALUES('case-123','t5','123','Private investigation','open','moderator','moderator',?,?)`
)
.run('2026-10-17T12:00:00.000Z', '2026-10-17T12:00:00.000Z');
});
afterEach(async () => fixture.cleanup());
async function api(user: AuthUser | null, id = 'case-123') {
const { GET } = await import('../../src/routes/api/moderation/cases/[id]/+server.js');
return GET({ locals: { user }, params: { id } } as Parameters<typeof GET>[0]);
}
async function page(user: AuthUser | null, id = 'case-123') {
const { load } = await import('../../src/routes/moderation/cases/[id]/+page.server.js');
const setHeaders = vi.fn();
const result = await load({
locals: { user },
params: { id },
setHeaders
} as unknown as Parameters<typeof load>[0]);
return { result, setHeaders };
}
it.each(['admin', 'moderator', 'super_viewer'])(
'serves private case detail without caching to %s',
async (role) => {
const { getUser } = await import('../../src/lib/server/auth/index.js');
const user = getUser(role)!;
const response = await api(user);
expect(response.status).toBe(200);
expect(response.headers.get('Cache-Control')).toBe('no-store');
expect(await response.json()).toMatchObject({
case: { id: 'case-123', title: 'Private investigation' }
});
const { result, setHeaders } = await page(user);
expect(result).toMatchObject({
moderationCase: { case: { id: 'case-123', title: 'Private investigation' } }
});
expect(setHeaders).toHaveBeenCalledWith({ 'Cache-Control': 'no-store' });
}
);
it.each(['operator', 'viewer'])(
'rejects %s reads before revealing case existence or content',
async (role) => {
const { getUser } = await import('../../src/lib/server/auth/index.js');
const user = getUser(role)!;
for (const id of ['case-123', 'missing']) {
const response = await api(user, id);
expect(response.status).toBe(403);
expect(response.headers.get('Cache-Control')).toBe('no-store');
expect(await response.json()).toEqual({ error: 'Moderation access is required.' });
await expect(page(user, id)).rejects.toMatchObject({
status: 403,
body: { message: 'Moderation access is required.' }
});
}
}
);
it.each(["role='viewer'", 'disabled=1', 'must_change_password=1'])(
'rechecks current staff access when stale session locals have %s',
async (change) => {
const { getUser } = await import('../../src/lib/server/auth/index.js');
const { db } = await import('../../src/lib/server/db/index.js');
const user = getUser('moderator')!;
db().prepare(`UPDATE auth_users SET ${change} WHERE id='moderator'`).run();
const response = await api(user);
expect(response.status).toBe(403);
expect(response.headers.get('Cache-Control')).toBe('no-store');
await expect(page(user)).rejects.toMatchObject({ status: 403 });
}
);
it('preserves private not-found responses in the API and page loader', async () => {
const { getUser } = await import('../../src/lib/server/auth/index.js');
const user = getUser('moderator')!;
const response = await api(user, 'missing');
expect(response.status).toBe(404);
expect(response.headers.get('Cache-Control')).toBe('no-store');
expect(await response.json()).toEqual({ error: 'Case not found.' });
await expect(page(user, 'missing')).rejects.toMatchObject({
status: 404,
body: { message: 'Case not found.' }
});
});
it('requires a signed-in user before looking up a case', async () => {
const response = await api(null);
expect(response.status).toBe(401);
expect(response.headers.get('Cache-Control')).toBe('no-store');
expect(await response.json()).toEqual({ error: 'Sign in to continue.' });
await expect(page(null)).rejects.toMatchObject({
status: 401,
body: { message: 'Sign in to continue.' }
});
});