Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions tests/lib/git.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, it, expect, vi } from "vitest";
import { mkdtempSync, rmSync } from "fs";
import { join } from "path";
import { tmpdir } from "os";
import { execSync } from "child_process";

// Create a real temp git repo
const testDir = mkdtempSync(join(tmpdir(), "preflight-git-test-"));
execSync("git init && git commit --allow-empty -m 'init'", { cwd: testDir, stdio: "pipe" });

Check failure on line 9 in tests/lib/git.test.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20)

tests/lib/git.test.ts

Error: Command failed: git init && git commit --allow-empty -m 'init' hint: Using 'master' as the name for the initial branch. This default branch name hint: will change to "main" in Git 3.0. To configure the initial branch name hint: to use in all of your new repositories, which will suppress this warning, hint: call: hint: hint: git config --global init.defaultBranch <name> hint: hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and hint: 'development'. The just-created branch can be renamed via this command: hint: hint: git branch -m <name> hint: hint: Disable this message with "git config set advice.defaultBranchName false" Author identity unknown *** Please tell me who you are. Run git config --global user.email "you@example.com" git config --global user.name "Your Name" to set your account's default identity. Omit --global to set the identity only in this repository. fatal: empty ident name (for <runner@runnervmnay03.y2bb5xqad21ehduu1mmloelxmg.ex.internal.cloudapp.net>) not allowed ❯ tests/lib/git.test.ts:9:1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 128, signal: null, output: [ null, '<Buffer(73) ...>', '<Buffer(962) ...>' ], pid: 2560, stdout: '<Buffer(73) ...>', stderr: '<Buffer(962) ...>' }

Check failure on line 9 in tests/lib/git.test.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22)

tests/lib/git.test.ts

Error: Command failed: git init && git commit --allow-empty -m 'init' hint: Using 'master' as the name for the initial branch. This default branch name hint: will change to "main" in Git 3.0. To configure the initial branch name hint: to use in all of your new repositories, which will suppress this warning, hint: call: hint: hint: git config --global init.defaultBranch <name> hint: hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and hint: 'development'. The just-created branch can be renamed via this command: hint: hint: git branch -m <name> hint: hint: Disable this message with "git config set advice.defaultBranchName false" Author identity unknown *** Please tell me who you are. Run git config --global user.email "you@example.com" git config --global user.name "Your Name" to set your account's default identity. Omit --global to set the identity only in this repository. fatal: empty ident name (for <runner@runnervmnay03.y1d5uzqg4p1u3jznlhajqvjm4e.dx.internal.cloudapp.net>) not allowed ❯ tests/lib/git.test.ts:9:1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 128, signal: null, output: [ null, '<Buffer(73) ...>', '<Buffer(962) ...>' ], pid: 2537, stdout: '<Buffer(73) ...>', stderr: '<Buffer(962) ...>' }

vi.mock("../../src/lib/files.js", () => ({
PROJECT_DIR: testDir,
}));

const git = await import("../../src/lib/git.js");

describe("git", () => {
it("getBranch returns a branch name", () => {
const branch = git.getBranch();
// Default branch after init is usually main or master
expect(typeof branch).toBe("string");
expect(branch.length).toBeGreaterThan(0);
});

it("getStatus returns string (empty for clean repo)", () => {
const status = git.getStatus();
expect(typeof status).toBe("string");
});

it("getRecentCommits returns commit lines", () => {
const commits = git.getRecentCommits(1);
expect(commits).toContain("init");
});

it("getLastCommit returns the init commit", () => {
const last = git.getLastCommit();
expect(last).toContain("init");
});

it("getLastCommitTime returns a date string", () => {
const time = git.getLastCommitTime();
// Should be parseable as a date
expect(new Date(time).getFullYear()).toBeGreaterThanOrEqual(2024);
});

it("getStagedFiles returns empty string for clean repo", () => {
const staged = git.getStagedFiles();
expect(staged).toBe("");
});

it("run handles invalid git command gracefully", () => {
const result = git.run(["not-a-real-command"]);
// Should return error string, not throw
expect(typeof result).toBe("string");
});

it("run handles timeout", () => {
// This should complete fast, just testing the timeout path exists
const result = git.run(["status"], { timeout: 5000 });
expect(typeof result).toBe("string");
});
});
100 changes: 100 additions & 0 deletions tests/lib/state.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { describe, it, expect, afterEach, vi } from "vitest";
import { mkdtempSync, rmSync, readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
import { join } from "path";
import { tmpdir } from "os";

const testDir = mkdtempSync(join(tmpdir(), "preflight-state-test-"));

vi.mock("../../src/lib/files.js", () => ({
PROJECT_DIR: testDir,
}));

const { loadState, saveState, appendLog, readLog, now, STATE_DIR } = await import("../../src/lib/state.js");

function ensureDir() {
if (!existsSync(STATE_DIR)) mkdirSync(STATE_DIR, { recursive: true });
}

describe("state", () => {
afterEach(() => {
try { rmSync(STATE_DIR, { recursive: true, force: true }); } catch {}
});

describe("loadState", () => {
it("returns empty object for missing file", () => {
expect(loadState("nonexistent")).toEqual({});
});

it("returns empty object for corrupt JSON", () => {
ensureDir();
writeFileSync(join(STATE_DIR, "corrupt.json"), "not json{{{");
expect(loadState("corrupt")).toEqual({});
});

it("loads valid state", () => {
ensureDir();
const data = { foo: "bar", count: 42 };
writeFileSync(join(STATE_DIR, "valid.json"), JSON.stringify(data));
expect(loadState("valid")).toEqual(data);
});
});

describe("saveState", () => {
it("creates state dir and writes file", () => {
saveState("test", { hello: "world" });
const content = JSON.parse(readFileSync(join(STATE_DIR, "test.json"), "utf-8"));
expect(content).toEqual({ hello: "world" });
});

it("overwrites existing state", () => {
saveState("overwrite", { v: 1 });
saveState("overwrite", { v: 2 });
expect(JSON.parse(readFileSync(join(STATE_DIR, "overwrite.json"), "utf-8"))).toEqual({ v: 2 });
});
});

describe("appendLog / readLog", () => {
it("appends entries and reads them back", () => {
appendLog("test.jsonl", { action: "a" });
appendLog("test.jsonl", { action: "b" });
appendLog("test.jsonl", { action: "c" });
const entries = readLog("test.jsonl");
expect(entries).toHaveLength(3);
expect(entries[0]).toEqual({ action: "a" });
expect(entries[2]).toEqual({ action: "c" });
});

it("returns empty array for missing log", () => {
expect(readLog("missing.jsonl")).toEqual([]);
});

it("supports lastN parameter", () => {
appendLog("last.jsonl", { n: 1 });
appendLog("last.jsonl", { n: 2 });
appendLog("last.jsonl", { n: 3 });
const last2 = readLog("last.jsonl", 2);
expect(last2).toHaveLength(2);
expect(last2[0]).toEqual({ n: 2 });
expect(last2[1]).toEqual({ n: 3 });
});

it("skips corrupt lines gracefully", () => {
ensureDir();
writeFileSync(
join(STATE_DIR, "mixed.jsonl"),
'{"ok":true}\nnot json\n{"also":"ok"}\n'
);
const entries = readLog("mixed.jsonl");
expect(entries).toHaveLength(2);
expect(entries[0]).toEqual({ ok: true });
expect(entries[1]).toEqual({ also: "ok" });
});
});

describe("now", () => {
it("returns a valid ISO string", () => {
const ts = now();
expect(new Date(ts).toISOString()).toBe(ts);
});
});
});
Loading