/** * Task 3 - document-folders service: listTree + createFolder (TDD). * Task 4 - renameFolder + moveFolder (TDD). * * Uses the makePort factory (not a "setupTestPort" helper - that name * doesn't exist in this codebase). TEST_USER_ID is resolved once via * beforeAll from any seeded user, matching the pattern in * alerts-tenant-isolation.test.ts and gdpr-export.test.ts. */ import { describe, it, expect, beforeAll, beforeEach } from 'vitest'; import { eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { documentFolders } from '@/lib/db/schema/documents'; import { user } from '@/lib/db/schema/users'; import { listTree, createFolder, renameFolder, moveFolder, } from '@/lib/services/document-folders.service'; import { makePort } from '../helpers/factories'; let TEST_USER_ID = ''; beforeAll(async () => { const [u] = await db.select({ id: user.id }).from(user).limit(1); if (!u) throw new Error('No user available; run pnpm db:seed first'); TEST_USER_ID = u.id; }); describe('document-folders service · listTree', () => { let portId: string; beforeEach(async () => { const port = await makePort(); portId = port.id; await db.delete(documentFolders).where(eq(documentFolders.portId, portId)); }); it('returns an empty array when no folders exist', async () => { const tree = await listTree(portId); expect(tree).toEqual([]); }); it('returns root folders with children nested under them', async () => { const root = await createFolder(portId, TEST_USER_ID, { name: 'Deals 2026', parentId: null }); const child = await createFolder(portId, TEST_USER_ID, { name: 'Q1', parentId: root.id, }); const tree = await listTree(portId); expect(tree).toHaveLength(1); expect(tree[0]?.id).toBe(root.id); expect(tree[0]?.children).toHaveLength(1); expect(tree[0]?.children[0]?.id).toBe(child.id); }); it('only returns folders for the requested port', async () => { const otherPort = await makePort(); await createFolder(otherPort.id, TEST_USER_ID, { name: 'Other Port', parentId: null }); const tree = await listTree(portId); expect(tree).toEqual([]); }); }); describe('document-folders service · createFolder unique-sibling guard', () => { let portId: string; beforeEach(async () => { const port = await makePort(); portId = port.id; await db.delete(documentFolders).where(eq(documentFolders.portId, portId)); }); it('rejects a duplicate sibling name (case-insensitive)', async () => { await createFolder(portId, TEST_USER_ID, { name: 'Deals 2026', parentId: null }); await expect( createFolder(portId, TEST_USER_ID, { name: 'deals 2026', parentId: null }), ).rejects.toThrow(/already exists/i); }); it('allows the same name under different parents', async () => { const a = await createFolder(portId, TEST_USER_ID, { name: 'A', parentId: null }); const b = await createFolder(portId, TEST_USER_ID, { name: 'B', parentId: null }); await createFolder(portId, TEST_USER_ID, { name: 'Drafts', parentId: a.id }); await expect( createFolder(portId, TEST_USER_ID, { name: 'Drafts', parentId: b.id }), ).resolves.toBeDefined(); }); it('rejects a parentId from another port', async () => { const otherPort = await makePort(); const otherFolder = await createFolder(otherPort.id, TEST_USER_ID, { name: 'Other', parentId: null, }); await expect( createFolder(portId, TEST_USER_ID, { name: 'Should fail', parentId: otherFolder.id }), ).rejects.toThrow(/invalid parent/i); }); }); describe('document-folders service · renameFolder', () => { let portId: string; beforeEach(async () => { const port = await makePort(); portId = port.id; await db.delete(documentFolders).where(eq(documentFolders.portId, portId)); }); it('renames a folder and bumps updatedAt', async () => { const folder = await createFolder(portId, TEST_USER_ID, { name: 'Old', parentId: null }); const before = folder.updatedAt.getTime(); await new Promise((r) => setTimeout(r, 10)); const renamed = await renameFolder(portId, folder.id, 'New', TEST_USER_ID); expect(renamed.name).toBe('New'); expect(renamed.updatedAt.getTime()).toBeGreaterThan(before); }); it('rejects rename to an existing sibling name', async () => { await createFolder(portId, TEST_USER_ID, { name: 'Existing', parentId: null }); const folder = await createFolder(portId, TEST_USER_ID, { name: 'Mine', parentId: null }); await expect(renameFolder(portId, folder.id, 'Existing', TEST_USER_ID)).rejects.toThrow( /already exists/i, ); }); it('throws NotFound when the folder belongs to another port', async () => { const otherPort = await makePort(); const folder = await createFolder(otherPort.id, TEST_USER_ID, { name: 'X', parentId: null }); await expect(renameFolder(portId, folder.id, 'Y', TEST_USER_ID)).rejects.toThrow( /couldn't find/i, ); }); }); describe('document-folders service · moveFolder', () => { let portId: string; beforeEach(async () => { const port = await makePort(); portId = port.id; await db.delete(documentFolders).where(eq(documentFolders.portId, portId)); }); it('moves a folder under a new parent', async () => { const root = await createFolder(portId, TEST_USER_ID, { name: 'Root', parentId: null }); const orphan = await createFolder(portId, TEST_USER_ID, { name: 'Orphan', parentId: null }); const moved = await moveFolder(portId, orphan.id, root.id, TEST_USER_ID); expect(moved.parentId).toBe(root.id); }); it('moves a folder back to root with parentId=null', async () => { const root = await createFolder(portId, TEST_USER_ID, { name: 'Root', parentId: null }); const child = await createFolder(portId, TEST_USER_ID, { name: 'Child', parentId: root.id }); const moved = await moveFolder(portId, child.id, null, TEST_USER_ID); expect(moved.parentId).toBeNull(); }); it('rejects a move that would create a cycle', async () => { const a = await createFolder(portId, TEST_USER_ID, { name: 'A', parentId: null }); const b = await createFolder(portId, TEST_USER_ID, { name: 'B', parentId: a.id }); const c = await createFolder(portId, TEST_USER_ID, { name: 'C', parentId: b.id }); // moving A under C would create A → B → C → A await expect(moveFolder(portId, a.id, c.id, TEST_USER_ID)).rejects.toThrow(/cycle/i); }); it('rejects moving a folder under itself', async () => { const a = await createFolder(portId, TEST_USER_ID, { name: 'A', parentId: null }); await expect(moveFolder(portId, a.id, a.id, TEST_USER_ID)).rejects.toThrow(/cycle/i); }); });