mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
* creating boolean, number and date tags with different equality matchings * feat: add UI for tag field types with filter operators - Update base-tags-modal with field type selector dropdown - Update document-tags-modal with different input types per fieldType - Update knowledge-tag-filters with operator dropdown and type-specific inputs - Update search routes to support all tag slot types - Update hook to use AllTagSlot type * feat: add field type support to document-tag-entry component - Add dropdown with all field types (Text, Number, Date, Boolean) - Render different value inputs based on field type - Update slot counting to include all field types (28 total) * fix: resolve MAX_TAG_SLOTS error and z-index dropdown issue - Replace MAX_TAG_SLOTS with totalSlots in document-tag-entry - Add z-index to SelectContent in base-tags-modal for proper layering * fix: handle non-text columns in getTagUsage query - Only apply empty string check for text columns (tag1-tag7) - Numeric/date/boolean columns only check IS NOT NULL - Cast values to text for consistent output * refactor: use EMCN components for KB UI - Replace @/components/ui imports with @/components/emcn - Use Combobox instead of Select for dropdowns - Use EMCN Switch, Button, Input, Label components - Remove unsupported 'size' prop from EMCN Button * fix: layout for delete button next to date picker - Change delete button from absolute to inline positioning - Add proper column width (w-10) for delete button - Add empty header cell for delete column - Apply fix to both document-tag-entry and knowledge-tag-filters * fix: clear value when switching tag field type - Reset value to empty when changing type (e.g., boolean to text) - Reset value when tag name changes and type differs - Prevents 'true'/'false' from sticking in text inputs * feat: add full support for number/date/boolean tag filtering in KB search - Copy all tag types (number, date, boolean) from document to embedding records - Update processDocumentTags to handle all field types with proper type conversion - Add number/date/boolean columns to document queries in checkDocumentWriteAccess - Update chunk creation to inherit all tag types from parent document - Add getSearchResultFields helper for consistent query result selection - Support structured filters with operators (eq, gt, lt, between, etc.) - Fix search queries to include all 28 tag fields in results * fixing tags import issue * fix rm file * reduced number to 3 and date to 2 * fixing lint * fixed the prop size issue * increased number from 3 to 5 and boolean from 7 to 2 * fixed number the sql stuff * progress * fix document tag and kb tag modals * update datepicker emcn component * fix ui * progress on KB block tags UI * fix issues with date filters * fix execution parsing of types for KB tags * remove migration before merge * regen migrations * fix tests and types * address greptile comments * fix more greptile comments * fix filtering logic for multiple of same row * fix tests --------- Co-authored-by: priyanshu.solanki <priyanshu.solanki@saviynt.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
595 lines
21 KiB
TypeScript
595 lines
21 KiB
TypeScript
/**
|
|
* Tests for document by ID API route
|
|
*
|
|
* @vitest-environment node
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
createMockRequest,
|
|
mockAuth,
|
|
mockConsoleLogger,
|
|
mockDrizzleOrm,
|
|
mockKnowledgeSchemas,
|
|
} from '@/app/api/__test-utils__/utils'
|
|
|
|
mockKnowledgeSchemas()
|
|
|
|
vi.mock('@/app/api/knowledge/utils', () => ({
|
|
checkKnowledgeBaseAccess: vi.fn(),
|
|
checkKnowledgeBaseWriteAccess: vi.fn(),
|
|
checkDocumentAccess: vi.fn(),
|
|
checkDocumentWriteAccess: vi.fn(),
|
|
checkChunkAccess: vi.fn(),
|
|
generateEmbeddings: vi.fn(),
|
|
processDocumentAsync: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/lib/knowledge/documents/service', () => ({
|
|
updateDocument: vi.fn(),
|
|
deleteDocument: vi.fn(),
|
|
markDocumentAsFailedTimeout: vi.fn(),
|
|
retryDocumentProcessing: vi.fn(),
|
|
processDocumentAsync: vi.fn(),
|
|
}))
|
|
|
|
mockDrizzleOrm()
|
|
mockConsoleLogger()
|
|
|
|
describe('Document By ID API Route', () => {
|
|
const mockAuth$ = mockAuth()
|
|
|
|
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(),
|
|
delete: vi.fn().mockReturnThis(),
|
|
transaction: vi.fn(),
|
|
}
|
|
|
|
const mockDocument = {
|
|
id: 'doc-123',
|
|
knowledgeBaseId: 'kb-123',
|
|
filename: 'test-document.pdf',
|
|
fileUrl: 'https://example.com/test-document.pdf',
|
|
fileSize: 1024,
|
|
mimeType: 'application/pdf',
|
|
chunkCount: 5,
|
|
tokenCount: 100,
|
|
characterCount: 500,
|
|
processingStatus: 'completed' as const,
|
|
processingStartedAt: new Date('2023-01-01T10:00:00Z'),
|
|
processingCompletedAt: new Date('2023-01-01T10:05:00Z'),
|
|
processingError: null,
|
|
enabled: true,
|
|
uploadedAt: new Date('2023-01-01T09:00:00Z'),
|
|
tag1: null,
|
|
tag2: null,
|
|
tag3: null,
|
|
tag4: null,
|
|
tag5: null,
|
|
tag6: null,
|
|
tag7: null,
|
|
number1: null,
|
|
number2: null,
|
|
number3: null,
|
|
number4: null,
|
|
number5: null,
|
|
date1: null,
|
|
date2: null,
|
|
boolean1: null,
|
|
boolean2: null,
|
|
boolean3: null,
|
|
deletedAt: null,
|
|
}
|
|
|
|
const resetMocks = () => {
|
|
vi.clearAllMocks()
|
|
Object.values(mockDbChain).forEach((fn) => {
|
|
if (typeof fn === 'function') {
|
|
fn.mockClear().mockReset()
|
|
if (fn !== mockDbChain.transaction) {
|
|
fn.mockReturnThis()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
resetMocks()
|
|
|
|
vi.doMock('@sim/db', () => ({
|
|
db: mockDbChain,
|
|
}))
|
|
|
|
vi.stubGlobal('crypto', {
|
|
randomUUID: vi.fn().mockReturnValue('mock-uuid-1234-5678'),
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('GET /api/knowledge/[id]/documents/[documentId]', () => {
|
|
const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' })
|
|
|
|
it('should retrieve document successfully for authenticated user', async () => {
|
|
const { checkDocumentAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: mockDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
const req = createMockRequest('GET')
|
|
const { GET } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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('doc-123')
|
|
expect(data.data.filename).toBe('test-document.pdf')
|
|
expect(vi.mocked(checkDocumentAccess)).toHaveBeenCalledWith('kb-123', 'doc-123', 'user-123')
|
|
})
|
|
|
|
it('should return unauthorized for unauthenticated user', async () => {
|
|
mockAuth$.mockUnauthenticated()
|
|
|
|
const req = createMockRequest('GET')
|
|
const { GET } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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 document', async () => {
|
|
const { checkDocumentAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentAccess).mockResolvedValue({
|
|
hasAccess: false,
|
|
notFound: true,
|
|
reason: 'Document not found',
|
|
})
|
|
|
|
const req = createMockRequest('GET')
|
|
const { GET } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await GET(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(404)
|
|
expect(data.error).toBe('Document not found')
|
|
})
|
|
|
|
it('should return unauthorized for document without access', async () => {
|
|
const { checkDocumentAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentAccess).mockResolvedValue({
|
|
hasAccess: false,
|
|
reason: 'Access denied',
|
|
})
|
|
|
|
const req = createMockRequest('GET')
|
|
const { GET } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await GET(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(401)
|
|
expect(data.error).toBe('Unauthorized')
|
|
})
|
|
|
|
it('should handle database errors', async () => {
|
|
const { checkDocumentAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentAccess).mockRejectedValue(new Error('Database error'))
|
|
|
|
const req = createMockRequest('GET')
|
|
const { GET } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await GET(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(500)
|
|
expect(data.error).toBe('Failed to fetch document')
|
|
})
|
|
})
|
|
|
|
describe('PUT /api/knowledge/[id]/documents/[documentId] - Regular Updates', () => {
|
|
const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' })
|
|
const validUpdateData = {
|
|
filename: 'updated-document.pdf',
|
|
enabled: false,
|
|
chunkCount: 10,
|
|
tokenCount: 200,
|
|
}
|
|
|
|
it('should update document successfully', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
const { updateDocument } = await import('@/lib/knowledge/documents/service')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: mockDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
const updatedDocument = {
|
|
...mockDocument,
|
|
...validUpdateData,
|
|
deletedAt: null,
|
|
}
|
|
vi.mocked(updateDocument).mockResolvedValue(updatedDocument)
|
|
|
|
const req = createMockRequest('PUT', validUpdateData)
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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.filename).toBe('updated-document.pdf')
|
|
expect(data.data.enabled).toBe(false)
|
|
expect(vi.mocked(updateDocument)).toHaveBeenCalledWith(
|
|
'doc-123',
|
|
validUpdateData,
|
|
expect.any(String)
|
|
)
|
|
})
|
|
|
|
it('should validate update data', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: mockDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
const invalidData = {
|
|
filename: '', // Invalid: empty filename
|
|
chunkCount: -1, // Invalid: negative count
|
|
processingStatus: 'invalid', // Invalid: not in enum
|
|
}
|
|
|
|
const req = createMockRequest('PUT', invalidData)
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await PUT(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(400)
|
|
expect(data.error).toBe('Invalid request data')
|
|
expect(data.details).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('PUT /api/knowledge/[id]/documents/[documentId] - Mark Failed Due to Timeout', () => {
|
|
const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' })
|
|
|
|
it('should mark document as failed due to timeout successfully', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
const { markDocumentAsFailedTimeout } = await import('@/lib/knowledge/documents/service')
|
|
|
|
const processingDocument = {
|
|
...mockDocument,
|
|
processingStatus: 'processing',
|
|
processingStartedAt: new Date(Date.now() - 200000), // 200 seconds ago
|
|
}
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: processingDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
vi.mocked(markDocumentAsFailedTimeout).mockResolvedValue({
|
|
success: true,
|
|
processingDuration: 200000,
|
|
})
|
|
|
|
const req = createMockRequest('PUT', { markFailedDueToTimeout: true })
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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.documentId).toBe('doc-123')
|
|
expect(data.data.status).toBe('failed')
|
|
expect(data.data.message).toBe('Document marked as failed due to timeout')
|
|
expect(vi.mocked(markDocumentAsFailedTimeout)).toHaveBeenCalledWith(
|
|
'doc-123',
|
|
processingDocument.processingStartedAt,
|
|
expect.any(String)
|
|
)
|
|
})
|
|
|
|
it('should reject marking failed for non-processing document', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: { ...mockDocument, processingStatus: 'completed' },
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
const req = createMockRequest('PUT', { markFailedDueToTimeout: true })
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await PUT(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(400)
|
|
expect(data.error).toContain('Document is not in processing state')
|
|
})
|
|
|
|
it('should reject marking failed for recently started processing', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
const { markDocumentAsFailedTimeout } = await import('@/lib/knowledge/documents/service')
|
|
|
|
const recentProcessingDocument = {
|
|
...mockDocument,
|
|
processingStatus: 'processing',
|
|
processingStartedAt: new Date(Date.now() - 60000), // 60 seconds ago
|
|
}
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: recentProcessingDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
vi.mocked(markDocumentAsFailedTimeout).mockRejectedValue(
|
|
new Error('Document has not been processing long enough to be considered dead')
|
|
)
|
|
|
|
const req = createMockRequest('PUT', { markFailedDueToTimeout: true })
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await PUT(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(400)
|
|
expect(data.error).toContain('Document has not been processing long enough')
|
|
})
|
|
})
|
|
|
|
describe('PUT /api/knowledge/[id]/documents/[documentId] - Retry Processing', () => {
|
|
const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' })
|
|
|
|
it('should retry processing successfully', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
const { retryDocumentProcessing } = await import('@/lib/knowledge/documents/service')
|
|
|
|
const failedDocument = {
|
|
...mockDocument,
|
|
processingStatus: 'failed',
|
|
processingError: 'Previous processing failed',
|
|
}
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: failedDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
vi.mocked(retryDocumentProcessing).mockResolvedValue({
|
|
success: true,
|
|
status: 'pending',
|
|
message: 'Document retry processing started',
|
|
})
|
|
|
|
const req = createMockRequest('PUT', { retryProcessing: true })
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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.status).toBe('pending')
|
|
expect(data.data.message).toBe('Document retry processing started')
|
|
expect(vi.mocked(retryDocumentProcessing)).toHaveBeenCalledWith(
|
|
'kb-123',
|
|
'doc-123',
|
|
{
|
|
filename: failedDocument.filename,
|
|
fileUrl: failedDocument.fileUrl,
|
|
fileSize: failedDocument.fileSize,
|
|
mimeType: failedDocument.mimeType,
|
|
},
|
|
expect.any(String)
|
|
)
|
|
})
|
|
|
|
it('should reject retry for non-failed document', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: { ...mockDocument, processingStatus: 'completed' },
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
const req = createMockRequest('PUT', { retryProcessing: true })
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await PUT(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(400)
|
|
expect(data.error).toBe('Document is not in failed state')
|
|
})
|
|
})
|
|
|
|
describe('PUT /api/knowledge/[id]/documents/[documentId] - Authentication & Authorization', () => {
|
|
const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' })
|
|
const validUpdateData = { filename: 'updated-document.pdf' }
|
|
|
|
it('should return unauthorized for unauthenticated user', async () => {
|
|
mockAuth$.mockUnauthenticated()
|
|
|
|
const req = createMockRequest('PUT', validUpdateData)
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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 document', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: false,
|
|
notFound: true,
|
|
reason: 'Document not found',
|
|
})
|
|
|
|
const req = createMockRequest('PUT', validUpdateData)
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await PUT(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(404)
|
|
expect(data.error).toBe('Document not found')
|
|
})
|
|
|
|
it('should handle database errors during update', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
const { updateDocument } = await import('@/lib/knowledge/documents/service')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: mockDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
vi.mocked(updateDocument).mockRejectedValue(new Error('Database error'))
|
|
|
|
const req = createMockRequest('PUT', validUpdateData)
|
|
const { PUT } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await PUT(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(500)
|
|
expect(data.error).toBe('Failed to update document')
|
|
})
|
|
})
|
|
|
|
describe('DELETE /api/knowledge/[id]/documents/[documentId]', () => {
|
|
const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' })
|
|
|
|
it('should delete document successfully', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
const { deleteDocument } = await import('@/lib/knowledge/documents/service')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: mockDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
|
|
vi.mocked(deleteDocument).mockResolvedValue({
|
|
success: true,
|
|
message: 'Document deleted successfully',
|
|
})
|
|
|
|
const req = createMockRequest('DELETE')
|
|
const { DELETE } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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('Document deleted successfully')
|
|
expect(vi.mocked(deleteDocument)).toHaveBeenCalledWith('doc-123', expect.any(String))
|
|
})
|
|
|
|
it('should return unauthorized for unauthenticated user', async () => {
|
|
mockAuth$.mockUnauthenticated()
|
|
|
|
const req = createMockRequest('DELETE')
|
|
const { DELETE } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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 document', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: false,
|
|
notFound: true,
|
|
reason: 'Document not found',
|
|
})
|
|
|
|
const req = createMockRequest('DELETE')
|
|
const { DELETE } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await DELETE(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(404)
|
|
expect(data.error).toBe('Document not found')
|
|
})
|
|
|
|
it('should return unauthorized for document without access', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: false,
|
|
reason: 'Access denied',
|
|
})
|
|
|
|
const req = createMockRequest('DELETE')
|
|
const { DELETE } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
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 deletion', async () => {
|
|
const { checkDocumentWriteAccess } = await import('@/app/api/knowledge/utils')
|
|
const { deleteDocument } = await import('@/lib/knowledge/documents/service')
|
|
|
|
mockAuth$.mockAuthenticatedUser()
|
|
vi.mocked(checkDocumentWriteAccess).mockResolvedValue({
|
|
hasAccess: true,
|
|
document: mockDocument,
|
|
knowledgeBase: { id: 'kb-123', userId: 'user-123' },
|
|
})
|
|
vi.mocked(deleteDocument).mockRejectedValue(new Error('Database error'))
|
|
|
|
const req = createMockRequest('DELETE')
|
|
const { DELETE } = await import('@/app/api/knowledge/[id]/documents/[documentId]/route')
|
|
const response = await DELETE(req, { params: mockParams })
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(500)
|
|
expect(data.error).toBe('Failed to delete document')
|
|
})
|
|
})
|
|
})
|