Skip to content

Commit de0e1a4

Browse files
OUT-4079 | Wrap legacy activity-log route with withErrorHandler
The GET /api/activity-logs/[id] route was the only authenticated API route not using withErrorHandler. Unhandled ZodErrors from invalid task UUIDs (and auth failures) bubbled to Next.js onRequestError and created Sentry noise titled 'ZodError: ['. Add regression tests for missing-token 401 and invalid-UUID 422 paths. Co-authored-by: Neil Raina <makeitraina@users.noreply.github.com>
1 parent 8f63a1b commit de0e1a4

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

src/app/api/activity-logs/[id]/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { NextRequest, NextResponse } from 'next/server'
22
import authenticate from '@api/core/utils/authenticate'
33
import { ActivityLogService } from '@api/activity-logs/services/activity-log.service'
44
import { IdParams } from '@api/core/types/api'
5+
import { withErrorHandler } from '@api/core/utils/withErrorHandler'
56
import { unstable_noStore as noStore } from 'next/cache'
67

7-
export const GET = async (req: NextRequest, props: IdParams) => {
8+
const getActivityLog = async (req: NextRequest, props: IdParams) => {
89
const params = await props.params
910

1011
const { id } = params
@@ -18,3 +19,5 @@ export const GET = async (req: NextRequest, props: IdParams) => {
1819

1920
return NextResponse.json({ activity })
2021
}
22+
23+
export const GET = withErrorHandler(getActivityLog)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { GET } from '@api/activity-logs/[id]/route'
2+
import { ActivityLogService } from '@api/activity-logs/services/activity-log.service'
3+
import APIError from '@api/core/exceptions/api'
4+
import authenticate from '@api/core/utils/authenticate'
5+
import User from '@api/core/models/User.model'
6+
import { buildNextRequest } from '@api/tests/__utils__/testUtils'
7+
import httpStatus from 'http-status'
8+
import { z } from 'zod'
9+
10+
jest.mock('@api/activity-logs/services/activity-log.service', () => ({
11+
ActivityLogService: jest.fn().mockImplementation(() => ({
12+
get: (taskId: string) => {
13+
z.string().uuid().parse(taskId)
14+
return Promise.resolve([])
15+
},
16+
})),
17+
}))
18+
19+
jest.mock('@api/core/utils/authenticate', () => jest.fn())
20+
21+
describe('activity log route', () => {
22+
const mockUser = new User('iu-token', {
23+
workspaceId: 'workspace-id',
24+
internalUserId: 'internal-user-id',
25+
})
26+
27+
beforeEach(() => {
28+
jest.clearAllMocks()
29+
jest.spyOn(console, 'error').mockImplementation()
30+
jest.mocked(authenticate).mockResolvedValue(mockUser)
31+
})
32+
33+
afterEach(() => {
34+
jest.restoreAllMocks()
35+
})
36+
37+
it('returns a quiet 401 response when token is missing', async () => {
38+
jest.mocked(authenticate).mockRejectedValue(new APIError(httpStatus.UNAUTHORIZED, 'Please provide a valid token'))
39+
40+
const req = buildNextRequest('/api/activity-logs/activity-id')
41+
const response = await GET(req, { params: Promise.resolve({ id: 'activity-id' }) })
42+
const body = await response.json()
43+
44+
expect(response.status).toBe(httpStatus.UNAUTHORIZED)
45+
expect(body.error).toBe('Please provide a valid token')
46+
expect(console.error).not.toHaveBeenCalled()
47+
})
48+
49+
it('returns a quiet 422 response when task id is not a valid UUID', async () => {
50+
const req = buildNextRequest('/api/activity-logs/not-a-uuid?token=iu-token')
51+
const response = await GET(req, { params: Promise.resolve({ id: 'not-a-uuid' }) })
52+
const body = await response.json()
53+
54+
expect(response.status).toBe(httpStatus.UNPROCESSABLE_ENTITY)
55+
expect(body.error).toContain('Invalid uuid')
56+
expect(console.error).not.toHaveBeenCalled()
57+
expect(ActivityLogService).toHaveBeenCalledWith(mockUser)
58+
})
59+
})

0 commit comments

Comments
 (0)