Files
Codewalkers/apps/server/test/topological-sort.test.ts
Lukas May 34578d39c6 refactor: Restructure monorepo to apps/server/ and apps/web/ layout
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
2026-03-03 11:22:53 +01:00

135 lines
4.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { topologicalSortPhases, type PhaseForSort, type DependencyEdge } from '@codewalk-district/shared';
function mkPhase(id: string, createdAt: string | Date): PhaseForSort {
return { id, createdAt };
}
describe('topologicalSortPhases', () => {
it('should return empty array for empty input', () => {
expect(topologicalSortPhases([], [])).toEqual([]);
});
it('should return phases in createdAt order when no edges', () => {
const phases = [
mkPhase('c', '2026-01-03'),
mkPhase('a', '2026-01-01'),
mkPhase('b', '2026-01-02'),
];
const result = topologicalSortPhases(phases, []);
expect(result.map((p) => p.id)).toEqual(['a', 'b', 'c']);
});
it('should sort linear chain correctly', () => {
// A -> B -> C (B depends on A, C depends on B)
const phases = [
mkPhase('a', '2026-01-01'),
mkPhase('b', '2026-01-02'),
mkPhase('c', '2026-01-03'),
];
const edges: DependencyEdge[] = [
{ phaseId: 'b', dependsOnPhaseId: 'a' },
{ phaseId: 'c', dependsOnPhaseId: 'b' },
];
const result = topologicalSortPhases(phases, edges);
expect(result.map((p) => p.id)).toEqual(['a', 'b', 'c']);
});
it('should handle diamond dependency', () => {
// A
// / \
// B C
// \ /
// D
const phases = [
mkPhase('a', '2026-01-01'),
mkPhase('b', '2026-01-02'),
mkPhase('c', '2026-01-03'),
mkPhase('d', '2026-01-04'),
];
const edges: DependencyEdge[] = [
{ phaseId: 'b', dependsOnPhaseId: 'a' },
{ phaseId: 'c', dependsOnPhaseId: 'a' },
{ phaseId: 'd', dependsOnPhaseId: 'b' },
{ phaseId: 'd', dependsOnPhaseId: 'c' },
];
const result = topologicalSortPhases(phases, edges);
// A must come first, D must come last, B before C by createdAt
expect(result[0].id).toBe('a');
expect(result[3].id).toBe('d');
expect(result.map((p) => p.id)).toEqual(['a', 'b', 'c', 'd']);
});
it('should use createdAt as deterministic tiebreaker', () => {
// Three independent phases — should sort by createdAt
const phases = [
mkPhase('z', '2026-01-03'),
mkPhase('y', '2026-01-01'),
mkPhase('x', '2026-01-02'),
];
const result = topologicalSortPhases(phases, []);
expect(result.map((p) => p.id)).toEqual(['y', 'x', 'z']);
});
it('should handle cycle gracefully by appending cycled nodes', () => {
// A -> B -> A (cycle), C is independent
const phases = [
mkPhase('a', '2026-01-01'),
mkPhase('b', '2026-01-02'),
mkPhase('c', '2026-01-03'),
];
const edges: DependencyEdge[] = [
{ phaseId: 'b', dependsOnPhaseId: 'a' },
{ phaseId: 'a', dependsOnPhaseId: 'b' },
];
const result = topologicalSortPhases(phases, edges);
// C has no deps so it comes first, then A and B appended (cycle)
expect(result[0].id).toBe('c');
expect(result.length).toBe(3);
// A and B are appended in createdAt order
expect(result[1].id).toBe('a');
expect(result[2].id).toBe('b');
});
it('should ignore edges referencing non-existent phases', () => {
const phases = [
mkPhase('a', '2026-01-01'),
mkPhase('b', '2026-01-02'),
];
const edges: DependencyEdge[] = [
{ phaseId: 'b', dependsOnPhaseId: 'nonexistent' },
];
const result = topologicalSortPhases(phases, edges);
// Edge is ignored, both treated as independent
expect(result.map((p) => p.id)).toEqual(['a', 'b']);
});
it('should handle single phase with no edges', () => {
const phases = [mkPhase('only', '2026-01-01')];
const result = topologicalSortPhases(phases, []);
expect(result.map((p) => p.id)).toEqual(['only']);
});
it('should work with Date objects', () => {
const phases = [
mkPhase('b', new Date('2026-01-02')),
mkPhase('a', new Date('2026-01-01')),
];
const edges: DependencyEdge[] = [
{ phaseId: 'b', dependsOnPhaseId: 'a' },
];
const result = topologicalSortPhases(phases, edges);
expect(result.map((p) => p.id)).toEqual(['a', 'b']);
});
it('should preserve extra properties on phase objects', () => {
const phases = [
{ id: 'a', createdAt: '2026-01-01', name: 'Alpha', status: 'pending' },
{ id: 'b', createdAt: '2026-01-02', name: 'Beta', status: 'active' },
];
const result = topologicalSortPhases(phases, []);
expect(result[0].name).toBe('Alpha');
expect(result[1].name).toBe('Beta');
});
});