Skip to content

Commit 7a93f79

Browse files
committed
chore: add tests for auth middleware
1 parent 76afccc commit 7a93f79

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import type { NextFunction, Request, Response } from 'express'
2+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3+
4+
import type { UnauthenticatedContext } from '@/types/express/context'
5+
6+
import {
7+
getAuthenticatedContext,
8+
requireAuthentication,
9+
setCurrentUserContext,
10+
} from '../../middleware/authentication'
11+
12+
const mocks = vi.hoisted(() => ({
13+
setGraphQLContext: vi.fn(),
14+
}))
15+
16+
vi.mock('@/helpers/authentication', () => ({
17+
setCurrentUserContext: mocks.setGraphQLContext,
18+
}))
19+
20+
describe('API Authentication Middleware', () => {
21+
let mockReq: Partial<Request>
22+
let mockRes: Partial<Response>
23+
let mockNext: NextFunction
24+
25+
beforeEach(() => {
26+
mockReq = {
27+
headers: {},
28+
context: undefined,
29+
} as Partial<Request>
30+
31+
mockRes = {
32+
status: vi.fn().mockReturnThis(),
33+
json: vi.fn(),
34+
} as Partial<Response>
35+
36+
mockNext = vi.fn()
37+
})
38+
39+
afterEach(() => {
40+
vi.restoreAllMocks()
41+
})
42+
43+
describe('setCurrentUserContext', () => {
44+
it('should call GraphQL context function and attach context to request', async () => {
45+
const mockContext: UnauthenticatedContext = {
46+
req: mockReq as Request,
47+
res: mockRes as Response,
48+
currentUser: {
49+
id: 'test-user-id',
50+
51+
} as any,
52+
isAdminOperation: false,
53+
}
54+
55+
mocks.setGraphQLContext.mockResolvedValueOnce(mockContext)
56+
57+
await setCurrentUserContext(
58+
mockReq as Request,
59+
mockRes as Response,
60+
mockNext,
61+
)
62+
63+
expect(mocks.setGraphQLContext).toHaveBeenCalledWith({
64+
req: mockReq,
65+
res: mockRes,
66+
})
67+
expect(mockReq.context).toEqual(mockContext)
68+
expect(mockNext).toHaveBeenCalledOnce()
69+
})
70+
71+
it('should handle admin operations', async () => {
72+
const mockContext: UnauthenticatedContext = {
73+
req: mockReq as Request,
74+
res: mockRes as Response,
75+
currentUser: {
76+
id: 'admin-user-id',
77+
78+
} as any,
79+
isAdminOperation: true,
80+
}
81+
82+
mocks.setGraphQLContext.mockResolvedValueOnce(mockContext)
83+
84+
await setCurrentUserContext(
85+
mockReq as Request,
86+
mockRes as Response,
87+
mockNext,
88+
)
89+
90+
expect(mockReq.context?.isAdminOperation).toBe(true)
91+
expect(mockNext).toHaveBeenCalledOnce()
92+
})
93+
94+
it('should attach context even when user is null', async () => {
95+
const mockContext: UnauthenticatedContext = {
96+
req: mockReq as Request,
97+
res: mockRes as Response,
98+
currentUser: null,
99+
isAdminOperation: false,
100+
}
101+
102+
mocks.setGraphQLContext.mockResolvedValueOnce(mockContext)
103+
104+
await setCurrentUserContext(
105+
mockReq as Request,
106+
mockRes as Response,
107+
mockNext,
108+
)
109+
110+
expect(mockReq.context).toEqual(mockContext)
111+
expect(mockReq.context?.currentUser).toBeNull()
112+
expect(mockNext).toHaveBeenCalledOnce()
113+
})
114+
})
115+
116+
describe('requireAuthentication', () => {
117+
it('should call next() when user is authenticated', () => {
118+
mockReq.context = {
119+
req: mockReq as Request,
120+
res: mockRes as Response,
121+
currentUser: {
122+
id: 'test-user-id',
123+
124+
} as any,
125+
isAdminOperation: false,
126+
}
127+
128+
requireAuthentication(mockReq as Request, mockRes as Response, mockNext)
129+
130+
expect(mockNext).toHaveBeenCalledOnce()
131+
expect(mockRes.status).not.toHaveBeenCalled()
132+
expect(mockRes.json).not.toHaveBeenCalled()
133+
})
134+
135+
it('should return 401 when context is undefined', () => {
136+
mockReq.context = undefined
137+
138+
requireAuthentication(mockReq as Request, mockRes as Response, mockNext)
139+
140+
expect(mockRes.status).toHaveBeenCalledWith(401)
141+
expect(mockRes.json).toHaveBeenCalledWith({
142+
error: 'Not Authorised!',
143+
})
144+
expect(mockNext).not.toHaveBeenCalled()
145+
})
146+
147+
it('should return 401 when currentUser is null', () => {
148+
mockReq.context = {
149+
req: mockReq as Request,
150+
res: mockRes as Response,
151+
currentUser: null,
152+
isAdminOperation: false,
153+
}
154+
155+
requireAuthentication(mockReq as Request, mockRes as Response, mockNext)
156+
157+
expect(mockRes.status).toHaveBeenCalledWith(401)
158+
expect(mockRes.json).toHaveBeenCalledWith({
159+
error: 'Not Authorised!',
160+
})
161+
expect(mockNext).not.toHaveBeenCalled()
162+
})
163+
164+
it('should allow admin operations through', () => {
165+
mockReq.context = {
166+
req: mockReq as Request,
167+
res: mockRes as Response,
168+
currentUser: {
169+
id: 'admin-user-id',
170+
171+
} as any,
172+
isAdminOperation: true,
173+
}
174+
175+
requireAuthentication(mockReq as Request, mockRes as Response, mockNext)
176+
177+
expect(mockNext).toHaveBeenCalledOnce()
178+
expect(mockRes.status).not.toHaveBeenCalled()
179+
})
180+
})
181+
182+
describe('getAuthenticatedContext', () => {
183+
it('should return context when user is authenticated', () => {
184+
const mockContext = {
185+
req: mockReq as Request,
186+
res: mockRes as Response,
187+
currentUser: {
188+
id: 'test-user-id',
189+
190+
} as any,
191+
isAdminOperation: false,
192+
}
193+
194+
mockReq.context = mockContext
195+
196+
const result = getAuthenticatedContext(mockReq as Request)
197+
198+
expect(result).toEqual(mockContext)
199+
expect(result.currentUser).toBeDefined()
200+
expect(result.currentUser.id).toBe('test-user-id')
201+
})
202+
203+
it('should throw error when context is undefined', () => {
204+
mockReq.context = undefined
205+
206+
expect(() => getAuthenticatedContext(mockReq as Request)).toThrow(
207+
'User must be authenticated',
208+
)
209+
})
210+
211+
it('should throw error when currentUser is null', () => {
212+
mockReq.context = {
213+
req: mockReq as Request,
214+
res: mockRes as Response,
215+
currentUser: null,
216+
isAdminOperation: false,
217+
}
218+
219+
expect(() => getAuthenticatedContext(mockReq as Request)).toThrow(
220+
'User must be authenticated',
221+
)
222+
})
223+
224+
it('should return context for admin users', () => {
225+
const mockContext = {
226+
req: mockReq as Request,
227+
res: mockRes as Response,
228+
currentUser: {
229+
id: 'admin-user-id',
230+
231+
} as any,
232+
isAdminOperation: true,
233+
}
234+
235+
mockReq.context = mockContext
236+
237+
const result = getAuthenticatedContext(mockReq as Request)
238+
239+
expect(result).toEqual(mockContext)
240+
expect(result.isAdminOperation).toBe(true)
241+
})
242+
})
243+
})

0 commit comments

Comments
 (0)