/** * Tests for knowledge base by ID API route * * @vitest-environment node */ import { auditMock, authMockFns, createMockRequest, knowledgeApiUtilsMock } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { mockDbChain } = vi.hoisted(() => { const mockDbChain = { select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), update: vi.fn().mockReturnThis(), set: vi.fn().mockReturnThis(), } return { mockDbChain } }) vi.mock('@sim/db', () => ({ db: mockDbChain, })) vi.mock('@sim/audit', () => auditMock) vi.mock('@/lib/knowledge/service', async (importOriginal) => { const actual = await importOriginal() return { ...actual, getKnowledgeBaseById: vi.fn(), updateKnowledgeBase: vi.fn(), deleteKnowledgeBase: vi.fn(), KnowledgeBasePermissionError: actual.KnowledgeBasePermissionError, } }) vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) import { deleteKnowledgeBase, getKnowledgeBaseById, KnowledgeBasePermissionError, updateKnowledgeBase, } from '@/lib/knowledge/service' import { DELETE, GET, PUT } from '@/app/api/knowledge/[id]/route' import { checkKnowledgeBaseAccess, checkKnowledgeBaseWriteAccess } from '@/app/api/knowledge/utils' describe('Knowledge Base By ID API Route', () => { const mockKnowledgeBase = { id: 'kb-123', userId: 'user-123', name: 'Test Knowledge Base', description: 'Test description', tokenCount: 100, embeddingModel: 'text-embedding-3-small', embeddingDimension: 1536, chunkingConfig: { maxSize: 1024, minSize: 100, overlap: 200 }, createdAt: new Date(), updatedAt: new Date(), workspaceId: null, deletedAt: null, } const resetMocks = () => { vi.clearAllMocks() Object.values(mockDbChain).forEach((fn) => { if (typeof fn === 'function') { fn.mockClear().mockReset().mockReturnThis() } }) } beforeEach(() => { vi.clearAllMocks() vi.stubGlobal('crypto', { randomUUID: vi.fn().mockReturnValue('mock-uuid-1234-5678'), }) }) afterEach(() => { vi.clearAllMocks() }) describe('GET /api/knowledge/[id]', () => { const mockParams = Promise.resolve({ id: 'kb-123' }) it('should retrieve knowledge base successfully for authenticated user', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, }) vi.mocked(getKnowledgeBaseById).mockResolvedValueOnce(mockKnowledgeBase) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(200) expect(data.success).toBe(true) expect(data.data.id).toBe('kb-123') expect(data.data.name).toBe('Test Knowledge Base') expect(checkKnowledgeBaseAccess).toHaveBeenCalledWith('kb-123', 'user-123') expect(getKnowledgeBaseById).toHaveBeenCalledWith('kb-123') }) it('should return unauthorized for unauthenticated user', async () => { authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(401) expect(data.error).toBe('Unauthorized') }) it('should return not found for non-existent knowledge base', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValueOnce({ hasAccess: false, notFound: true, }) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(404) expect(data.error).toBe('Knowledge base not found') }) it('should return unauthorized for knowledge base owned by different user', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValueOnce({ hasAccess: false, notFound: false, }) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(401) expect(data.error).toBe('Unauthorized') }) it('should return not found when service returns null', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) vi.mocked(checkKnowledgeBaseAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, }) vi.mocked(getKnowledgeBaseById).mockResolvedValueOnce(null) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(404) expect(data.error).toBe('Knowledge base not found') }) it('should handle database errors', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) vi.mocked(checkKnowledgeBaseAccess).mockRejectedValueOnce(new Error('Database error')) const req = createMockRequest('GET') const response = await GET(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(500) expect(data.error).toBe('Failed to fetch knowledge base') }) }) describe('PUT /api/knowledge/[id]', () => { const mockParams = Promise.resolve({ id: 'kb-123' }) const validUpdateData = { name: 'Updated Knowledge Base', description: 'Updated description', } it('should update knowledge base successfully', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) resetMocks() vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, }) const updatedKnowledgeBase = { ...mockKnowledgeBase, ...validUpdateData } vi.mocked(updateKnowledgeBase).mockResolvedValueOnce(updatedKnowledgeBase) const req = createMockRequest('PUT', validUpdateData) const response = await PUT(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(200) expect(data.success).toBe(true) expect(data.data.name).toBe('Updated Knowledge Base') expect(checkKnowledgeBaseWriteAccess).toHaveBeenCalledWith('kb-123', 'user-123') expect(updateKnowledgeBase).toHaveBeenCalledWith( 'kb-123', { name: validUpdateData.name, description: validUpdateData.description, workspaceId: undefined, chunkingConfig: undefined, }, expect.any(String), { actorUserId: 'user-123' } ) }) it('returns 403 when service rejects a cross-workspace transfer', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'attacker', email: 'a@example.com' }, }) resetMocks() vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123', workspaceId: 'ws-current' }, }) vi.mocked(updateKnowledgeBase).mockRejectedValueOnce( new KnowledgeBasePermissionError('User does not have permission on the target workspace') ) const req = createMockRequest('PUT', { workspaceId: 'ws-target' }) const response = await PUT(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toBe('User does not have permission on the target workspace') }) it('returns 403 when service rejects clearing workspaceId', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) resetMocks() vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123', workspaceId: 'ws-current' }, }) vi.mocked(updateKnowledgeBase).mockRejectedValueOnce( new KnowledgeBasePermissionError('Knowledge base workspace cannot be cleared') ) const req = createMockRequest('PUT', { workspaceId: null }) const response = await PUT(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toBe('Knowledge base workspace cannot be cleared') }) it('should return unauthorized for unauthenticated user', async () => { authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('PUT', validUpdateData) const response = await PUT(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(401) expect(data.error).toBe('Unauthorized') }) it('should return not found for non-existent knowledge base', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) resetMocks() vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: false, notFound: true, }) const req = createMockRequest('PUT', validUpdateData) const response = await PUT(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(404) expect(data.error).toBe('Knowledge base not found') }) it('should validate update data', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) resetMocks() vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, }) const invalidData = { name: '', } const req = createMockRequest('PUT', invalidData) const response = await PUT(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(400) expect(data.error).toBe('Validation error') expect(data.details).toBeDefined() }) it('should handle database errors during update', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, }) vi.mocked(updateKnowledgeBase).mockRejectedValueOnce(new Error('Database error')) const req = createMockRequest('PUT', validUpdateData) const response = await PUT(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(500) expect(data.error).toBe('Failed to update knowledge base') }) }) describe('DELETE /api/knowledge/[id]', () => { const mockParams = Promise.resolve({ id: 'kb-123' }) it('should delete knowledge base successfully', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) resetMocks() vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, }) vi.mocked(deleteKnowledgeBase).mockResolvedValueOnce(undefined) const req = createMockRequest('DELETE') const response = await DELETE(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(200) expect(data.success).toBe(true) expect(data.data.message).toBe('Knowledge base deleted successfully') expect(checkKnowledgeBaseWriteAccess).toHaveBeenCalledWith('kb-123', 'user-123') expect(deleteKnowledgeBase).toHaveBeenCalledWith('kb-123', expect.any(String)) }) it('should return unauthorized for unauthenticated user', async () => { authMockFns.mockGetSession.mockResolvedValue(null) const req = createMockRequest('DELETE') const response = await DELETE(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(401) expect(data.error).toBe('Unauthorized') }) it('should return not found for non-existent knowledge base', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) resetMocks() vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: false, notFound: true, }) const req = createMockRequest('DELETE') const response = await DELETE(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(404) expect(data.error).toBe('Knowledge base not found') }) it('should return unauthorized for knowledge base owned by different user', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) resetMocks() vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: false, notFound: false, }) const req = createMockRequest('DELETE') const response = await DELETE(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(401) expect(data.error).toBe('Unauthorized') }) it('should handle database errors during delete', async () => { authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-123', email: 'test@example.com' }, }) vi.mocked(checkKnowledgeBaseWriteAccess).mockResolvedValueOnce({ hasAccess: true, knowledgeBase: { id: 'kb-123', userId: 'user-123' }, }) vi.mocked(deleteKnowledgeBase).mockRejectedValueOnce(new Error('Database error')) const req = createMockRequest('DELETE') const response = await DELETE(req, { params: mockParams }) const data = await response.json() expect(response.status).toBe(500) expect(data.error).toBe('Failed to delete knowledge base') }) }) })