mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 14:22:02 +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>
307 lines
11 KiB
TypeScript
307 lines
11 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
import {
|
|
generateSyntheticDiffForNewFile,
|
|
appendUntrackedFileDiffs,
|
|
listAllFilesInDirectory,
|
|
generateDiffsForNonGitDirectory,
|
|
getGitRepositoryDiffs,
|
|
} from "../src/diff";
|
|
import fs from "fs/promises";
|
|
import path from "path";
|
|
import os from "os";
|
|
|
|
describe("diff.ts", () => {
|
|
let tempDir: string;
|
|
|
|
beforeEach(async () => {
|
|
// Create a temporary directory for each test
|
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "git-utils-test-"));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
// Clean up temporary directory
|
|
try {
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
} catch (error) {
|
|
// Ignore cleanup errors
|
|
}
|
|
});
|
|
|
|
describe("generateSyntheticDiffForNewFile", () => {
|
|
it("should generate diff for binary file", async () => {
|
|
const fileName = "test.png";
|
|
const filePath = path.join(tempDir, fileName);
|
|
await fs.writeFile(filePath, Buffer.from([0x89, 0x50, 0x4e, 0x47]));
|
|
|
|
const diff = await generateSyntheticDiffForNewFile(tempDir, fileName);
|
|
|
|
expect(diff).toContain(`diff --git a/${fileName} b/${fileName}`);
|
|
expect(diff).toContain("new file mode 100644");
|
|
expect(diff).toContain(`Binary file ${fileName} added`);
|
|
});
|
|
|
|
it("should generate diff for large text file", async () => {
|
|
const fileName = "large.txt";
|
|
const filePath = path.join(tempDir, fileName);
|
|
// Create a file > 1MB
|
|
const largeContent = "x".repeat(1024 * 1024 + 100);
|
|
await fs.writeFile(filePath, largeContent);
|
|
|
|
const diff = await generateSyntheticDiffForNewFile(tempDir, fileName);
|
|
|
|
expect(diff).toContain(`diff --git a/${fileName} b/${fileName}`);
|
|
expect(diff).toContain("[File too large to display:");
|
|
expect(diff).toMatch(/\d+KB\]/);
|
|
});
|
|
|
|
it("should generate diff for small text file with trailing newline", async () => {
|
|
const fileName = "test.txt";
|
|
const filePath = path.join(tempDir, fileName);
|
|
const content = "line 1\nline 2\nline 3\n";
|
|
await fs.writeFile(filePath, content);
|
|
|
|
const diff = await generateSyntheticDiffForNewFile(tempDir, fileName);
|
|
|
|
expect(diff).toContain(`diff --git a/${fileName} b/${fileName}`);
|
|
expect(diff).toContain("new file mode 100644");
|
|
expect(diff).toContain("--- /dev/null");
|
|
expect(diff).toContain(`+++ b/${fileName}`);
|
|
expect(diff).toContain("@@ -0,0 +1,3 @@");
|
|
expect(diff).toContain("+line 1");
|
|
expect(diff).toContain("+line 2");
|
|
expect(diff).toContain("+line 3");
|
|
expect(diff).not.toContain("\\ No newline at end of file");
|
|
});
|
|
|
|
it("should generate diff for text file without trailing newline", async () => {
|
|
const fileName = "no-newline.txt";
|
|
const filePath = path.join(tempDir, fileName);
|
|
const content = "line 1\nline 2";
|
|
await fs.writeFile(filePath, content);
|
|
|
|
const diff = await generateSyntheticDiffForNewFile(tempDir, fileName);
|
|
|
|
expect(diff).toContain(`diff --git a/${fileName} b/${fileName}`);
|
|
expect(diff).toContain("+line 1");
|
|
expect(diff).toContain("+line 2");
|
|
expect(diff).toContain("\\ No newline at end of file");
|
|
});
|
|
|
|
it("should generate diff for empty file", async () => {
|
|
const fileName = "empty.txt";
|
|
const filePath = path.join(tempDir, fileName);
|
|
await fs.writeFile(filePath, "");
|
|
|
|
const diff = await generateSyntheticDiffForNewFile(tempDir, fileName);
|
|
|
|
expect(diff).toContain(`diff --git a/${fileName} b/${fileName}`);
|
|
expect(diff).toContain("@@ -0,0 +1,0 @@");
|
|
});
|
|
|
|
it("should generate diff for single line file", async () => {
|
|
const fileName = "single.txt";
|
|
const filePath = path.join(tempDir, fileName);
|
|
await fs.writeFile(filePath, "single line\n");
|
|
|
|
const diff = await generateSyntheticDiffForNewFile(tempDir, fileName);
|
|
|
|
expect(diff).toContain("@@ -0,0 +1,1 @@");
|
|
expect(diff).toContain("+single line");
|
|
});
|
|
|
|
it("should handle file not found error", async () => {
|
|
const fileName = "nonexistent.txt";
|
|
|
|
const diff = await generateSyntheticDiffForNewFile(tempDir, fileName);
|
|
|
|
expect(diff).toContain(`diff --git a/${fileName} b/${fileName}`);
|
|
expect(diff).toContain("[Unable to read file content]");
|
|
});
|
|
});
|
|
|
|
describe("appendUntrackedFileDiffs", () => {
|
|
it("should return existing diff when no untracked files", async () => {
|
|
const existingDiff = "diff --git a/test.txt b/test.txt\n";
|
|
const files = [
|
|
{ status: "M", path: "test.txt" },
|
|
{ status: "A", path: "new.txt" },
|
|
];
|
|
|
|
const result = await appendUntrackedFileDiffs(tempDir, existingDiff, files);
|
|
|
|
expect(result).toBe(existingDiff);
|
|
});
|
|
|
|
it("should append synthetic diffs for untracked files", async () => {
|
|
const existingDiff = "existing diff\n";
|
|
const untrackedFile = "untracked.txt";
|
|
const filePath = path.join(tempDir, untrackedFile);
|
|
await fs.writeFile(filePath, "content\n");
|
|
|
|
const files = [
|
|
{ status: "M", path: "modified.txt" },
|
|
{ status: "?", path: untrackedFile },
|
|
];
|
|
|
|
const result = await appendUntrackedFileDiffs(tempDir, existingDiff, files);
|
|
|
|
expect(result).toContain("existing diff");
|
|
expect(result).toContain(`diff --git a/${untrackedFile} b/${untrackedFile}`);
|
|
expect(result).toContain("+content");
|
|
});
|
|
|
|
it("should handle multiple untracked files", async () => {
|
|
const file1 = "file1.txt";
|
|
const file2 = "file2.txt";
|
|
await fs.writeFile(path.join(tempDir, file1), "file1\n");
|
|
await fs.writeFile(path.join(tempDir, file2), "file2\n");
|
|
|
|
const files = [
|
|
{ status: "?", path: file1 },
|
|
{ status: "?", path: file2 },
|
|
];
|
|
|
|
const result = await appendUntrackedFileDiffs(tempDir, "", files);
|
|
|
|
expect(result).toContain(`diff --git a/${file1} b/${file1}`);
|
|
expect(result).toContain(`diff --git a/${file2} b/${file2}`);
|
|
expect(result).toContain("+file1");
|
|
expect(result).toContain("+file2");
|
|
});
|
|
});
|
|
|
|
describe("listAllFilesInDirectory", () => {
|
|
it("should list files in empty directory", async () => {
|
|
const files = await listAllFilesInDirectory(tempDir);
|
|
expect(files).toEqual([]);
|
|
});
|
|
|
|
it("should list files in flat directory", async () => {
|
|
await fs.writeFile(path.join(tempDir, "file1.txt"), "content");
|
|
await fs.writeFile(path.join(tempDir, "file2.js"), "code");
|
|
|
|
const files = await listAllFilesInDirectory(tempDir);
|
|
|
|
expect(files).toHaveLength(2);
|
|
expect(files).toContain("file1.txt");
|
|
expect(files).toContain("file2.js");
|
|
});
|
|
|
|
it("should list files in nested directories", async () => {
|
|
await fs.mkdir(path.join(tempDir, "subdir"));
|
|
await fs.writeFile(path.join(tempDir, "root.txt"), "");
|
|
await fs.writeFile(path.join(tempDir, "subdir", "nested.txt"), "");
|
|
|
|
const files = await listAllFilesInDirectory(tempDir);
|
|
|
|
expect(files).toHaveLength(2);
|
|
expect(files).toContain("root.txt");
|
|
expect(files).toContain("subdir/nested.txt");
|
|
});
|
|
|
|
it("should skip node_modules directory", async () => {
|
|
await fs.mkdir(path.join(tempDir, "node_modules"));
|
|
await fs.writeFile(path.join(tempDir, "app.js"), "");
|
|
await fs.writeFile(path.join(tempDir, "node_modules", "package.js"), "");
|
|
|
|
const files = await listAllFilesInDirectory(tempDir);
|
|
|
|
expect(files).toHaveLength(1);
|
|
expect(files).toContain("app.js");
|
|
expect(files).not.toContain("node_modules/package.js");
|
|
});
|
|
|
|
it("should skip common build directories", async () => {
|
|
await fs.mkdir(path.join(tempDir, "dist"));
|
|
await fs.mkdir(path.join(tempDir, "build"));
|
|
await fs.mkdir(path.join(tempDir, ".next"));
|
|
await fs.writeFile(path.join(tempDir, "source.ts"), "");
|
|
await fs.writeFile(path.join(tempDir, "dist", "output.js"), "");
|
|
await fs.writeFile(path.join(tempDir, "build", "output.js"), "");
|
|
|
|
const files = await listAllFilesInDirectory(tempDir);
|
|
|
|
expect(files).toHaveLength(1);
|
|
expect(files).toContain("source.ts");
|
|
});
|
|
|
|
it("should skip hidden files except .env", async () => {
|
|
await fs.writeFile(path.join(tempDir, ".hidden"), "");
|
|
await fs.writeFile(path.join(tempDir, ".env"), "");
|
|
await fs.writeFile(path.join(tempDir, "visible.txt"), "");
|
|
|
|
const files = await listAllFilesInDirectory(tempDir);
|
|
|
|
expect(files).toHaveLength(2);
|
|
expect(files).toContain(".env");
|
|
expect(files).toContain("visible.txt");
|
|
expect(files).not.toContain(".hidden");
|
|
});
|
|
|
|
it("should skip .git directory", async () => {
|
|
await fs.mkdir(path.join(tempDir, ".git"));
|
|
await fs.writeFile(path.join(tempDir, ".git", "config"), "");
|
|
await fs.writeFile(path.join(tempDir, "README.md"), "");
|
|
|
|
const files = await listAllFilesInDirectory(tempDir);
|
|
|
|
expect(files).toHaveLength(1);
|
|
expect(files).toContain("README.md");
|
|
});
|
|
});
|
|
|
|
describe("generateDiffsForNonGitDirectory", () => {
|
|
it("should generate diffs for all files in directory", async () => {
|
|
await fs.writeFile(path.join(tempDir, "file1.txt"), "content1\n");
|
|
await fs.writeFile(path.join(tempDir, "file2.js"), "console.log('hi');\n");
|
|
|
|
const result = await generateDiffsForNonGitDirectory(tempDir);
|
|
|
|
expect(result.files).toHaveLength(2);
|
|
expect(result.files.every(f => f.status === "?")).toBe(true);
|
|
expect(result.diff).toContain("diff --git a/file1.txt b/file1.txt");
|
|
expect(result.diff).toContain("diff --git a/file2.js b/file2.js");
|
|
expect(result.diff).toContain("+content1");
|
|
expect(result.diff).toContain("+console.log('hi');");
|
|
});
|
|
|
|
it("should return empty result for empty directory", async () => {
|
|
const result = await generateDiffsForNonGitDirectory(tempDir);
|
|
|
|
expect(result.files).toEqual([]);
|
|
expect(result.diff).toBe("");
|
|
});
|
|
|
|
it("should mark all files as untracked", async () => {
|
|
await fs.writeFile(path.join(tempDir, "test.txt"), "test");
|
|
|
|
const result = await generateDiffsForNonGitDirectory(tempDir);
|
|
|
|
expect(result.files).toHaveLength(1);
|
|
expect(result.files[0].status).toBe("?");
|
|
expect(result.files[0].statusText).toBe("New");
|
|
});
|
|
});
|
|
|
|
describe("getGitRepositoryDiffs", () => {
|
|
it("should treat non-git directory as all new files", async () => {
|
|
await fs.writeFile(path.join(tempDir, "file.txt"), "content\n");
|
|
|
|
const result = await getGitRepositoryDiffs(tempDir);
|
|
|
|
expect(result.hasChanges).toBe(true);
|
|
expect(result.files).toHaveLength(1);
|
|
expect(result.files[0].status).toBe("?");
|
|
expect(result.diff).toContain("diff --git a/file.txt b/file.txt");
|
|
});
|
|
|
|
it("should return no changes for empty non-git directory", async () => {
|
|
const result = await getGitRepositoryDiffs(tempDir);
|
|
|
|
expect(result.hasChanges).toBe(false);
|
|
expect(result.files).toEqual([]);
|
|
expect(result.diff).toBe("");
|
|
});
|
|
});
|
|
});
|