Skip to content

Commit 74db11c

Browse files
committed
added tests
1 parent e207ad2 commit 74db11c

File tree

12 files changed

+1538
-15
lines changed

12 files changed

+1538
-15
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { createMockRequest, mockConsoleLogger, mockDrizzleOrm } from '@sim/testing'
5+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6+
7+
vi.mock('@sim/db/schema', () => ({
8+
document: {
9+
id: 'id',
10+
connectorId: 'connectorId',
11+
deletedAt: 'deletedAt',
12+
filename: 'filename',
13+
externalId: 'externalId',
14+
sourceUrl: 'sourceUrl',
15+
enabled: 'enabled',
16+
userExcluded: 'userExcluded',
17+
uploadedAt: 'uploadedAt',
18+
processingStatus: 'processingStatus',
19+
},
20+
knowledgeConnector: {
21+
id: 'id',
22+
knowledgeBaseId: 'knowledgeBaseId',
23+
deletedAt: 'deletedAt',
24+
},
25+
}))
26+
27+
vi.mock('@/app/api/knowledge/utils', () => ({
28+
checkKnowledgeBaseAccess: vi.fn(),
29+
checkKnowledgeBaseWriteAccess: vi.fn(),
30+
}))
31+
vi.mock('@/lib/auth/hybrid', () => ({
32+
checkSessionOrInternalAuth: vi.fn(),
33+
}))
34+
vi.mock('@/lib/core/utils/request', () => ({
35+
generateRequestId: vi.fn().mockReturnValue('test-req-id'),
36+
}))
37+
38+
mockDrizzleOrm()
39+
mockConsoleLogger()
40+
41+
describe('Connector Documents API Route', () => {
42+
/**
43+
* The route chains db calls in sequence. We track call order
44+
* to return different values for connector lookup vs document queries.
45+
*/
46+
let limitCallCount: number
47+
let orderByCallCount: number
48+
49+
const mockDbChain = {
50+
select: vi.fn().mockReturnThis(),
51+
from: vi.fn().mockReturnThis(),
52+
where: vi.fn().mockReturnThis(),
53+
orderBy: vi.fn(() => {
54+
orderByCallCount++
55+
return Promise.resolve([])
56+
}),
57+
limit: vi.fn(() => {
58+
limitCallCount++
59+
return Promise.resolve([])
60+
}),
61+
update: vi.fn().mockReturnThis(),
62+
set: vi.fn().mockReturnThis(),
63+
returning: vi.fn().mockResolvedValue([]),
64+
}
65+
66+
const mockParams = Promise.resolve({ id: 'kb-123', connectorId: 'conn-456' })
67+
68+
beforeEach(() => {
69+
vi.clearAllMocks()
70+
limitCallCount = 0
71+
orderByCallCount = 0
72+
mockDbChain.select.mockReturnThis()
73+
mockDbChain.from.mockReturnThis()
74+
mockDbChain.where.mockReturnThis()
75+
mockDbChain.orderBy.mockImplementation(() => {
76+
orderByCallCount++
77+
return Promise.resolve([])
78+
})
79+
mockDbChain.limit.mockImplementation(() => {
80+
limitCallCount++
81+
return Promise.resolve([])
82+
})
83+
mockDbChain.update.mockReturnThis()
84+
mockDbChain.set.mockReturnThis()
85+
mockDbChain.returning.mockResolvedValue([])
86+
87+
vi.doMock('@sim/db', () => ({ db: mockDbChain }))
88+
})
89+
90+
afterEach(() => {
91+
vi.clearAllMocks()
92+
})
93+
94+
describe('GET', () => {
95+
it('returns 401 when unauthenticated', async () => {
96+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
97+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
98+
success: false,
99+
userId: null,
100+
} as never)
101+
102+
const req = createMockRequest('GET')
103+
const { GET } = await import(
104+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
105+
)
106+
const response = await GET(req as never, { params: mockParams })
107+
108+
expect(response.status).toBe(401)
109+
})
110+
111+
it('returns 404 when connector not found', async () => {
112+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
113+
const { checkKnowledgeBaseAccess } = await import('@/app/api/knowledge/utils')
114+
115+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
116+
success: true,
117+
userId: 'user-1',
118+
} as never)
119+
vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: true } as never)
120+
121+
mockDbChain.limit.mockResolvedValueOnce([])
122+
123+
const req = createMockRequest('GET')
124+
const { GET } = await import(
125+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
126+
)
127+
const response = await GET(req as never, { params: mockParams })
128+
129+
expect(response.status).toBe(404)
130+
})
131+
132+
it('returns documents list on success', async () => {
133+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
134+
const { checkKnowledgeBaseAccess } = await import('@/app/api/knowledge/utils')
135+
136+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
137+
success: true,
138+
userId: 'user-1',
139+
} as never)
140+
vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: true } as never)
141+
142+
const doc = { id: 'doc-1', filename: 'test.txt', userExcluded: false }
143+
mockDbChain.limit.mockResolvedValueOnce([{ id: 'conn-456' }])
144+
mockDbChain.orderBy.mockResolvedValueOnce([doc])
145+
146+
const url = 'http://localhost/api/knowledge/kb-123/connectors/conn-456/documents'
147+
const req = createMockRequest('GET', undefined, undefined, url)
148+
Object.assign(req, { nextUrl: new URL(url) })
149+
const { GET } = await import(
150+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
151+
)
152+
const response = await GET(req as never, { params: mockParams })
153+
const data = await response.json()
154+
155+
expect(response.status).toBe(200)
156+
expect(data.data.documents).toHaveLength(1)
157+
expect(data.data.counts.active).toBe(1)
158+
expect(data.data.counts.excluded).toBe(0)
159+
})
160+
161+
it('includes excluded documents when includeExcluded=true', async () => {
162+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
163+
const { checkKnowledgeBaseAccess } = await import('@/app/api/knowledge/utils')
164+
165+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
166+
success: true,
167+
userId: 'user-1',
168+
} as never)
169+
vi.mocked(checkKnowledgeBaseAccess).mockResolvedValue({ hasAccess: true } as never)
170+
171+
mockDbChain.limit.mockResolvedValueOnce([{ id: 'conn-456' }])
172+
mockDbChain.orderBy
173+
.mockResolvedValueOnce([{ id: 'doc-1', userExcluded: false }])
174+
.mockResolvedValueOnce([{ id: 'doc-2', userExcluded: true }])
175+
176+
const url =
177+
'http://localhost/api/knowledge/kb-123/connectors/conn-456/documents?includeExcluded=true'
178+
const req = createMockRequest('GET', undefined, undefined, url)
179+
Object.assign(req, { nextUrl: new URL(url) })
180+
const { GET } = await import(
181+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
182+
)
183+
const response = await GET(req as never, { params: mockParams })
184+
const data = await response.json()
185+
186+
expect(response.status).toBe(200)
187+
expect(data.data.documents).toHaveLength(2)
188+
expect(data.data.counts.active).toBe(1)
189+
expect(data.data.counts.excluded).toBe(1)
190+
})
191+
})
192+
193+
describe('PATCH', () => {
194+
it('returns 401 when unauthenticated', async () => {
195+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
196+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
197+
success: false,
198+
userId: null,
199+
} as never)
200+
201+
const req = createMockRequest('PATCH', { operation: 'restore', documentIds: ['doc-1'] })
202+
const { PATCH } = await import(
203+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
204+
)
205+
const response = await PATCH(req as never, { params: mockParams })
206+
207+
expect(response.status).toBe(401)
208+
})
209+
210+
it('returns 400 for invalid body', async () => {
211+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
212+
const { checkKnowledgeBaseWriteAccess } = await import('@/app/api/knowledge/utils')
213+
214+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
215+
success: true,
216+
userId: 'user-1',
217+
} as never)
218+
vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true } as never)
219+
mockDbChain.limit.mockResolvedValueOnce([{ id: 'conn-456' }])
220+
221+
const req = createMockRequest('PATCH', { documentIds: [] })
222+
const { PATCH } = await import(
223+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
224+
)
225+
const response = await PATCH(req as never, { params: mockParams })
226+
227+
expect(response.status).toBe(400)
228+
})
229+
230+
it('returns 404 when connector not found', async () => {
231+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
232+
const { checkKnowledgeBaseWriteAccess } = await import('@/app/api/knowledge/utils')
233+
234+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
235+
success: true,
236+
userId: 'user-1',
237+
} as never)
238+
vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true } as never)
239+
mockDbChain.limit.mockResolvedValueOnce([])
240+
241+
const req = createMockRequest('PATCH', { operation: 'restore', documentIds: ['doc-1'] })
242+
const { PATCH } = await import(
243+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
244+
)
245+
const response = await PATCH(req as never, { params: mockParams })
246+
247+
expect(response.status).toBe(404)
248+
})
249+
250+
it('returns success for restore operation', async () => {
251+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
252+
const { checkKnowledgeBaseWriteAccess } = await import('@/app/api/knowledge/utils')
253+
254+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
255+
success: true,
256+
userId: 'user-1',
257+
} as never)
258+
vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true } as never)
259+
mockDbChain.limit.mockResolvedValueOnce([{ id: 'conn-456' }])
260+
mockDbChain.returning.mockResolvedValueOnce([{ id: 'doc-1' }])
261+
262+
const req = createMockRequest('PATCH', { operation: 'restore', documentIds: ['doc-1'] })
263+
const { PATCH } = await import(
264+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
265+
)
266+
const response = await PATCH(req as never, { params: mockParams })
267+
const data = await response.json()
268+
269+
expect(response.status).toBe(200)
270+
expect(data.data.restoredCount).toBe(1)
271+
})
272+
273+
it('returns success for exclude operation', async () => {
274+
const { checkSessionOrInternalAuth } = await import('@/lib/auth/hybrid')
275+
const { checkKnowledgeBaseWriteAccess } = await import('@/app/api/knowledge/utils')
276+
277+
vi.mocked(checkSessionOrInternalAuth).mockResolvedValue({
278+
success: true,
279+
userId: 'user-1',
280+
} as never)
281+
vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValue({ hasAccess: true } as never)
282+
mockDbChain.limit.mockResolvedValueOnce([{ id: 'conn-456' }])
283+
mockDbChain.returning.mockResolvedValueOnce([{ id: 'doc-2' }, { id: 'doc-3' }])
284+
285+
const req = createMockRequest('PATCH', {
286+
operation: 'exclude',
287+
documentIds: ['doc-2', 'doc-3'],
288+
})
289+
const { PATCH } = await import(
290+
'@/app/api/knowledge/[id]/connectors/[connectorId]/documents/route'
291+
)
292+
const response = await PATCH(req as never, { params: mockParams })
293+
const data = await response.json()
294+
295+
expect(response.status).toBe(200)
296+
expect(data.data.excludedCount).toBe(2)
297+
expect(data.data.documentIds).toEqual(['doc-2', 'doc-3'])
298+
})
299+
})
300+
})

0 commit comments

Comments
 (0)