import { vi } from 'vitest'; import { setActivePinia, createPinia } from 'pinia'; import { faker } from '@faker-js/faker'; import { useFoldersStore } from '@/stores/folders.store'; import * as workflowsApi from '@/api/workflows'; import type { ChangeLocationSearchResponseItem, IUsedCredential } from '@/Interface'; import { useRootStore } from '@n8n/stores/useRootStore'; vi.mock('@/utils/apiUtils', () => ({ makeRestApiRequest: vi.fn(), })); const createFolder = ( overrides: Partial = {}, ): ChangeLocationSearchResponseItem => ({ createdAt: faker.date.recent().toISOString(), updatedAt: faker.date.recent().toISOString(), id: faker.string.alphanumeric(10), name: faker.lorem.words(3), tags: [], parentFolder: { id: faker.string.alphanumeric(10), name: faker.lorem.words(2), parentFolderId: null, }, workflowCount: 2, subFolderCount: 2, path: [faker.lorem.word(), faker.lorem.word()], ...overrides, }); describe('folders.store', () => { let foldersStore: ReturnType; let rootStore: ReturnType; beforeEach(() => { setActivePinia(createPinia()); rootStore = useRootStore(); foldersStore = useFoldersStore(); }); describe('fetchFoldersAvailableForMove', () => { const projectId = faker.string.alphanumeric(10); const folderId = faker.string.alphanumeric(10); const selectFields = [ 'id', 'name', 'createdAt', 'updatedAt', 'project', 'tags', 'parentFolder', 'workflowCount', 'subFolderCount', 'path', ]; it('should fetch folders in a single page, empty filter', async () => { const folder = createFolder(); vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({ count: 1, data: [folder], }); const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId); expect(available).toEqual([{ ...folder, resource: 'folder' }]); expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(1); expect(workflowsApi.getProjectFolders).toHaveBeenCalledWith( rootStore.restApiContext, projectId, { skip: 0, take: 100, sortBy: 'updatedAt:desc', }, { excludeFolderIdAndDescendants: folderId, }, selectFields, ); }); it('should fetch folders in a single page with filter', async () => { const folder = createFolder({ name: 'Test Folder' }); vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({ count: 1, data: [folder], }); const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId, { name: 'Test', }); expect(available).toEqual([{ ...folder, resource: 'folder' }]); expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(1); expect(workflowsApi.getProjectFolders).toHaveBeenCalledWith( rootStore.restApiContext, projectId, { skip: 0, take: 100, sortBy: 'updatedAt:desc', }, { excludeFolderIdAndDescendants: folderId, name: 'Test', }, selectFields, ); }); it('should fetch folders in multiple pages', async () => { const folders = Array.from({ length: 150 }, (_, i) => createFolder({ name: `Folder ${i + 1}` }), ); vi.spyOn(workflowsApi, 'getProjectFolders') .mockResolvedValueOnce({ count: 150, data: folders.slice(0, 100), }) .mockResolvedValueOnce({ count: 150, data: folders.slice(100), }); const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId, { name: 'Test', }); expect(available).toHaveLength(150); expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(2); expect(workflowsApi.getProjectFolders).toHaveBeenNthCalledWith( 1, rootStore.restApiContext, projectId, { skip: 0, take: 100, sortBy: 'updatedAt:desc', }, { excludeFolderIdAndDescendants: folderId, name: 'Test', }, selectFields, ); expect(workflowsApi.getProjectFolders).toHaveBeenNthCalledWith( 2, rootStore.restApiContext, projectId, { skip: 100, take: 100, sortBy: 'updatedAt:desc', }, { excludeFolderIdAndDescendants: folderId, name: 'Test', }, selectFields, ); }); it('should cache the results on breadcrumbs cache', async () => { const folder = createFolder(); vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({ count: 1, data: [folder], }); const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId); expect(available).toEqual([{ ...folder, resource: 'folder' }]); expect(foldersStore.breadcrumbsCache).toEqual({ [folder.id]: { id: folder.id, name: folder.name, parentFolder: folder.parentFolder?.id, }, }); }); }); describe('fetchFolderUsedCredentials', () => { const projectId = faker.string.alphanumeric(10); const folderId = faker.string.alphanumeric(10); it('should fetch credentials used within a folder', async () => { const usedCredential: IUsedCredential = { id: faker.string.alphanumeric(10), name: faker.lorem.words(2), credentialType: faker.lorem.word(), currentUserHasAccess: true, sharedWithProjects: [], }; vi.spyOn(workflowsApi, 'getFolderUsedCredentials').mockResolvedValue([usedCredential]); const credentials = await foldersStore.fetchFolderUsedCredentials(projectId, folderId); expect(credentials).toEqual([usedCredential]); expect(workflowsApi.getFolderUsedCredentials).toHaveBeenCalledWith( rootStore.restApiContext, projectId, folderId, ); }); }); });