Files
automaker/libs/dependency-resolver/tests/resolver.test.ts
Kacper 0cef537a3d test: Add comprehensive unit tests for shared packages
Add 88 new unit tests covering critical business logic in shared packages:

- libs/git-utils/tests/diff.test.ts (22 tests)
  * Synthetic diff generation for new files
  * Binary file handling
  * Large file handling
  * Untracked file diff appending
  * Directory file listing with exclusions
  * Non-git directory handling

- libs/dependency-resolver/tests/resolver.test.ts (30 tests)
  * Topological sorting with dependencies
  * Priority-aware ordering
  * Circular dependency detection
  * Missing dependency tracking
  * Blocked feature detection
  * Complex dependency graphs

- libs/utils/tests/error-handler.test.ts (36 tests)
  * Abort error detection
  * Cancellation error detection
  * Authentication error detection
  * Error classification logic
  * User-friendly error messages

All tests use vitest and follow best practices with proper setup/teardown.

Resolves PR review issue #1 (HIGH PRIORITY)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-20 22:48:43 +01:00

361 lines
12 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
resolveDependencies,
areDependenciesSatisfied,
getBlockingDependencies,
} from "../src/resolver";
import type { Feature } from "@automaker/types";
// Helper to create test features
function createFeature(
id: string,
options: {
dependencies?: string[];
status?: string;
priority?: number;
} = {}
): Feature {
return {
id,
category: "test",
description: `Feature ${id}`,
dependencies: options.dependencies,
status: options.status || "pending",
priority: options.priority,
};
}
describe("resolver.ts", () => {
describe("resolveDependencies", () => {
it("should handle features with no dependencies", () => {
const features = [
createFeature("A"),
createFeature("B"),
createFeature("C"),
];
const result = resolveDependencies(features);
expect(result.orderedFeatures).toHaveLength(3);
expect(result.circularDependencies).toEqual([]);
expect(result.missingDependencies.size).toBe(0);
expect(result.blockedFeatures.size).toBe(0);
});
it("should order features with linear dependencies", () => {
const features = [
createFeature("C", { dependencies: ["B"] }),
createFeature("A"),
createFeature("B", { dependencies: ["A"] }),
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map(f => f.id);
expect(ids.indexOf("A")).toBeLessThan(ids.indexOf("B"));
expect(ids.indexOf("B")).toBeLessThan(ids.indexOf("C"));
expect(result.circularDependencies).toEqual([]);
});
it("should respect priority within same dependency level", () => {
const features = [
createFeature("Low", { priority: 3 }),
createFeature("High", { priority: 1 }),
createFeature("Medium", { priority: 2 }),
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map(f => f.id);
expect(ids).toEqual(["High", "Medium", "Low"]);
});
it("should use default priority 2 when not specified", () => {
const features = [
createFeature("NoPriority"),
createFeature("HighPriority", { priority: 1 }),
createFeature("LowPriority", { priority: 3 }),
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map(f => f.id);
expect(ids.indexOf("HighPriority")).toBeLessThan(ids.indexOf("NoPriority"));
expect(ids.indexOf("NoPriority")).toBeLessThan(ids.indexOf("LowPriority"));
});
it("should respect dependencies over priority", () => {
const features = [
createFeature("B", { dependencies: ["A"], priority: 1 }), // High priority but depends on A
createFeature("A", { priority: 3 }), // Low priority but no dependencies
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map(f => f.id);
expect(ids.indexOf("A")).toBeLessThan(ids.indexOf("B"));
});
it("should detect circular dependencies (simple cycle)", () => {
const features = [
createFeature("A", { dependencies: ["B"] }),
createFeature("B", { dependencies: ["A"] }),
];
const result = resolveDependencies(features);
expect(result.circularDependencies).toHaveLength(1);
expect(result.circularDependencies[0]).toContain("A");
expect(result.circularDependencies[0]).toContain("B");
expect(result.orderedFeatures).toHaveLength(2); // All features still included
});
it("should detect circular dependencies (3-way cycle)", () => {
const features = [
createFeature("A", { dependencies: ["C"] }),
createFeature("B", { dependencies: ["A"] }),
createFeature("C", { dependencies: ["B"] }),
];
const result = resolveDependencies(features);
expect(result.circularDependencies.length).toBeGreaterThan(0);
const allCycleIds = result.circularDependencies.flat();
expect(allCycleIds).toContain("A");
expect(allCycleIds).toContain("B");
expect(allCycleIds).toContain("C");
});
it("should detect missing dependencies", () => {
const features = [
createFeature("A", { dependencies: ["NonExistent"] }),
createFeature("B"),
];
const result = resolveDependencies(features);
expect(result.missingDependencies.has("A")).toBe(true);
expect(result.missingDependencies.get("A")).toContain("NonExistent");
});
it("should detect blocked features (incomplete dependencies)", () => {
const features = [
createFeature("A", { status: "pending" }),
createFeature("B", { dependencies: ["A"], status: "pending" }),
];
const result = resolveDependencies(features);
expect(result.blockedFeatures.has("B")).toBe(true);
expect(result.blockedFeatures.get("B")).toContain("A");
});
it("should not mark features as blocked if dependencies are completed", () => {
const features = [
createFeature("A", { status: "completed" }),
createFeature("B", { dependencies: ["A"], status: "pending" }),
];
const result = resolveDependencies(features);
expect(result.blockedFeatures.has("B")).toBe(false);
});
it("should not mark features as blocked if dependencies are verified", () => {
const features = [
createFeature("A", { status: "verified" }),
createFeature("B", { dependencies: ["A"], status: "pending" }),
];
const result = resolveDependencies(features);
expect(result.blockedFeatures.has("B")).toBe(false);
});
it("should handle complex dependency graph", () => {
const features = [
createFeature("E", { dependencies: ["C", "D"] }),
createFeature("D", { dependencies: ["B"] }),
createFeature("C", { dependencies: ["A", "B"] }),
createFeature("B"),
createFeature("A"),
];
const result = resolveDependencies(features);
const ids = result.orderedFeatures.map(f => f.id);
// A and B have no dependencies - can be first or second
expect(ids.indexOf("A")).toBeLessThan(ids.indexOf("C"));
expect(ids.indexOf("B")).toBeLessThan(ids.indexOf("C"));
expect(ids.indexOf("B")).toBeLessThan(ids.indexOf("D"));
// C depends on A and B
expect(ids.indexOf("C")).toBeLessThan(ids.indexOf("E"));
// D depends on B
expect(ids.indexOf("D")).toBeLessThan(ids.indexOf("E"));
expect(result.circularDependencies).toEqual([]);
});
it("should handle multiple missing dependencies", () => {
const features = [
createFeature("A", { dependencies: ["X", "Y", "Z"] }),
];
const result = resolveDependencies(features);
expect(result.missingDependencies.get("A")).toEqual(["X", "Y", "Z"]);
});
it("should handle empty feature list", () => {
const result = resolveDependencies([]);
expect(result.orderedFeatures).toEqual([]);
expect(result.circularDependencies).toEqual([]);
expect(result.missingDependencies.size).toBe(0);
expect(result.blockedFeatures.size).toBe(0);
});
it("should handle features with both missing and existing dependencies", () => {
const features = [
createFeature("A"),
createFeature("B", { dependencies: ["A", "NonExistent"] }),
];
const result = resolveDependencies(features);
expect(result.missingDependencies.get("B")).toContain("NonExistent");
const ids = result.orderedFeatures.map(f => f.id);
expect(ids.indexOf("A")).toBeLessThan(ids.indexOf("B"));
});
});
describe("areDependenciesSatisfied", () => {
it("should return true for feature with no dependencies", () => {
const feature = createFeature("A");
const allFeatures = [feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(true);
});
it("should return true for feature with empty dependencies array", () => {
const feature = createFeature("A", { dependencies: [] });
const allFeatures = [feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(true);
});
it("should return true when all dependencies are completed", () => {
const dep = createFeature("Dep", { status: "completed" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(true);
});
it("should return true when all dependencies are verified", () => {
const dep = createFeature("Dep", { status: "verified" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(true);
});
it("should return false when any dependency is pending", () => {
const dep = createFeature("Dep", { status: "pending" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(false);
});
it("should return false when any dependency is running", () => {
const dep = createFeature("Dep", { status: "running" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(false);
});
it("should return false when dependency is missing", () => {
const feature = createFeature("A", { dependencies: ["NonExistent"] });
const allFeatures = [feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(false);
});
it("should check all dependencies", () => {
const dep1 = createFeature("Dep1", { status: "completed" });
const dep2 = createFeature("Dep2", { status: "pending" });
const feature = createFeature("A", { dependencies: ["Dep1", "Dep2"] });
const allFeatures = [dep1, dep2, feature];
expect(areDependenciesSatisfied(feature, allFeatures)).toBe(false);
});
});
describe("getBlockingDependencies", () => {
it("should return empty array for feature with no dependencies", () => {
const feature = createFeature("A");
const allFeatures = [feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual([]);
});
it("should return empty array when all dependencies are completed", () => {
const dep = createFeature("Dep", { status: "completed" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual([]);
});
it("should return empty array when all dependencies are verified", () => {
const dep = createFeature("Dep", { status: "verified" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual([]);
});
it("should return pending dependencies", () => {
const dep = createFeature("Dep", { status: "pending" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual(["Dep"]);
});
it("should return running dependencies", () => {
const dep = createFeature("Dep", { status: "running" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual(["Dep"]);
});
it("should return failed dependencies", () => {
const dep = createFeature("Dep", { status: "failed" });
const feature = createFeature("A", { dependencies: ["Dep"] });
const allFeatures = [dep, feature];
expect(getBlockingDependencies(feature, allFeatures)).toEqual(["Dep"]);
});
it("should return all incomplete dependencies", () => {
const dep1 = createFeature("Dep1", { status: "pending" });
const dep2 = createFeature("Dep2", { status: "completed" });
const dep3 = createFeature("Dep3", { status: "running" });
const feature = createFeature("A", { dependencies: ["Dep1", "Dep2", "Dep3"] });
const allFeatures = [dep1, dep2, dep3, feature];
const blocking = getBlockingDependencies(feature, allFeatures);
expect(blocking).toContain("Dep1");
expect(blocking).toContain("Dep3");
expect(blocking).not.toContain("Dep2");
});
});
});