Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 41 additions & 88 deletions apps/sim/app/api/folders/[id]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ import {
auditMock,
authMockFns,
createMockRequest,
dbChainMock,
dbChainMockFns,
type MockUser,
permissionsMock,
permissionsMockFns,
queueTableRows,
resetDbChainMock,
schemaMock,
workflowsOrchestrationMock,
workflowsOrchestrationMockFns,
workflowsUtilsMock,
workflowsUtilsMockFns,
} from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'

const { mockLogger, mockDbRef } = vi.hoisted(() => {
const { mockLogger } = vi.hoisted(() => {
const logger = {
info: vi.fn(),
warn: vi.fn(),
Expand All @@ -29,7 +34,6 @@ const { mockLogger, mockDbRef } = vi.hoisted(() => {
}
return {
mockLogger: logger,
mockDbRef: { current: null as any },
}
})

Expand All @@ -45,23 +49,12 @@ vi.mock('@sim/logger', () => ({
getRequestContext: () => undefined,
}))
vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)
vi.mock('@sim/db', () => ({
get db() {
return mockDbRef.current
},
}))
vi.mock('@sim/db', () => dbChainMock)
vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)
vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)

import { DELETE, PUT } from '@/app/api/folders/[id]/route'

interface FolderDbMockOptions {
folderLookupResult?: any
updateResult?: any[]
throwError?: boolean
circularCheckResults?: any[]
}

const TEST_USER: MockUser = {
id: 'user-123',
email: 'test@example.com',
Expand All @@ -80,57 +73,16 @@ const mockFolder = {
updatedAt: new Date('2024-01-01T00:00:00Z'),
}

function createFolderDbMock(options: FolderDbMockOptions = {}) {
const {
folderLookupResult = mockFolder,
updateResult = [{ ...mockFolder, name: 'Updated Folder' }],
throwError = false,
circularCheckResults = [],
} = options

let callCount = 0

const mockSelect = vi.fn().mockImplementation(() => ({
from: vi.fn().mockImplementation(() => ({
where: vi.fn().mockImplementation(() => ({
then: vi.fn().mockImplementation((callback) => {
if (throwError) {
throw new Error('Database error')
}

callCount++
if (callCount === 1) {
const result = folderLookupResult === undefined ? [] : [folderLookupResult]
return Promise.resolve(callback(result))
}
if (callCount > 1 && circularCheckResults.length > 0) {
const index = callCount - 2
const result = circularCheckResults[index] ? [circularCheckResults[index]] : []
return Promise.resolve(callback(result))
}
return Promise.resolve(callback([]))
}),
})),
})),
}))

const mockUpdate = vi.fn().mockImplementation(() => ({
set: vi.fn().mockImplementation(() => ({
where: vi.fn().mockImplementation(() => ({
returning: vi.fn().mockReturnValue(updateResult),
})),
})),
}))

const mockDelete = vi.fn().mockImplementation(() => ({
where: vi.fn().mockImplementation(() => Promise.resolve()),
}))
/** Queues the folder-existence lookup the route runs before authorizing. */
function queueFolderLookup(folder: Record<string, unknown> = mockFolder) {
queueTableRows(schemaMock.workflowFolder, [folder])
}

return {
select: mockSelect,
update: mockUpdate,
delete: mockDelete,
}
/** Makes the next folder lookup throw, exercising the route's 500 path. */
function failFolderLookup() {
dbChainMockFns.where.mockImplementationOnce(() => {
throw new Error('Database error')
})
}

function mockAuthenticatedUser(user?: MockUser) {
Expand All @@ -142,11 +94,15 @@ function mockUnauthenticated() {
}

describe('Individual Folder API Route', () => {
afterAll(() => {
resetDbChainMock()
})

beforeEach(() => {
vi.clearAllMocks()
resetDbChainMock()

mockGetUserEntityPermissions.mockResolvedValue('admin')
mockDbRef.current = createFolderDbMock()
mockPerformDeleteFolder.mockResolvedValue({
success: true,
deletedItems: { folders: 1, workflows: 0 },
Expand Down Expand Up @@ -193,6 +149,7 @@ describe('Individual Folder API Route', () => {
it('should update folder successfully', async () => {
mockAuthenticatedUser()

queueFolderLookup()
const req = createMockRequest('PUT', {
name: 'Updated Folder Name',
color: '#FF0000',
Expand All @@ -213,6 +170,7 @@ describe('Individual Folder API Route', () => {
it('should update parent folder successfully', async () => {
mockAuthenticatedUser()

queueFolderLookup()
const req = createMockRequest('PUT', {
name: 'Updated Folder',
parentId: 'parent-folder-1',
Expand Down Expand Up @@ -244,6 +202,7 @@ describe('Individual Folder API Route', () => {
mockAuthenticatedUser()
mockGetUserEntityPermissions.mockResolvedValue('read')

queueFolderLookup()
const req = createMockRequest('PUT', {
name: 'Updated Folder',
})
Expand All @@ -261,6 +220,7 @@ describe('Individual Folder API Route', () => {
mockAuthenticatedUser()
mockGetUserEntityPermissions.mockResolvedValue('write')

queueFolderLookup()
const req = createMockRequest('PUT', {
name: 'Updated Folder',
})
Expand All @@ -278,6 +238,7 @@ describe('Individual Folder API Route', () => {
mockAuthenticatedUser()
mockGetUserEntityPermissions.mockResolvedValue('admin')

queueFolderLookup()
const req = createMockRequest('PUT', {
name: 'Updated Folder',
})
Expand All @@ -294,6 +255,7 @@ describe('Individual Folder API Route', () => {
it('should return 400 when trying to set folder as its own parent', async () => {
mockAuthenticatedUser()

queueFolderLookup()
const req = createMockRequest('PUT', {
name: 'Updated Folder',
parentId: 'folder-1',
Expand All @@ -311,6 +273,7 @@ describe('Individual Folder API Route', () => {
it('should trim folder name when updating', async () => {
mockAuthenticatedUser()

queueFolderLookup()
const req = createMockRequest('PUT', {
name: ' Folder With Spaces ',
})
Expand All @@ -325,9 +288,7 @@ describe('Individual Folder API Route', () => {
it('should handle database errors gracefully', async () => {
mockAuthenticatedUser()

mockDbRef.current = createFolderDbMock({
throwError: true,
})
failFolderLookup()

const req = createMockRequest('PUT', {
name: 'Updated Folder',
Expand All @@ -350,6 +311,7 @@ describe('Individual Folder API Route', () => {
it('should handle empty folder name', async () => {
mockAuthenticatedUser()

queueFolderLookup()
const req = createMockRequest('PUT', {
name: '',
})
Expand Down Expand Up @@ -383,13 +345,11 @@ describe('Individual Folder API Route', () => {
it('should prevent circular references when updating parent', async () => {
mockAuthenticatedUser()

mockDbRef.current = createFolderDbMock({
folderLookupResult: {
id: 'folder-3',
parentId: null,
name: 'Folder 3',
workspaceId: 'workspace-123',
},
queueFolderLookup({
id: 'folder-3',
parentId: null,
name: 'Folder 3',
workspaceId: 'workspace-123',
})

workflowsUtilsMockFns.mockCheckForCircularReference.mockResolvedValue(true)
Expand Down Expand Up @@ -417,9 +377,7 @@ describe('Individual Folder API Route', () => {
it('should delete folder and all contents successfully', async () => {
mockAuthenticatedUser()

mockDbRef.current = createFolderDbMock({
folderLookupResult: mockFolder,
})
queueFolderLookup()

const req = createMockRequest('DELETE')
const params = Promise.resolve({ id: 'folder-1' })
Expand Down Expand Up @@ -457,6 +415,7 @@ describe('Individual Folder API Route', () => {
mockAuthenticatedUser()
mockGetUserEntityPermissions.mockResolvedValue('read')

queueFolderLookup()
const req = createMockRequest('DELETE')
const params = Promise.resolve({ id: 'folder-1' })

Expand All @@ -472,9 +431,7 @@ describe('Individual Folder API Route', () => {
mockAuthenticatedUser()
mockGetUserEntityPermissions.mockResolvedValue('write')

mockDbRef.current = createFolderDbMock({
folderLookupResult: mockFolder,
})
queueFolderLookup()

const req = createMockRequest('DELETE')
const params = Promise.resolve({ id: 'folder-1' })
Expand All @@ -492,9 +449,7 @@ describe('Individual Folder API Route', () => {
mockAuthenticatedUser()
mockGetUserEntityPermissions.mockResolvedValue('admin')

mockDbRef.current = createFolderDbMock({
folderLookupResult: mockFolder,
})
queueFolderLookup()

const req = createMockRequest('DELETE')
const params = Promise.resolve({ id: 'folder-1' })
Expand All @@ -511,9 +466,7 @@ describe('Individual Folder API Route', () => {
it('should handle database errors during deletion', async () => {
mockAuthenticatedUser()

mockDbRef.current = createFolderDbMock({
throwError: true,
})
failFolderLookup()

const req = createMockRequest('DELETE')
const params = Promise.resolve({ id: 'folder-1' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,17 @@
*
* @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(),
delete: vi.fn().mockReturnThis(),
transaction: vi.fn(),
}
return { mockDbChain }
})
import {
auditMock,
authMockFns,
createMockRequest,
dbChainMock,
knowledgeApiUtilsMock,
resetDbChainMock,
} from '@sim/testing'
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('@sim/db', () => ({
db: mockDbChain,
}))
vi.mock('@sim/db', () => dbChainMock)

vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)

Expand Down Expand Up @@ -82,20 +73,9 @@ describe('Document By ID API Route', () => {
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(() => {
resetMocks()
vi.clearAllMocks()
resetDbChainMock()

vi.stubGlobal('crypto', {
randomUUID: vi.fn().mockReturnValue('mock-uuid-1234-5678'),
Expand All @@ -106,6 +86,10 @@ describe('Document By ID API Route', () => {
vi.clearAllMocks()
})

afterAll(() => {
resetDbChainMock()
})

describe('GET /api/knowledge/[id]/documents/[documentId]', () => {
const mockParams = Promise.resolve({ id: 'kb-123', documentId: 'doc-123' })

Expand Down
Loading
Loading