import { describe, expect, it } from "vitest"; import { AccessPath } from "#src/access-intent/access-path"; import { resolveExternalDirectoryPolicy, selectUncoveredExternalPaths, } from "#src/handlers/gates/external-directory-policy"; import { posixPathFlavor } from "#src/path/path-flavor"; import type { PermissionCheckResult } from "#src/types"; import { makeResolver } from "#test/helpers/gate-fixtures"; const cwd = "/test/project"; function makeCheckResult( state: "allow" | "deny" | "ask", overrides: Partial = {}, ): PermissionCheckResult { return { state, toolName: "external_directory", source: "special", origin: "builtin", ...overrides, }; } describe("resolveExternalDirectoryPolicy", () => { it("resolves the path's match aliases on the external_directory surface (#418)", () => { const path = AccessPath.forPath("/outside/a.ts", { cwd, flavor: posixPathFlavor, }); const resolver = makeResolver(makeCheckResult("ask")); const result = resolveExternalDirectoryPolicy(path, resolver, undefined); expect(resolver.resolve).toHaveBeenCalledWith({ kind: "access-path", surface: "external_directory", path, agentName: undefined, }); expect(result).toEqual(makeCheckResult("ask")); }); it("threads the agent name through to the resolver", () => { const path = AccessPath.forPath("/outside/a.ts", { cwd, flavor: posixPathFlavor, }); const resolver = makeResolver(makeCheckResult("allow")); resolveExternalDirectoryPolicy(path, resolver, "reviewer"); expect(resolver.resolve).toHaveBeenCalledWith({ kind: "access-path", surface: "external_directory", path, agentName: "reviewer", }); }); }); describe("selectUncoveredExternalPaths", () => { it("returns no uncovered paths when every path resolves to allow", () => { const paths = [ AccessPath.forPath("/outside/a.ts", { cwd, flavor: posixPathFlavor }), AccessPath.forPath("/outside/b.ts", { cwd, flavor: posixPathFlavor }), ]; const resolver = makeResolver(makeCheckResult("allow")); const { uncovered, worstCheck } = selectUncoveredExternalPaths( paths, resolver, undefined, ); expect(uncovered).toEqual([]); expect(worstCheck).toBeUndefined(); }); it("collects only paths whose resolved state is not allow", () => { const allowed = AccessPath.forPath("/outside/ok.ts", { cwd, flavor: posixPathFlavor, }); const asked = AccessPath.forPath("/outside/ask.ts", { cwd, flavor: posixPathFlavor, }); const resolver = makeResolver(); resolver.resolve.mockImplementation((intent) => { const values = intent.kind === "access-path" ? intent.path.matchValues() : []; return values.includes("/outside/ok.ts") ? makeCheckResult("allow") : makeCheckResult("ask"); }); const { uncovered } = selectUncoveredExternalPaths( [allowed, asked], resolver, undefined, ); expect(uncovered.map(({ path }) => path.value())).toEqual([asked.value()]); }); it("returns the most restrictive uncovered check as worstCheck (deny > ask)", () => { const asked = AccessPath.forPath("/outside/ask.ts", { cwd, flavor: posixPathFlavor, }); const denied = AccessPath.forPath("/outside/deny.ts", { cwd, flavor: posixPathFlavor, }); const resolver = makeResolver(); resolver.resolve.mockImplementation((intent) => { const values = intent.kind === "access-path" ? intent.path.matchValues() : []; return values.includes("/outside/deny.ts") ? makeCheckResult("deny") : makeCheckResult("ask"); }); const { worstCheck } = selectUncoveredExternalPaths( [asked, denied], resolver, undefined, ); expect(worstCheck?.state).toBe("deny"); }); });