Barazo AppView backend barazo.forum
at main 202 lines 5.6 kB view raw
1import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' 2import Fastify from 'fastify' 3import type { FastifyInstance } from 'fastify' 4import { createRequireAdmin } from '../../../src/auth/require-admin.js' 5import type { AuthMiddleware, RequestUser } from '../../../src/auth/middleware.js' 6 7// --------------------------------------------------------------------------- 8// Mock database 9// --------------------------------------------------------------------------- 10 11interface MockUserRow { 12 did: string 13 handle: string 14 role: string 15} 16 17const mockDbSelect = vi.fn() 18const mockDbFrom = vi.fn() 19const mockDbWhere = vi.fn() 20 21function createMockDb() { 22 // Chain: db.select().from(users).where(eq(users.did, did)) 23 mockDbWhere.mockReturnValue([]) 24 mockDbFrom.mockReturnValue({ where: mockDbWhere }) 25 mockDbSelect.mockReturnValue({ from: mockDbFrom }) 26 27 return { 28 select: mockDbSelect, 29 } 30} 31 32// --------------------------------------------------------------------------- 33// Mock auth middleware 34// --------------------------------------------------------------------------- 35 36function createMockAuthMiddleware(): AuthMiddleware { 37 return { 38 requireAuth: vi.fn(async (_request, _reply) => { 39 // Simulate setting user - tests will set request.user before calling 40 }), 41 optionalAuth: vi.fn(), 42 } 43} 44 45// --------------------------------------------------------------------------- 46// Fixtures 47// --------------------------------------------------------------------------- 48 49const ADMIN_USER: RequestUser = { 50 did: 'did:plc:admin123', 51 handle: 'admin.bsky.social', 52 sid: 's'.repeat(64), 53} 54 55const REGULAR_USER: RequestUser = { 56 did: 'did:plc:user456', 57 handle: 'user.bsky.social', 58 sid: 's'.repeat(64), 59} 60 61const ADMIN_DB_ROW: MockUserRow = { 62 did: ADMIN_USER.did, 63 handle: ADMIN_USER.handle, 64 role: 'admin', 65} 66 67const REGULAR_DB_ROW: MockUserRow = { 68 did: REGULAR_USER.did, 69 handle: REGULAR_USER.handle, 70 role: 'user', 71} 72 73const MODERATOR_DB_ROW: MockUserRow = { 74 did: 'did:plc:mod789', 75 handle: 'mod.bsky.social', 76 role: 'moderator', 77} 78 79// --------------------------------------------------------------------------- 80// Tests 81// --------------------------------------------------------------------------- 82 83describe('requireAdmin middleware', () => { 84 let app: FastifyInstance 85 let mockAuthMiddleware: AuthMiddleware 86 87 beforeEach(async () => { 88 vi.clearAllMocks() 89 90 const mockDb = createMockDb() 91 mockAuthMiddleware = createMockAuthMiddleware() 92 93 const requireAdmin = createRequireAdmin(mockDb as never, mockAuthMiddleware) 94 95 app = Fastify({ logger: false }) 96 app.decorateRequest('user', undefined as RequestUser | undefined) 97 98 app.get('/admin-test', { preHandler: [requireAdmin] }, (request) => { 99 return { user: request.user } 100 }) 101 102 await app.ready() 103 }) 104 105 afterEach(async () => { 106 await app.close() 107 }) 108 109 it('returns 401 when requireAuth rejects (no token)', async () => { 110 // Make requireAuth return 401 111 vi.mocked(mockAuthMiddleware.requireAuth).mockImplementation(async (_request, reply) => { 112 await reply.status(401).send({ error: 'Authentication required' }) 113 }) 114 115 const response = await app.inject({ 116 method: 'GET', 117 url: '/admin-test', 118 }) 119 120 expect(response.statusCode).toBe(401) 121 expect(response.json<{ error: string }>()).toStrictEqual({ 122 error: 'Authentication required', 123 }) 124 }) 125 126 it('returns 403 when user is not found in database', async () => { 127 // requireAuth passes and sets user 128 vi.mocked(mockAuthMiddleware.requireAuth).mockImplementation(async (request, _reply) => { 129 request.user = ADMIN_USER 130 }) 131 132 // User not found in DB 133 mockDbWhere.mockResolvedValueOnce([]) 134 135 const response = await app.inject({ 136 method: 'GET', 137 url: '/admin-test', 138 }) 139 140 expect(response.statusCode).toBe(403) 141 expect(response.json<{ error: string }>()).toStrictEqual({ 142 error: 'Admin access required', 143 }) 144 }) 145 146 it("returns 403 when user has role 'user'", async () => { 147 vi.mocked(mockAuthMiddleware.requireAuth).mockImplementation(async (request, _reply) => { 148 request.user = REGULAR_USER 149 }) 150 151 mockDbWhere.mockResolvedValueOnce([REGULAR_DB_ROW]) 152 153 const response = await app.inject({ 154 method: 'GET', 155 url: '/admin-test', 156 }) 157 158 expect(response.statusCode).toBe(403) 159 expect(response.json<{ error: string }>()).toStrictEqual({ 160 error: 'Admin access required', 161 }) 162 }) 163 164 it("returns 403 when user has role 'moderator'", async () => { 165 vi.mocked(mockAuthMiddleware.requireAuth).mockImplementation(async (request, _reply) => { 166 request.user = { 167 did: MODERATOR_DB_ROW.did, 168 handle: MODERATOR_DB_ROW.handle, 169 sid: 's'.repeat(64), 170 } 171 }) 172 173 mockDbWhere.mockResolvedValueOnce([MODERATOR_DB_ROW]) 174 175 const response = await app.inject({ 176 method: 'GET', 177 url: '/admin-test', 178 }) 179 180 expect(response.statusCode).toBe(403) 181 expect(response.json<{ error: string }>()).toStrictEqual({ 182 error: 'Admin access required', 183 }) 184 }) 185 186 it('passes through for admin user and returns 200', async () => { 187 vi.mocked(mockAuthMiddleware.requireAuth).mockImplementation(async (request, _reply) => { 188 request.user = ADMIN_USER 189 }) 190 191 mockDbWhere.mockResolvedValueOnce([ADMIN_DB_ROW]) 192 193 const response = await app.inject({ 194 method: 'GET', 195 url: '/admin-test', 196 }) 197 198 expect(response.statusCode).toBe(200) 199 const body = response.json<{ user: RequestUser }>() 200 expect(body.user).toStrictEqual(ADMIN_USER) 201 }) 202})