mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
301 lines
9.1 KiB
TypeScript
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("");
|
|
});
|
|
});
|