Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
200 lines
5.6 KiB
TypeScript
200 lines
5.6 KiB
TypeScript
/**
|
|
* 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'
|
|
);
|
|
});
|
|
});
|
|
});
|