feat(02-02): update exports and add cascade delete tests

- src/db/index.ts exports repository interfaces and adapters
- cascade.test.ts tests full hierarchy cascade behavior:
  - Delete initiative removes all phases, plans, tasks
  - Delete phase removes only its plans and tasks
  - Delete plan removes only its tasks
- All 45 repository tests passing
This commit is contained in:
Lukas May
2026-01-30 19:11:31 +01:00
parent 830aa4b03f
commit 112cc231c7
2 changed files with 187 additions and 0 deletions

View File

@@ -41,3 +41,9 @@ export { getDbPath, ensureDbDirectory } from './config.js';
// Re-export schema and types
export * from './schema.js';
// Re-export repository interfaces (ports)
export * from './repositories/index.js';
// Re-export Drizzle adapters
export * from './repositories/drizzle/index.js';

View File

@@ -0,0 +1,181 @@
/**
* Cascade Delete Tests
*
* Tests that cascade deletes work correctly through the repository layer.
* Verifies the SQLite foreign key cascade behavior configured in schema.ts.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { DrizzleInitiativeRepository } from './initiative.js';
import { DrizzlePhaseRepository } from './phase.js';
import { DrizzlePlanRepository } from './plan.js';
import { DrizzleTaskRepository } from './task.js';
import { createTestDatabase } from './test-helpers.js';
import type { DrizzleDatabase } from '../../index.js';
describe('Cascade Deletes', () => {
let db: DrizzleDatabase;
let initiativeRepo: DrizzleInitiativeRepository;
let phaseRepo: DrizzlePhaseRepository;
let planRepo: DrizzlePlanRepository;
let taskRepo: DrizzleTaskRepository;
beforeEach(() => {
db = createTestDatabase();
initiativeRepo = new DrizzleInitiativeRepository(db);
phaseRepo = new DrizzlePhaseRepository(db);
planRepo = new DrizzlePlanRepository(db);
taskRepo = new DrizzleTaskRepository(db);
});
/**
* Helper to create a full hierarchy for testing.
*/
async function createFullHierarchy() {
const initiative = await initiativeRepo.create({
name: 'Test Initiative',
});
const phase1 = await phaseRepo.create({
initiativeId: initiative.id,
number: 1,
name: 'Phase 1',
});
const phase2 = await phaseRepo.create({
initiativeId: initiative.id,
number: 2,
name: 'Phase 2',
});
const plan1 = await planRepo.create({
phaseId: phase1.id,
number: 1,
name: 'Plan 1-1',
});
const plan2 = await planRepo.create({
phaseId: phase1.id,
number: 2,
name: 'Plan 1-2',
});
const plan3 = await planRepo.create({
phaseId: phase2.id,
number: 1,
name: 'Plan 2-1',
});
const task1 = await taskRepo.create({
planId: plan1.id,
name: 'Task 1-1-1',
order: 1,
});
const task2 = await taskRepo.create({
planId: plan1.id,
name: 'Task 1-1-2',
order: 2,
});
const task3 = await taskRepo.create({
planId: plan2.id,
name: 'Task 1-2-1',
order: 1,
});
const task4 = await taskRepo.create({
planId: plan3.id,
name: 'Task 2-1-1',
order: 1,
});
return {
initiative,
phases: { phase1, phase2 },
plans: { plan1, plan2, plan3 },
tasks: { task1, task2, task3, task4 },
};
}
describe('delete initiative', () => {
it('should cascade delete all phases, plans, and tasks', async () => {
const { initiative, phases, plans, tasks } = await createFullHierarchy();
// Verify everything exists
expect(await initiativeRepo.findById(initiative.id)).not.toBeNull();
expect(await phaseRepo.findById(phases.phase1.id)).not.toBeNull();
expect(await phaseRepo.findById(phases.phase2.id)).not.toBeNull();
expect(await planRepo.findById(plans.plan1.id)).not.toBeNull();
expect(await planRepo.findById(plans.plan2.id)).not.toBeNull();
expect(await planRepo.findById(plans.plan3.id)).not.toBeNull();
expect(await taskRepo.findById(tasks.task1.id)).not.toBeNull();
expect(await taskRepo.findById(tasks.task2.id)).not.toBeNull();
expect(await taskRepo.findById(tasks.task3.id)).not.toBeNull();
expect(await taskRepo.findById(tasks.task4.id)).not.toBeNull();
// Delete initiative
await initiativeRepo.delete(initiative.id);
// Verify everything is gone
expect(await initiativeRepo.findById(initiative.id)).toBeNull();
expect(await phaseRepo.findById(phases.phase1.id)).toBeNull();
expect(await phaseRepo.findById(phases.phase2.id)).toBeNull();
expect(await planRepo.findById(plans.plan1.id)).toBeNull();
expect(await planRepo.findById(plans.plan2.id)).toBeNull();
expect(await planRepo.findById(plans.plan3.id)).toBeNull();
expect(await taskRepo.findById(tasks.task1.id)).toBeNull();
expect(await taskRepo.findById(tasks.task2.id)).toBeNull();
expect(await taskRepo.findById(tasks.task3.id)).toBeNull();
expect(await taskRepo.findById(tasks.task4.id)).toBeNull();
});
});
describe('delete phase', () => {
it('should cascade delete plans and tasks under that phase only', async () => {
const { initiative, phases, plans, tasks } = await createFullHierarchy();
// Delete phase 1
await phaseRepo.delete(phases.phase1.id);
// Initiative still exists
expect(await initiativeRepo.findById(initiative.id)).not.toBeNull();
// Phase 1 and its children are gone
expect(await phaseRepo.findById(phases.phase1.id)).toBeNull();
expect(await planRepo.findById(plans.plan1.id)).toBeNull();
expect(await planRepo.findById(plans.plan2.id)).toBeNull();
expect(await taskRepo.findById(tasks.task1.id)).toBeNull();
expect(await taskRepo.findById(tasks.task2.id)).toBeNull();
expect(await taskRepo.findById(tasks.task3.id)).toBeNull();
// Phase 2 and its children still exist
expect(await phaseRepo.findById(phases.phase2.id)).not.toBeNull();
expect(await planRepo.findById(plans.plan3.id)).not.toBeNull();
expect(await taskRepo.findById(tasks.task4.id)).not.toBeNull();
});
});
describe('delete plan', () => {
it('should cascade delete tasks under that plan only', async () => {
const { phases, plans, tasks } = await createFullHierarchy();
// Delete plan 1
await planRepo.delete(plans.plan1.id);
// Phase still exists
expect(await phaseRepo.findById(phases.phase1.id)).not.toBeNull();
// Plan 1 and its tasks are gone
expect(await planRepo.findById(plans.plan1.id)).toBeNull();
expect(await taskRepo.findById(tasks.task1.id)).toBeNull();
expect(await taskRepo.findById(tasks.task2.id)).toBeNull();
// Other plans and tasks still exist
expect(await planRepo.findById(plans.plan2.id)).not.toBeNull();
expect(await planRepo.findById(plans.plan3.id)).not.toBeNull();
expect(await taskRepo.findById(tasks.task3.id)).not.toBeNull();
expect(await taskRepo.findById(tasks.task4.id)).not.toBeNull();
});
});
});