mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 06:42:03 +00:00
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>
361 lines
12 KiB
TypeScript
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");
|
|
});
|
|
});
|
|
});
|