SPB Git

spb/khaelor Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

TypeScript 82.9% HTML 14.9% CSS 1.1% JavaScript 0.7%
3.9 KB · 99 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: tests/tools/glob.test.ts4 * Description: Unit tests for the glob tool — pattern matching, mtime ordering, ignore behavior, caps.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { readFileSync, rmSync, utimesSync } from "node:fs";11import { afterEach, beforeEach, describe, expect, it } from "vitest";12import { createGlobTool } from "../../src/tools/index.js";13import type { TestHarness } from "./helpers.js";14import { fixture, makeHarness } from "./helpers.js";1516let h: TestHarness;17const glob = createGlobTool();1819beforeEach(() => {20  h = makeHarness();21});2223afterEach(() => {24  rmSync(h.dir, { recursive: true, force: true });25});2627describe("glob tool", () => {28  it("matches nested files and orders newest first", async () => {29    const oldFile = fixture(h, "src/old.ts", "x\n");30    const newFile = fixture(h, "src/deep/new.ts", "x\n");31    fixture(h, "src/skip.py", "x\n");32    const now = Date.now() / 1000;33    utimesSync(oldFile, now - 1000, now - 1000);34    utimesSync(newFile, now, now);3536    const result = await glob.execute({ pattern: "**/*.ts" }, h.ctx);37    expect(result.isError).toBeUndefined();38    const lines = result.content.split("\n");39    expect(lines[0]).toBe('2 files match "**/*.ts" (newest first):');40    expect(lines[1]).toBe("src/deep/new.ts");41    expect(lines[2]).toBe("src/old.ts");42    expect(result.metadata?.files).toBe(2);43  });4445  it("treats a slash-less pattern as a basename match at any depth", async () => {46    fixture(h, "a/b/config.json", "{}\n");47    const result = await glob.execute({ pattern: "config.*" }, h.ctx);48    expect(result.content).toContain("a/b/config.json");49  });5051  it("ignores node_modules, .git-style noise, and .gitignore entries", async () => {52    fixture(h, ".gitignore", "coverage/\n");53    fixture(h, "node_modules/pkg/index.ts", "x\n");54    fixture(h, "dist/out.ts", "x\n");55    fixture(h, "coverage/cov.ts", "x\n");56    fixture(h, "src/keep.ts", "x\n");57    const result = await glob.execute({ pattern: "**/*.ts" }, h.ctx);58    expect(result.content).toContain("src/keep.ts");59    expect(result.content).not.toContain("node_modules");60    expect(result.content).not.toContain("dist/out.ts");61    expect(result.content).not.toContain("coverage/cov.ts");62    expect(result.metadata?.files).toBe(1);63  });6465  it("searches built-in ignored dirs when the pattern targets them explicitly", async () => {66    fixture(h, "dist/out.ts", "x\n");67    const result = await glob.execute({ pattern: "dist/**/*.ts" }, h.ctx);68    expect(result.content).toContain("dist/out.ts");69  });7071  it("reports zero matches with a nearest-extension hint", async () => {72    fixture(h, "src/a.ts", "x\n");73    fixture(h, "src/b.ts", "x\n");74    const result = await glob.execute({ pattern: "src/**/*.tsx" }, h.ctx);75    expect(result.isError).toBeUndefined();76    expect(result.content).toContain(`No files match "src/**/*.tsx" under ${h.dir}.`);77    expect(result.content).toContain("Nearest existing extension: .ts (2 files).");78  });7980  it("caps at 100 paths and spills the full list", async () => {81    for (let i = 0; i < 120; i++) fixture(h, `many/f${String(i).padStart(3, "0")}.ts`, "x\n");82    const result = await glob.execute({ pattern: "many/*.ts" }, h.ctx);83    expect(result.content).toContain('120 files match "many/*.ts" (newest first):');84    expect(result.content).toContain("[Showing 100 of 120. Full list: ");85    expect(result.content).toContain("or narrow the pattern.]");86    const spill = result.metadata?.truncation?.spillPath;87    expect(spill).toBeDefined();88    expect(readFileSync(spill!, "utf8").split("\n")).toHaveLength(120);89  });9091  it("scopes the search with the path parameter", async () => {92    fixture(h, "src/a.ts", "x\n");93    fixture(h, "other/b.ts", "x\n");94    const result = await glob.execute({ pattern: "*.ts", path: "src" }, h.ctx);95    expect(result.content).toContain("a.ts");96    expect(result.content).not.toContain("b.ts");97  });98});99