import { describe, it, expect, vi, beforeEach } from 'vitest'; import request from 'supertest'; import { app } from '../index.js'; import pool from '../db.js'; // Mock DB const mockQuery = vi.fn(); const mockRelease = vi.fn(); const mockCommit = vi.fn(); const mockRollback = vi.fn(); const mockBeginTransaction = vi.fn(); vi.mock('../db.js', () => ({ default: { query: vi.fn(), getConnection: vi.fn() }, initDB: vi.fn() })); describe('Task Routes', () => { beforeEach(() => { vi.clearAllMocks(); mockQuery.mockReset(); // Important to clear implementations pool.query = mockQuery; pool.getConnection.mockResolvedValue({ query: mockQuery, release: mockRelease, beginTransaction: mockBeginTransaction, commit: mockCommit, rollback: mockRollback }); }); describe('GET /api/tasks', () => { it('returns list of tasks', async () => { // Mock fetching task IDs mockQuery.mockResolvedValueOnce([[{ id: 't1' }]]); // Mock getFullTask queries for 't1' // 1. Task details mockQuery.mockResolvedValueOnce([[{ id: 't1', title: 'Task 1', status: 'todo' }]]); // Task // 2. Subtasks mockQuery.mockResolvedValueOnce([[]]); // Subtasks // 3. Comments mockQuery.mockResolvedValueOnce([[]]); // Comments // 4. Activities mockQuery.mockResolvedValueOnce([[]]); // Activities // 5. Tags mockQuery.mockResolvedValueOnce([[]]); // Tags // 6. Dependencies mockQuery.mockResolvedValueOnce([[]]); // Dependencies const res = await request(app).get('/api/tasks'); expect(res.status).toBe(200); expect(res.body).toHaveLength(1); expect(res.body[0].title).toBe('Task 1'); }); }); describe('POST /api/tasks', () => { it('creates a new task', async () => { // Mock INSERTs (1: Task, 2: Activities) -> Return {} mockQuery.mockResolvedValueOnce({}); // Insert task mockQuery.mockResolvedValueOnce({}); // Insert activity // getFullTask queries (3-8) mockQuery.mockResolvedValueOnce([[{ id: 'new-id', title: 'New Task', status: 'todo' }]]); // Task mockQuery.mockResolvedValueOnce([[]]); // Subtasks mockQuery.mockResolvedValueOnce([[]]); // Comments mockQuery.mockResolvedValueOnce([[]]); // Activities mockQuery.mockResolvedValueOnce([[]]); // Tags mockQuery.mockResolvedValueOnce([[]]); // Deps const newTask = { // For getFullTask called at end title: 'New Task', description: 'Desc', status: 'todo' }; const res = await request(app) .post('/api/tasks') .send(newTask); expect(res.status).toBe(201); expect(mockBeginTransaction).toHaveBeenCalled(); expect(mockCommit).toHaveBeenCalled(); expect(mockRelease).toHaveBeenCalled(); }); it('rolls back on error', async () => { mockQuery.mockRejectedValue(new Error('DB Error')); const res = await request(app) .post('/api/tasks') .send({ title: 'Task' }); expect(res.status).toBe(500); expect(mockRollback).toHaveBeenCalled(); expect(mockRelease).toHaveBeenCalled(); }); }); describe('DELETE /api/tasks/:id', () => { it('deletes a task', async () => { mockQuery.mockResolvedValue({}); const res = await request(app).delete('/api/tasks/t1'); expect(res.status).toBe(200); expect(mockQuery).toHaveBeenCalledWith( expect.stringContaining('DELETE FROM tasks'), ['t1'] ); }); }); });