From 26778c60f5d823d4b70fa652722269161d269e8e Mon Sep 17 00:00:00 2001 From: Lukas May Date: Mon, 2 Feb 2026 13:46:53 +0100 Subject: [PATCH] test(14-07): add PhaseRepository dependency tests - Add createDependency tests (create, FK constraint, multiple deps) - Add getDependencies tests (empty, with deps, direct only) - Add getDependents tests (empty, with dependents, direct only) - Add phase_dependencies table to test-helpers.ts --- src/db/repositories/drizzle/phase.test.ts | 204 ++++++++++++++++++++ src/db/repositories/drizzle/test-helpers.ts | 8 + 2 files changed, 212 insertions(+) diff --git a/src/db/repositories/drizzle/phase.test.ts b/src/db/repositories/drizzle/phase.test.ts index 9ab0b5a..726bb06 100644 --- a/src/db/repositories/drizzle/phase.test.ts +++ b/src/db/repositories/drizzle/phase.test.ts @@ -257,4 +257,208 @@ describe('DrizzlePhaseRepository', () => { expect(next).toBe(6); }); }); + + // =========================================================================== + // Phase Dependency Tests + // =========================================================================== + + describe('createDependency', () => { + it('should create dependency between two phases', async () => { + const phase1 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + const phase2 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 2, + name: 'Phase 2', + }); + + await phaseRepo.createDependency(phase2.id, phase1.id); + + const deps = await phaseRepo.getDependencies(phase2.id); + expect(deps).toContain(phase1.id); + }); + + it('should throw if phase does not exist', async () => { + const phase1 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + + // Creating dependency with non-existent phase should throw (FK constraint) + await expect( + phaseRepo.createDependency('non-existent-phase', phase1.id) + ).rejects.toThrow(); + }); + + it('should allow multiple dependencies for same phase', async () => { + const phase1 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + const phase2 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 2, + name: 'Phase 2', + }); + const phase3 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 3, + name: 'Phase 3', + }); + + // Phase 3 depends on both Phase 1 and Phase 2 + await phaseRepo.createDependency(phase3.id, phase1.id); + await phaseRepo.createDependency(phase3.id, phase2.id); + + const deps = await phaseRepo.getDependencies(phase3.id); + expect(deps.length).toBe(2); + expect(deps).toContain(phase1.id); + expect(deps).toContain(phase2.id); + }); + }); + + describe('getDependencies', () => { + it('should return empty array for phase with no dependencies', async () => { + const phase = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + + const deps = await phaseRepo.getDependencies(phase.id); + expect(deps).toEqual([]); + }); + + it('should return dependency IDs for phase with dependencies', async () => { + const phase1 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + const phase2 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 2, + name: 'Phase 2', + }); + const phase3 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 3, + name: 'Phase 3', + }); + + // Phase 3 depends on Phase 1 and Phase 2 + await phaseRepo.createDependency(phase3.id, phase1.id); + await phaseRepo.createDependency(phase3.id, phase2.id); + + const deps = await phaseRepo.getDependencies(phase3.id); + expect(deps.length).toBe(2); + expect(deps).toContain(phase1.id); + expect(deps).toContain(phase2.id); + }); + + it('should return only direct dependencies (not transitive)', async () => { + // Phase 1 -> Phase 2 -> Phase 3 (linear chain) + const phase1 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + const phase2 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 2, + name: 'Phase 2', + }); + const phase3 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 3, + name: 'Phase 3', + }); + + // Phase 2 depends on Phase 1 + await phaseRepo.createDependency(phase2.id, phase1.id); + // Phase 3 depends on Phase 2 + await phaseRepo.createDependency(phase3.id, phase2.id); + + // Phase 3's dependencies should only include Phase 2, not Phase 1 + const depsPhase3 = await phaseRepo.getDependencies(phase3.id); + expect(depsPhase3.length).toBe(1); + expect(depsPhase3).toContain(phase2.id); + expect(depsPhase3).not.toContain(phase1.id); + }); + }); + + describe('getDependents', () => { + it('should return empty array for phase with no dependents', async () => { + const phase = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + + const dependents = await phaseRepo.getDependents(phase.id); + expect(dependents).toEqual([]); + }); + + it('should return dependent phase IDs', async () => { + const phase1 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + const phase2 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 2, + name: 'Phase 2', + }); + const phase3 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 3, + name: 'Phase 3', + }); + + // Phase 2 and Phase 3 both depend on Phase 1 + await phaseRepo.createDependency(phase2.id, phase1.id); + await phaseRepo.createDependency(phase3.id, phase1.id); + + const dependents = await phaseRepo.getDependents(phase1.id); + expect(dependents.length).toBe(2); + expect(dependents).toContain(phase2.id); + expect(dependents).toContain(phase3.id); + }); + + it('should return only direct dependents', async () => { + // Phase 1 -> Phase 2 -> Phase 3 (linear chain) + const phase1 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 1, + name: 'Phase 1', + }); + const phase2 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 2, + name: 'Phase 2', + }); + const phase3 = await phaseRepo.create({ + initiativeId: testInitiativeId, + number: 3, + name: 'Phase 3', + }); + + // Phase 2 depends on Phase 1 + await phaseRepo.createDependency(phase2.id, phase1.id); + // Phase 3 depends on Phase 2 + await phaseRepo.createDependency(phase3.id, phase2.id); + + // Phase 1's dependents should only include Phase 2, not Phase 3 + const dependentsPhase1 = await phaseRepo.getDependents(phase1.id); + expect(dependentsPhase1.length).toBe(1); + expect(dependentsPhase1).toContain(phase2.id); + expect(dependentsPhase1).not.toContain(phase3.id); + }); + }); }); diff --git a/src/db/repositories/drizzle/test-helpers.ts b/src/db/repositories/drizzle/test-helpers.ts index 01abfd7..1ec8e7b 100644 --- a/src/db/repositories/drizzle/test-helpers.ts +++ b/src/db/repositories/drizzle/test-helpers.ts @@ -37,6 +37,14 @@ CREATE TABLE IF NOT EXISTS phases ( updated_at INTEGER NOT NULL ); +-- Phase dependencies table +CREATE TABLE IF NOT EXISTS phase_dependencies ( + id TEXT PRIMARY KEY NOT NULL, + phase_id TEXT NOT NULL REFERENCES phases(id) ON DELETE CASCADE, + depends_on_phase_id TEXT NOT NULL REFERENCES phases(id) ON DELETE CASCADE, + created_at INTEGER NOT NULL +); + -- Plans table CREATE TABLE IF NOT EXISTS plans ( id TEXT PRIMARY KEY NOT NULL,