Files
jayjennings c69eccb5c1
CI / Lint, typecheck, test, build (push) Canceled after 0s
CI / End-to-end tests (push) Canceled after 0s
feat: implement incident reporting and management system
- Add incident reporting routes and logic to handle creation, updating, and retrieval of incidents.
- Implement visibility rules for users based on roles (reporters, supervisors).
- Create tests for incident reporting functionality to ensure correctness and reliability.
- Introduce site and area management routes, including validation for area hierarchy.
- Establish user management routes with role-based access control for creating and updating users.
- Add setup routes for initializing the application with an organization and admin user.
- Implement shared enums and schemas for consistent validation and data handling across the application.
- Create a test helper for setting up an in-memory database and API client for testing.
- Add TypeScript configuration for server-side code with strict type checking and module resolution.
2026-10-06 10:51:18 -07:00

57 lines
2.9 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { createTestContext } from '../test/helpers.ts'
let ctx: Awaited<ReturnType<typeof createTestContext>>
beforeAll(async () => {
ctx = await createTestContext()
})
afterAll(() => ctx.close())
describe('users', () => {
it('lets admins create users and rejects duplicate emails', async () => {
const body = { name: 'Sam Rivera', email: 'Sam@Example.com', role: 'supervisor', password: 'a sufficiently long password' }
const created = await ctx.admin.post('/api/users', body)
expect(created.status).toBe(201)
expect(created.body.user).toMatchObject({ email: 'sam@example.com', role: 'supervisor', active: true })
expect(created.body.user.passwordHash).toBeUndefined()
expect((await ctx.admin.post('/api/users', body)).status).toBe(409)
})
it('validates input', async () => {
const res = await ctx.admin.post('/api/users', { name: '', email: 'not-an-email', role: 'boss', password: 'short' })
expect(res.status).toBe(400)
expect(res.body.issues.map((issue: { path: string }) => issue.path).sort()).toEqual(['email', 'name', 'password', 'role'])
})
it('restricts listing to safety managers and creating to admins', async () => {
const supervisor = await ctx.createUser('supervisor')
const manager = await ctx.createUser('safety_manager')
expect((await supervisor.get('/api/users')).status).toBe(403)
expect((await manager.get('/api/users')).status).toBe(200)
expect((await manager.post('/api/users', { name: 'X', email: 'x@example.com', role: 'admin', password: 'a sufficiently long password' })).status).toBe(403)
expect((await ctx.anonymous().get('/api/users')).status).toBe(401)
})
it('returns 404 for unknown or malformed ids', async () => {
expect((await ctx.admin.patch('/api/users/00000000-0000-4000-8000-000000000000', { name: 'Nobody' })).status).toBe(404)
expect((await ctx.admin.patch('/api/users/not-a-uuid', { name: 'Nobody' })).status).toBe(400)
})
it('keeps at least one active admin', async () => {
const me = await ctx.admin.get('/api/auth/me')
const adminId = me.body.user.id
expect((await ctx.admin.patch(`/api/users/${adminId}`, { role: 'reporter' })).status).toBe(409)
expect((await ctx.admin.patch(`/api/users/${adminId}`, { active: false })).status).toBe(400)
const second = await ctx.createUser('admin', 'second-admin@example.com')
const demoted = await second.patch(`/api/users/${adminId}`, { role: 'safety_manager' })
expect(demoted.status).toBe(200)
expect(demoted.body.user.role).toBe('safety_manager')
// The demoted user stays signed in but loses admin rights immediately
expect((await ctx.admin.get('/api/auth/me')).status).toBe(200)
expect((await ctx.admin.post('/api/users', { name: 'Y', email: 'y@example.com', role: 'reporter', password: 'a sufficiently long password' })).status).toBe(403)
})
})