mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
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>
This commit is contained in:
@@ -14,7 +14,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch"
|
||||
"watch": "tsc --watch",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"keywords": ["automaker", "dependency", "resolver"],
|
||||
"author": "AutoMaker Team",
|
||||
@@ -24,6 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"typescript": "^5.7.3"
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^4.0.16"
|
||||
}
|
||||
}
|
||||
|
||||
360
libs/dependency-resolver/tests/resolver.test.ts
Normal file
360
libs/dependency-resolver/tests/resolver.test.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user