add pi-graphify extension
This commit is contained in:
226
extensions/pi-graphify/src/lib/runner.test.ts
Normal file
226
extensions/pi-graphify/src/lib/runner.test.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { detectFiles, detectPython, ensureGraphifyGitignore, ensureInstalled } from "./runner";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock exec function
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createMockExec(
|
||||
responses: Record<string, { stdout: string; stderr: string; exitCode: number }>,
|
||||
) {
|
||||
return vi.fn(async (cmd: string, _opts?: unknown) => {
|
||||
for (const [pattern, response] of Object.entries(responses)) {
|
||||
if (cmd.includes(pattern)) return response;
|
||||
}
|
||||
return { stdout: "", stderr: "unknown command", exitCode: 1 };
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// detectPython
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("detectPython", () => {
|
||||
it("returns cached python from graphify-out/.graphify_python", async () => {
|
||||
const mockExec = createMockExec({
|
||||
"cat graphify-out/.graphify_python": {
|
||||
stdout: "/usr/bin/python3\n",
|
||||
stderr: "",
|
||||
exitCode: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await detectPython(mockExec, "python3", "/tmp/test");
|
||||
expect(result).toBe("/usr/bin/python3");
|
||||
});
|
||||
|
||||
it("detects python from graphify shebang when no cache", async () => {
|
||||
const mockExec = createMockExec({
|
||||
"cat graphify-out": { stdout: "", stderr: "", exitCode: 1 },
|
||||
"which graphify": { stdout: "/usr/local/bin/graphify\n", stderr: "", exitCode: 0 },
|
||||
"head -1": { stdout: "#!/usr/local/bin/python3.11\n", stderr: "", exitCode: 0 },
|
||||
});
|
||||
|
||||
const result = await detectPython(mockExec, "python3", "/tmp/test");
|
||||
expect(result).toBe("/usr/local/bin/python3.11");
|
||||
});
|
||||
|
||||
it("falls back to config python when graphify not found", async () => {
|
||||
const mockExec = createMockExec({
|
||||
"cat graphify-out": { stdout: "", stderr: "", exitCode: 1 },
|
||||
"which graphify": { stdout: "", stderr: "", exitCode: 1 },
|
||||
});
|
||||
|
||||
const result = await detectPython(mockExec, "python3.12", "/tmp/test");
|
||||
expect(result).toBe("python3.12");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ensureInstalled
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("ensureInstalled", () => {
|
||||
it("skips install when graphify is importable", async () => {
|
||||
const mockExec = vi.fn(async (cmd: string, _opts?: unknown) => {
|
||||
// All calls return success
|
||||
if (cmd.includes("import graphify")) {
|
||||
return { stdout: "0", stderr: "", exitCode: 0 };
|
||||
}
|
||||
return { stdout: "", stderr: "", exitCode: 0 };
|
||||
});
|
||||
|
||||
await ensureInstalled(mockExec, "python3", "/tmp/test");
|
||||
// Only called once (the initial check), no pip install
|
||||
expect(mockExec).toHaveBeenCalledTimes(1);
|
||||
expect(mockExec).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining("pip install"),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it("installs graphifyy when not importable", async () => {
|
||||
let callIdx = 0;
|
||||
const mockExec = vi.fn(async (cmd: string, _opts?: unknown) => {
|
||||
callIdx++;
|
||||
// First call: check import → fails
|
||||
if (callIdx === 1 && cmd.includes("import graphify")) {
|
||||
return { stdout: "1", stderr: "", exitCode: 0 };
|
||||
}
|
||||
// Second call: pip install → succeeds
|
||||
if (cmd.includes("pip install")) {
|
||||
return { stdout: "", stderr: "", exitCode: 0 };
|
||||
}
|
||||
// Third call: verify → succeeds
|
||||
if (callIdx === 3 && cmd.includes("import graphify")) {
|
||||
return { stdout: "0", stderr: "", exitCode: 0 };
|
||||
}
|
||||
return { stdout: "", stderr: "", exitCode: 0 };
|
||||
});
|
||||
|
||||
await ensureInstalled(mockExec, "python3", "/tmp/test");
|
||||
expect(mockExec).toHaveBeenCalledWith(
|
||||
expect.stringContaining("pip install graphifyy"),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// detectFiles
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("detectFiles", () => {
|
||||
it("parses detection output correctly", async () => {
|
||||
const detectionResult = {
|
||||
total_files: 5,
|
||||
total_words: 1200,
|
||||
files: {
|
||||
code: ["/tmp/test/main.ts", "/tmp/test/util.ts"],
|
||||
document: ["/tmp/test/README.md"],
|
||||
paper: [],
|
||||
image: [],
|
||||
video: [],
|
||||
},
|
||||
};
|
||||
|
||||
const mockExec = createMockExec({
|
||||
"from graphify.detect import detect": {
|
||||
stdout: JSON.stringify(detectionResult),
|
||||
stderr: "",
|
||||
exitCode: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await detectFiles(mockExec, "python3", "/tmp/test", "/tmp/test");
|
||||
expect(result.total_files).toBe(5);
|
||||
expect(result.total_words).toBe(1200);
|
||||
expect(result.files.code).toHaveLength(2);
|
||||
expect(result.files.document).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("throws on detection failure", async () => {
|
||||
const mockExec = createMockExec({
|
||||
"from graphify.detect import detect": {
|
||||
stdout: "",
|
||||
stderr: "ModuleNotFoundError: No module named 'graphify'",
|
||||
exitCode: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(detectFiles(mockExec, "python3", "/tmp/test", "/tmp/test")).rejects.toThrow(
|
||||
"graphify detect failed",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ensureGraphifyGitignore
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("ensureGraphifyGitignore", () => {
|
||||
it("creates .gitignore with graphify cache exclusions when missing", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "pi-graphify-test-"));
|
||||
|
||||
try {
|
||||
const result = await ensureGraphifyGitignore(dir);
|
||||
expect(result.updated).toBe(true);
|
||||
|
||||
const content = await readFile(join(dir, ".gitignore"), "utf-8");
|
||||
expect(content).toContain("graphify-out/cache/");
|
||||
expect(content).toContain("graphify-out/.graphify_python");
|
||||
expect(content).toContain("graphify-out/.graphify_root");
|
||||
expect(content).toContain("graphify-out/cost.json");
|
||||
expect(content).not.toContain("graphify-out/\n");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("removes legacy graphify-out/ ignore and preserves other entries", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "pi-graphify-test-"));
|
||||
|
||||
try {
|
||||
await writeFile(
|
||||
join(dir, ".gitignore"),
|
||||
"node_modules/\ngraphify-out/\ncustom-file.txt\n",
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const result = await ensureGraphifyGitignore(dir);
|
||||
expect(result.updated).toBe(true);
|
||||
|
||||
const content = await readFile(join(dir, ".gitignore"), "utf-8");
|
||||
expect(content).toContain("node_modules/");
|
||||
expect(content).toContain("custom-file.txt");
|
||||
expect(content).toContain("graphify-out/cache/");
|
||||
expect(content).toContain("graphify-out/.graphify_python");
|
||||
expect(content).toContain("graphify-out/.graphify_root");
|
||||
expect(content).toContain("graphify-out/cost.json");
|
||||
expect(content).not.toContain("\ngraphify-out/\n");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("is idempotent when required entries already exist", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "pi-graphify-test-"));
|
||||
|
||||
try {
|
||||
const expected =
|
||||
"node_modules/\ngraphify-out/cache/\ngraphify-out/.graphify_python\ngraphify-out/.graphify_root\ngraphify-out/cost.json\n";
|
||||
await writeFile(join(dir, ".gitignore"), expected, "utf-8");
|
||||
|
||||
const result = await ensureGraphifyGitignore(dir);
|
||||
expect(result.updated).toBe(false);
|
||||
|
||||
const content = await readFile(join(dir, ".gitignore"), "utf-8");
|
||||
expect(content).toBe(expected);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user