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:
@@ -41,3 +41,9 @@ export { getDbPath, ensureDbDirectory } from './config.js';
|
|||||||
|
|
||||||
// Re-export schema and types
|
// Re-export schema and types
|
||||||
export * from './schema.js';
|
export * from './schema.js';
|
||||||
|
|
||||||
|
// Re-export repository interfaces (ports)
|
||||||
|
export * from './repositories/index.js';
|
||||||
|
|
||||||
|
// Re-export Drizzle adapters
|
||||||
|
export * from './repositories/drizzle/index.js';
|
||||||
|
|||||||
181
src/db/repositories/drizzle/cascade.test.ts
Normal file
181
src/db/repositories/drizzle/cascade.test.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user