/** * DrizzleTaskRepository Tests * * Tests for the Task repository adapter. */ import { describe, it, expect, beforeEach } from 'vitest'; import { DrizzleTaskRepository } from './task.js'; import { DrizzlePhaseRepository } from './phase.js'; import { DrizzleInitiativeRepository } from './initiative.js'; import { createTestDatabase } from './test-helpers.js'; import type { DrizzleDatabase } from '../../index.js'; describe('DrizzleTaskRepository', () => { let db: DrizzleDatabase; let taskRepo: DrizzleTaskRepository; let phaseRepo: DrizzlePhaseRepository; let initiativeRepo: DrizzleInitiativeRepository; let testPhaseId: string; beforeEach(async () => { db = createTestDatabase(); taskRepo = new DrizzleTaskRepository(db); phaseRepo = new DrizzlePhaseRepository(db); initiativeRepo = new DrizzleInitiativeRepository(db); // Create full hierarchy for FK constraint const initiative = await initiativeRepo.create({ name: 'Test Initiative', }); const phase = await phaseRepo.create({ initiativeId: initiative.id, name: 'Test Phase', }); testPhaseId = phase.id; }); describe('create', () => { it('should create a task with generated id and timestamps', async () => { const task = await taskRepo.create({ phaseId: testPhaseId, name: 'Test Task', description: 'A test task', order: 1, }); expect(task.id).toBeDefined(); expect(task.id.length).toBeGreaterThan(0); expect(task.phaseId).toBe(testPhaseId); expect(task.name).toBe('Test Task'); expect(task.type).toBe('auto'); expect(task.priority).toBe('medium'); expect(task.status).toBe('pending'); expect(task.order).toBe(1); expect(task.createdAt).toBeInstanceOf(Date); expect(task.updatedAt).toBeInstanceOf(Date); }); it('should throw for invalid phaseId (FK constraint)', async () => { await expect( taskRepo.create({ phaseId: 'invalid-phase-id', name: 'Invalid Task', order: 1, }) ).rejects.toThrow(); }); it('should accept custom type and priority', async () => { const task = await taskRepo.create({ phaseId: testPhaseId, name: 'Checkpoint Task', type: 'checkpoint:human-verify', priority: 'high', order: 1, }); expect(task.type).toBe('checkpoint:human-verify'); expect(task.priority).toBe('high'); }); }); describe('findById', () => { it('should return null for non-existent task', async () => { const result = await taskRepo.findById('non-existent-id'); expect(result).toBeNull(); }); it('should find an existing task', async () => { const created = await taskRepo.create({ phaseId: testPhaseId, name: 'Find Me', order: 1, }); const found = await taskRepo.findById(created.id); expect(found).not.toBeNull(); expect(found!.id).toBe(created.id); expect(found!.name).toBe('Find Me'); }); }); describe('findByPhaseId', () => { it('should return empty array for phase with no tasks', async () => { const tasks = await taskRepo.findByPhaseId(testPhaseId); expect(tasks).toEqual([]); }); it('should return only matching tasks ordered by order field', async () => { // Create tasks out of order await taskRepo.create({ phaseId: testPhaseId, name: 'Task 3', order: 3, }); await taskRepo.create({ phaseId: testPhaseId, name: 'Task 1', order: 1, }); await taskRepo.create({ phaseId: testPhaseId, name: 'Task 2', order: 2, }); const tasks = await taskRepo.findByPhaseId(testPhaseId); expect(tasks.length).toBe(3); expect(tasks[0].name).toBe('Task 1'); expect(tasks[1].name).toBe('Task 2'); expect(tasks[2].name).toBe('Task 3'); }); }); describe('update', () => { it('should update status correctly', async () => { const created = await taskRepo.create({ phaseId: testPhaseId, name: 'Status Test', status: 'pending', order: 1, }); const updated = await taskRepo.update(created.id, { status: 'in_progress', }); expect(updated.status).toBe('in_progress'); }); it('should update fields and updatedAt', async () => { const created = await taskRepo.create({ phaseId: testPhaseId, name: 'Original Name', order: 1, }); await new Promise((resolve) => setTimeout(resolve, 10)); const updated = await taskRepo.update(created.id, { name: 'Updated Name', priority: 'low', }); expect(updated.name).toBe('Updated Name'); expect(updated.priority).toBe('low'); expect(updated.updatedAt.getTime()).toBeGreaterThanOrEqual(created.updatedAt.getTime()); }); it('should throw for non-existent task', async () => { await expect( taskRepo.update('non-existent-id', { name: 'New Name' }) ).rejects.toThrow('Task not found'); }); }); describe('delete', () => { it('should delete an existing task', async () => { const created = await taskRepo.create({ phaseId: testPhaseId, name: 'To Delete', order: 1, }); await taskRepo.delete(created.id); const found = await taskRepo.findById(created.id); expect(found).toBeNull(); }); it('should throw for non-existent task', async () => { await expect(taskRepo.delete('non-existent-id')).rejects.toThrow( 'Task not found' ); }); }); });