Files
my-pi/pi-permission-system/test/access-intent/access-path.test.ts
T

301 lines
9.1 KiB
TypeScript

import { beforeEach, describe, expect, test, vi } from "vitest";
// Mock node:os so tilde-expansion is deterministic across platforms.
vi.mock("node:os", () => {
const homedir = vi.fn(() => "/mock/home");
return {
homedir,
default: { homedir },
};
});
// Mock node:fs so realpathSync (used by canonicalizePath) is controllable.
// Default implementation is identity — lexical tests are unaffected.
const realpathSync = vi.hoisted(() =>
vi.fn<(path: string) => string>((p) => p),
);
vi.mock("node:fs", () => ({
realpathSync,
default: { realpathSync },
}));
import { AccessPath } from "#src/access-intent/access-path";
import { posixPathFlavor, win32PathFlavor } from "#src/path/path-flavor";
describe("AccessPath.forPath", () => {
const cwd = "/projects/my-app";
beforeEach(() => {
realpathSync.mockReset();
realpathSync.mockImplementation((p: string) => p);
});
describe("matchValues()", () => {
test("adds the symlink-resolved alias alongside the typed path", () => {
// /tmp -> /private/tmp (the macOS symlink from the bug report, #418).
realpathSync.mockImplementation((p: string) =>
p.startsWith("/tmp") ? `/private${p}` : p,
);
expect(
AccessPath.forPath("/tmp/x", {
cwd,
flavor: posixPathFlavor,
}).matchValues(),
).toEqual(["/tmp/x", "/private/tmp/x"]);
});
test("deduplicates when the canonical form equals the lexical form", () => {
expect(
AccessPath.forPath("/etc/hosts", {
cwd,
flavor: posixPathFlavor,
}).matchValues(),
).toEqual(["/etc/hosts"]);
});
test("keeps the relative aliases for an in-cwd token without duplicating", () => {
expect(
AccessPath.forPath("src/foo.ts", {
cwd,
flavor: posixPathFlavor,
}).matchValues(),
).toEqual(["/projects/my-app/src/foo.ts", "src/foo.ts"]);
});
test("includes only the lexical aliases when canonical is empty", () => {
// Force canonicalizePath to return the original (no-op symlink resolution
// effectively means canonical === lexical, handled by dedup).
expect(
AccessPath.forPath("/etc/hosts", {
cwd,
flavor: posixPathFlavor,
}).matchValues(),
).not.toHaveLength(0);
});
test("resolves a relative token against an explicit resolveBase", () => {
// The cd-folded effective base differs from cwd (the bash-path case).
expect(
AccessPath.forPath("foo.ts", {
cwd,
resolveBase: "/projects/my-app/sub",
flavor: posixPathFlavor,
}).matchValues(),
).toEqual(["/projects/my-app/sub/foo.ts", "sub/foo.ts", "foo.ts"]);
});
test("adds the canonical alias resolved against resolveBase", () => {
realpathSync.mockImplementation((p: string) =>
p === "/projects/my-app/sub/foo.ts" ? "/real/foo.ts" : p,
);
expect(
AccessPath.forPath("foo.ts", {
cwd,
resolveBase: "/projects/my-app/sub",
flavor: posixPathFlavor,
}).matchValues(),
).toEqual([
"/projects/my-app/sub/foo.ts",
"sub/foo.ts",
"foo.ts",
"/real/foo.ts",
]);
});
});
describe("platform option", () => {
test("win32: builds lexical/match/boundary values with win32 rules", () => {
const ap = AccessPath.forPath("src\\foo.ts", {
cwd: "C:\\Projects\\App",
flavor: win32PathFlavor,
});
expect(ap.value()).toBe("c:\\projects\\app\\src\\foo.ts");
expect(ap.boundaryValue()).toBe("c:\\projects\\app\\src\\foo.ts");
expect(ap.matchValues()).toEqual([
"c:\\projects\\app\\src\\foo.ts",
"src\\foo.ts",
]);
});
test("win32: lowercases the symlink-resolved boundary value", () => {
realpathSync.mockImplementation((p: string) =>
p === "c:\\projects\\app\\link" ? "C:\\Real\\App" : p,
);
expect(
AccessPath.forPath("link", {
cwd: "C:\\Projects\\App",
flavor: win32PathFlavor,
}).boundaryValue(),
).toBe("c:\\real\\app");
});
});
describe("boundaryValue()", () => {
test("returns the canonical (symlink-resolved) form", () => {
realpathSync.mockImplementation((p: string) =>
p.startsWith("/tmp") ? `/private${p}` : p,
);
expect(
AccessPath.forPath("/tmp/x", {
cwd,
flavor: posixPathFlavor,
}).boundaryValue(),
).toBe("/private/tmp/x");
});
test("returns the lexical form when path has no symlinks", () => {
expect(
AccessPath.forPath("/etc/hosts", {
cwd,
flavor: posixPathFlavor,
}).boundaryValue(),
).toBe("/etc/hosts");
});
test("returns empty string for empty input", () => {
expect(
AccessPath.forPath("", {
cwd,
flavor: posixPathFlavor,
}).boundaryValue(),
).toBe("");
});
});
describe("value()", () => {
test("returns the lexical (as-typed, normalized) form", () => {
realpathSync.mockImplementation((p: string) =>
p.startsWith("/tmp") ? `/private${p}` : p,
);
// Even when the path resolves to a different canonical, value() stays lexical.
expect(
AccessPath.forPath("/tmp/x", { cwd, flavor: posixPathFlavor }).value(),
).toBe("/tmp/x");
});
test("normalizes the path against cwd", () => {
// A relative path becomes an absolute lexical value.
expect(
AccessPath.forPath("src/foo.ts", {
cwd,
flavor: posixPathFlavor,
}).value(),
).toBe("/projects/my-app/src/foo.ts");
});
test("normalizes a relative path against an explicit resolveBase", () => {
expect(
AccessPath.forPath("foo.ts", {
cwd,
resolveBase: "/projects/my-app/sub",
flavor: posixPathFlavor,
}).value(),
).toBe("/projects/my-app/sub/foo.ts");
});
test("returns empty string for empty input", () => {
expect(
AccessPath.forPath("", { cwd, flavor: posixPathFlavor }).value(),
).toBe("");
});
});
});
describe("resolvedAlias()", () => {
const cwd = "/projects/my-app";
beforeEach(() => {
realpathSync.mockReset();
realpathSync.mockImplementation((p: string) => p);
});
test("returns the canonical form when a symlink resolves elsewhere", () => {
realpathSync.mockImplementation((p: string) =>
p === "/projects/my-app/demo-symlink-passwd" ? "/etc/passwd" : p,
);
expect(
AccessPath.forPath("demo-symlink-passwd", {
cwd,
flavor: posixPathFlavor,
}).resolvedAlias(),
).toBe("/etc/passwd");
});
test("returns undefined when the path has no symlinks (canonical equals lexical)", () => {
expect(
AccessPath.forPath("/etc/hosts", {
cwd,
flavor: posixPathFlavor,
}).resolvedAlias(),
).toBeUndefined();
});
test("returns undefined for a literal-only path (no canonical)", () => {
expect(AccessPath.forLiteral("foo.ts").resolvedAlias()).toBeUndefined();
});
test("returns undefined for empty input", () => {
expect(
AccessPath.forPath("", { cwd, flavor: posixPathFlavor }).resolvedAlias(),
).toBeUndefined();
});
test("win32: returns the lowercased canonical form for a real symlink target", () => {
realpathSync.mockImplementation((p: string) =>
p === "c:\\projects\\app\\link" ? "C:\\Real\\App" : p,
);
expect(
AccessPath.forPath("link", {
cwd: "C:\\Projects\\App",
flavor: win32PathFlavor,
}).resolvedAlias(),
).toBe("c:\\real\\app");
});
test("win32: returns undefined for a case-only difference (both forms lowercased)", () => {
expect(
AccessPath.forPath("src\\foo.ts", {
cwd: "C:\\Projects\\App",
flavor: win32PathFlavor,
}).resolvedAlias(),
).toBeUndefined();
});
});
describe("AccessPath.forDevice", () => {
test("lexical, boundary, and match values are all the device path", () => {
const ap = AccessPath.forDevice("/dev/null");
expect(ap.value()).toBe("/dev/null");
expect(ap.boundaryValue()).toBe("/dev/null");
expect(ap.matchValues()).toEqual(["/dev/null"]);
});
test("resolvedAlias is undefined (canonical equals lexical)", () => {
expect(AccessPath.forDevice("/dev/null").resolvedAlias()).toBeUndefined();
});
});
describe("AccessPath.forLiteral", () => {
beforeEach(() => {
realpathSync.mockReset();
realpathSync.mockImplementation((p: string) => p);
});
test("matchValues() carries only the literal — no canonical, no absolute", () => {
expect(AccessPath.forLiteral("foo.ts").matchValues()).toEqual(["foo.ts"]);
});
test("boundaryValue() is empty (no outside-cwd notion for an unknown base)", () => {
expect(AccessPath.forLiteral("foo.ts").boundaryValue()).toBe("");
});
test("value() returns the literal", () => {
expect(AccessPath.forLiteral("foo.ts").value()).toBe("foo.ts");
});
test("an empty literal yields no match values", () => {
expect(AccessPath.forLiteral("").matchValues()).toEqual([]);
expect(AccessPath.forLiteral("").value()).toBe("");
});
});