Files
codetyper.cli/test/path-matcher.test.ts
Carlos Gutierrez 187cc68304 Improve agent autonomy and diff view readability
Agent behavior improvements:
  - Add project context detection (tsconfig.json, pom.xml, etc.)
  - Enforce validation after changes (tsc --noEmit, mvn compile, etc.)
  - Run tests automatically - never ask "do you want me to run tests"
  - Complete full loop: create → type-check → test → confirm
  - Add command detection for direct execution (run tree, run ls)

  Diff view improvements:
  - Use darker backgrounds for added/removed lines
  - Add diffLineBgAdded, diffLineBgRemoved, diffLineText theme colors
  - Improve text visibility with white text on dark backgrounds
  - Update both React/Ink and SolidJS diff components

  Streaming fixes:
  - Fix tool call argument accumulation using OpenAI index field
  - Fix streaming content display after tool calls
  - Add consecutive error tracking to prevent token waste

  Other changes:
  - ESC to abort operations, Ctrl+C to exit
  - Fix model selection when provider changes in cascade mode
  - Add debug logging for troubleshooting
  - Move tests to root tests/ folder
  - Fix banner test GRADIENT_COLORS reference
2026-01-29 07:33:30 -05:00

159 lines
5.2 KiB
TypeScript

/**
* Unit tests for Path Pattern Matcher
*/
import { describe, it, expect } from "bun:test";
import {
matchesPathPattern,
matchesFilePattern,
isFileOpAllowedByIndex,
findMatchingFilePatterns,
generateFilePattern,
normalizePath,
isPathInDirectory,
} from "@services/permissions/matchers/path";
import { buildPatternIndex } from "@services/permissions/pattern-index";
import type { PermissionPattern } from "@/types/permissions";
describe("Path Pattern Matcher", () => {
describe("matchesPathPattern", () => {
it("should match wildcard pattern", () => {
expect(matchesPathPattern("/any/path/file.ts", "*")).toBe(true);
expect(matchesPathPattern("relative/file.js", "*")).toBe(true);
});
it("should match directory prefix pattern", () => {
expect(matchesPathPattern("src/file.ts", "src/*")).toBe(true);
expect(matchesPathPattern("src/nested/file.ts", "src/*")).toBe(true);
expect(matchesPathPattern("tests/file.ts", "src/*")).toBe(false);
});
it("should match extension pattern", () => {
expect(matchesPathPattern("file.ts", "*.ts")).toBe(true);
expect(matchesPathPattern("src/nested/file.ts", "*.ts")).toBe(true);
expect(matchesPathPattern("file.js", "*.ts")).toBe(false);
});
it("should match exact path", () => {
expect(matchesPathPattern("src/file.ts", "src/file.ts")).toBe(true);
expect(matchesPathPattern("src/other.ts", "src/file.ts")).toBe(false);
});
it("should match substring", () => {
expect(
matchesPathPattern("/path/to/config/settings.json", "config"),
).toBe(true);
});
});
describe("matchesFilePattern", () => {
it("should match with parsed pattern", () => {
const pattern: PermissionPattern = {
tool: "Read",
path: "*.ts",
};
expect(matchesFilePattern("file.ts", pattern)).toBe(true);
expect(matchesFilePattern("file.js", pattern)).toBe(false);
});
it("should return false for pattern without path", () => {
const pattern: PermissionPattern = {
tool: "Bash",
command: "git",
};
expect(matchesFilePattern("file.ts", pattern)).toBe(false);
});
});
describe("isFileOpAllowedByIndex", () => {
it("should check Read operations", () => {
const index = buildPatternIndex(["Read(*.ts)", "Read(src/*)"]);
expect(isFileOpAllowedByIndex("Read", "file.ts", index)).toBe(true);
expect(isFileOpAllowedByIndex("Read", "src/nested.js", index)).toBe(true);
expect(isFileOpAllowedByIndex("Read", "tests/file.js", index)).toBe(
false,
);
});
it("should check Write operations separately", () => {
const index = buildPatternIndex(["Read(*)", "Write(src/*)"]);
expect(isFileOpAllowedByIndex("Read", "any/file.ts", index)).toBe(true);
expect(isFileOpAllowedByIndex("Write", "any/file.ts", index)).toBe(false);
expect(isFileOpAllowedByIndex("Write", "src/file.ts", index)).toBe(true);
});
it("should return false for empty index", () => {
const index = buildPatternIndex([]);
expect(isFileOpAllowedByIndex("Read", "file.ts", index)).toBe(false);
});
});
describe("findMatchingFilePatterns", () => {
it("should find all matching patterns", () => {
const index = buildPatternIndex(["Read(*)", "Read(*.ts)", "Read(src/*)"]);
const matches = findMatchingFilePatterns("Read", "src/file.ts", index);
expect(matches.length).toBe(3);
});
it("should return empty for no matches", () => {
const index = buildPatternIndex(["Read(src/*)"]);
const matches = findMatchingFilePatterns("Read", "tests/file.ts", index);
expect(matches).toHaveLength(0);
});
});
describe("generateFilePattern", () => {
it("should generate extension-based pattern for common extensions", () => {
expect(generateFilePattern("Read", "file.ts")).toBe("Read(*.ts)");
expect(generateFilePattern("Write", "file.json")).toBe("Write(*.json)");
expect(generateFilePattern("Edit", "file.tsx")).toBe("Edit(*.tsx)");
});
it("should generate directory-based pattern when appropriate", () => {
expect(generateFilePattern("Read", "src/file.xyz")).toBe("Read(src/*)");
});
it("should fall back to basename", () => {
expect(generateFilePattern("Read", "Makefile")).toBe("Read(Makefile)");
});
});
describe("normalizePath", () => {
it("should normalize path separators", () => {
expect(normalizePath("src/file.ts")).toBe("src/file.ts");
expect(normalizePath("src//file.ts")).toBe("src/file.ts");
expect(normalizePath("./src/file.ts")).toBe("src/file.ts");
});
});
describe("isPathInDirectory", () => {
it("should check if path is in directory", () => {
expect(isPathInDirectory("/project/src/file.ts", "/project/src")).toBe(
true,
);
expect(
isPathInDirectory("/project/src/nested/file.ts", "/project/src"),
).toBe(true);
expect(isPathInDirectory("/project/tests/file.ts", "/project/src")).toBe(
false,
);
});
it("should not match partial directory names", () => {
expect(
isPathInDirectory("/project/src-backup/file.ts", "/project/src"),
).toBe(false);
});
});
});