mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
import { join } from "node:path";
|
||||
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 — existing lexical tests are unaffected.
|
||||
const realpathSync = vi.hoisted(() =>
|
||||
vi.fn<(path: string) => string>((p) => p),
|
||||
);
|
||||
vi.mock("node:fs", () => ({
|
||||
realpathSync,
|
||||
default: { realpathSync },
|
||||
}));
|
||||
|
||||
import {
|
||||
canonicalNormalizePathForComparison,
|
||||
getPathPolicyValues,
|
||||
normalizePathForComparison,
|
||||
normalizePathPolicyLiteral,
|
||||
} from "#src/access-intent/path-normalization";
|
||||
import { posixPathFlavor, win32PathFlavor } from "#src/path/path-flavor";
|
||||
|
||||
describe("normalizePathForComparison", () => {
|
||||
const cwd = "/projects/my-app";
|
||||
|
||||
test("resolves absolute path unchanged", () => {
|
||||
expect(
|
||||
normalizePathForComparison("/usr/local/bin", cwd, posixPathFlavor),
|
||||
).toBe("/usr/local/bin");
|
||||
});
|
||||
|
||||
test("resolves relative path against cwd", () => {
|
||||
expect(normalizePathForComparison("src/foo.ts", cwd, posixPathFlavor)).toBe(
|
||||
"/projects/my-app/src/foo.ts",
|
||||
);
|
||||
});
|
||||
|
||||
test("expands bare ~ to homedir", () => {
|
||||
expect(normalizePathForComparison("~", cwd, posixPathFlavor)).toBe(
|
||||
"/mock/home",
|
||||
);
|
||||
});
|
||||
|
||||
test("expands ~/... to homedir-relative path", () => {
|
||||
expect(
|
||||
normalizePathForComparison("~/docs/readme.md", cwd, posixPathFlavor),
|
||||
).toBe(join("/mock/home", "docs/readme.md"));
|
||||
});
|
||||
|
||||
test("expands bare $HOME to homedir", () => {
|
||||
expect(normalizePathForComparison("$HOME", cwd, posixPathFlavor)).toBe(
|
||||
"/mock/home",
|
||||
);
|
||||
});
|
||||
|
||||
test("expands $HOME/... to homedir-relative path", () => {
|
||||
expect(
|
||||
normalizePathForComparison("$HOME/.ssh/config", cwd, posixPathFlavor),
|
||||
).toBe(join("/mock/home", ".ssh/config"));
|
||||
});
|
||||
|
||||
test("strips leading @ before resolving", () => {
|
||||
expect(
|
||||
normalizePathForComparison("@/usr/local/bin", cwd, posixPathFlavor),
|
||||
).toBe("/usr/local/bin");
|
||||
});
|
||||
|
||||
test("strips surrounding quotes", () => {
|
||||
expect(
|
||||
normalizePathForComparison("'/usr/local/bin'", cwd, posixPathFlavor),
|
||||
).toBe("/usr/local/bin");
|
||||
expect(
|
||||
normalizePathForComparison('"/usr/local/bin"', cwd, posixPathFlavor),
|
||||
).toBe("/usr/local/bin");
|
||||
});
|
||||
|
||||
test("returns empty string for blank/whitespace-only path", () => {
|
||||
expect(normalizePathForComparison("", cwd, posixPathFlavor)).toBe("");
|
||||
expect(normalizePathForComparison(" ", cwd, posixPathFlavor)).toBe("");
|
||||
});
|
||||
|
||||
// ── injected platform flavor (Windows is case-folded, win32-resolved) ────
|
||||
|
||||
test("win32: lowercases the resolved absolute path", () => {
|
||||
expect(
|
||||
normalizePathForComparison(
|
||||
"C:\\Users\\Foo\\Bar.txt",
|
||||
"C:\\Projects",
|
||||
win32PathFlavor,
|
||||
),
|
||||
).toBe("c:\\users\\foo\\bar.txt");
|
||||
});
|
||||
|
||||
test("win32: resolves a relative path against cwd with win32 rules", () => {
|
||||
expect(
|
||||
normalizePathForComparison(
|
||||
"src\\foo.ts",
|
||||
"C:\\Projects\\App",
|
||||
win32PathFlavor,
|
||||
),
|
||||
).toBe("c:\\projects\\app\\src\\foo.ts");
|
||||
});
|
||||
|
||||
test("posix platform leaves case untouched", () => {
|
||||
expect(
|
||||
normalizePathForComparison("/Projects/App/Src.ts", cwd, posixPathFlavor),
|
||||
).toBe("/Projects/App/Src.ts");
|
||||
});
|
||||
});
|
||||
|
||||
describe("canonicalNormalizePathForComparison", () => {
|
||||
const cwd = "/projects/my-app";
|
||||
|
||||
beforeEach(() => {
|
||||
realpathSync.mockReset();
|
||||
realpathSync.mockImplementation((p: string) => p);
|
||||
});
|
||||
|
||||
test("returns canonical form of an existing path", () => {
|
||||
realpathSync.mockImplementation((p: string) => {
|
||||
if (p === "/projects/link") return "/real/projects/app";
|
||||
return p;
|
||||
});
|
||||
expect(
|
||||
canonicalNormalizePathForComparison(
|
||||
"/projects/link",
|
||||
cwd,
|
||||
posixPathFlavor,
|
||||
),
|
||||
).toBe("/real/projects/app");
|
||||
});
|
||||
|
||||
test("returns empty string for empty input", () => {
|
||||
expect(canonicalNormalizePathForComparison("", cwd, posixPathFlavor)).toBe(
|
||||
"",
|
||||
);
|
||||
});
|
||||
|
||||
test("returns lexical form when no symlinks (identity realpathSync)", () => {
|
||||
expect(
|
||||
canonicalNormalizePathForComparison(
|
||||
"/projects/my-app/src/index.ts",
|
||||
cwd,
|
||||
posixPathFlavor,
|
||||
),
|
||||
).toBe("/projects/my-app/src/index.ts");
|
||||
});
|
||||
|
||||
test("win32: lowercases the canonical form", () => {
|
||||
expect(
|
||||
canonicalNormalizePathForComparison(
|
||||
"C:\\Projects\\App\\Src",
|
||||
"C:\\Projects\\App",
|
||||
win32PathFlavor,
|
||||
),
|
||||
).toBe("c:\\projects\\app\\src");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizePathPolicyLiteral", () => {
|
||||
test("returns a relative token unchanged", () => {
|
||||
expect(normalizePathPolicyLiteral("src/foo.ts")).toBe("src/foo.ts");
|
||||
});
|
||||
|
||||
test("trims and strips simple wrapping quotes", () => {
|
||||
expect(normalizePathPolicyLiteral(" 'src/foo.ts' ")).toBe("src/foo.ts");
|
||||
expect(normalizePathPolicyLiteral('"a/b"')).toBe("a/b");
|
||||
});
|
||||
|
||||
test("strips a leading @ prefix", () => {
|
||||
expect(normalizePathPolicyLiteral("@src/foo.ts")).toBe("src/foo.ts");
|
||||
});
|
||||
|
||||
test("expands ~ to the home directory", () => {
|
||||
expect(normalizePathPolicyLiteral("~/docs/readme.md")).toBe(
|
||||
join("/mock/home", "docs/readme.md"),
|
||||
);
|
||||
});
|
||||
|
||||
test("does not resolve a relative value against any cwd", () => {
|
||||
expect(normalizePathPolicyLiteral("foo.ts")).toBe("foo.ts");
|
||||
});
|
||||
|
||||
test("returns empty string for blank input", () => {
|
||||
expect(normalizePathPolicyLiteral(" ")).toBe("");
|
||||
});
|
||||
|
||||
test("preserves the surface catch-all", () => {
|
||||
expect(normalizePathPolicyLiteral("*")).toBe("*");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPathPolicyValues", () => {
|
||||
const cwd = "/projects/my-app";
|
||||
|
||||
test("returns only the literal when no base is available", () => {
|
||||
expect(getPathPolicyValues("src/foo.ts", {}, posixPathFlavor)).toEqual([
|
||||
"src/foo.ts",
|
||||
]);
|
||||
expect(getPathPolicyValues("src/foo.ts", {}, posixPathFlavor)).toEqual([
|
||||
"src/foo.ts",
|
||||
]);
|
||||
});
|
||||
|
||||
test("adds absolute and project-relative aliases for a relative token", () => {
|
||||
expect(getPathPolicyValues("src/foo.ts", { cwd }, posixPathFlavor)).toEqual(
|
||||
["/projects/my-app/src/foo.ts", "src/foo.ts"],
|
||||
);
|
||||
});
|
||||
|
||||
test("omits the relative alias for a token outside cwd", () => {
|
||||
expect(getPathPolicyValues("/etc/hosts", { cwd }, posixPathFlavor)).toEqual(
|
||||
["/etc/hosts"],
|
||||
);
|
||||
});
|
||||
|
||||
test("resolves against resolveBase while aliasing relative to cwd", () => {
|
||||
expect(
|
||||
getPathPolicyValues(
|
||||
"foo.txt",
|
||||
{
|
||||
cwd,
|
||||
resolveBase: "/projects/my-app/nested",
|
||||
},
|
||||
posixPathFlavor,
|
||||
),
|
||||
).toEqual(["/projects/my-app/nested/foo.txt", "nested/foo.txt", "foo.txt"]);
|
||||
});
|
||||
|
||||
test("preserves the surface catch-all", () => {
|
||||
expect(getPathPolicyValues("*", { cwd }, posixPathFlavor)).toEqual(["*"]);
|
||||
});
|
||||
|
||||
test("returns empty for blank input", () => {
|
||||
expect(getPathPolicyValues(" ", { cwd }, posixPathFlavor)).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user