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
This commit is contained in:
Lukas May
2026-02-02 13:46:53 +01:00
parent 96b585a2e0
commit 26778c60f5
2 changed files with 212 additions and 0 deletions

View File

@@ -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);
});
});
});

View File

@@ -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,