Files
ab1b9e35d0 fix(entropy)!: own selected Windows path spelling at admission (#9393)
* fix(evidence)!: keep one Evidence identity across Windows path case

On Windows, an existing caller path now takes its filesystem spelling when
that spelling differs from the resolved path by letter case alone. One file
spelled `C:\Temp\x.bin`, `c:\temp\x.bin` or `C:\TEMP\X.BIN` produces one
`parameters.path`, `normalized_result` path and `evidence_id`, matching the
existing separator and `.` folding. `caller_input_path` keeps the input.

Links, junctions, `subst` drives, 8.3 short names and paths that do not
exist yet keep the caller spelling, so no link is followed and requested
output names keep their case.

BREAKING CHANGE: Windows Evidence produced from a path spelled in a case
other than its filesystem spelling now gets a different `evidence_id`.

Fixes #9373

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(entropy): keep Windows casing lookup optional

* ci(entropy): select Windows identity verification

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 20:16:13 +08:00

87 lines
3.1 KiB
TypeScript

import { expect, it, vi } from "vitest";
import { absoluteCallerPath } from "../../../src/filesystem/callerPath.js";
it("resolves absolute POSIX paths independently of an unavailable cwd", () => {
const cwd = vi.spyOn(process, "cwd").mockImplementation(() => {
throw new Error("working directory was removed");
});
try {
expect(
absoluteCallerPath("/cache//rea/kuna/.", { platform: "linux" }),
).toBe("/cache/rea/kuna/");
expect(
absoluteCallerPath("sample", { cwd: "/explicit", platform: "linux" }),
).toBe("/explicit/sample");
} finally {
cwd.mockRestore();
}
});
it("keeps POSIX parent components for the filesystem to resolve", () => {
const posix = { cwd: "/work", platform: "linux" } as const;
expect(absoluteCallerPath("link/../sample", posix)).toBe(
"/work/link/../sample",
);
expect(absoluteCallerPath("/abs/link/../sample", posix)).toBe(
"/abs/link/../sample",
);
expect(absoluteCallerPath("../sample", posix)).toBe("/work/../sample");
});
it("removes only POSIX spellings that cannot change the named file", () => {
const posix = { cwd: "/work/", platform: "darwin" } as const;
expect(absoluteCallerPath("./module.wasm", posix)).toBe("/work/module.wasm");
expect(absoluteCallerPath("a//./b", posix)).toBe("/work/a/b");
expect(absoluteCallerPath("/x/./y", posix)).toBe("/x/y");
expect(absoluteCallerPath("sample", { ...posix, cwd: "/" })).toBe("/sample");
expect(absoluteCallerPath("/", posix)).toBe("/");
// A trailing separator or `.` still requires a directory.
expect(absoluteCallerPath("sample/", posix)).toBe("/work/sample/");
expect(absoluteCallerPath("sample/.", posix)).toBe("/work/sample/");
expect(absoluteCallerPath("a//b//", posix)).toBe("/work/a/b/");
});
it("makes a relative POSIX cwd absolute before appending the path", () => {
const cwd = vi.spyOn(process, "cwd").mockReturnValue("/work");
try {
expect(
absoluteCallerPath("sample", { cwd: "fixtures/../x", platform: "linux" }),
).toBe("/work/fixtures/../x/sample");
} finally {
cwd.mockRestore();
}
});
it("uses lexical resolution on Windows, where it is exact", () => {
const win32 = { cwd: "C:\\work", platform: "win32" } as const;
expect(absoluteCallerPath("link\\..\\sample", win32)).toBe(
"C:\\work\\sample",
);
expect(absoluteCallerPath("D:\\x\\..\\y", win32)).toBe("D:\\y");
});
it("normalizes separator spellings of absolute Windows paths (#4707)", () => {
const win32 = { cwd: "C:\\work", platform: "win32" } as const;
for (const spelling of [
"C:\\Users\\u\\h.exe",
"C:/Users/u/h.exe",
"C:\\\\Users\\\\u\\\\h.exe",
"C://Users//u//h.exe",
])
expect(absoluteCallerPath(spelling, win32)).toBe("C:\\Users\\u\\h.exe");
});
it("keeps unselected Windows share paths lexical", () => {
expect(
absoluteCallerPath("\\\\unselected-server\\share\\Source.JS", {
platform: "win32",
}),
).toBe("\\\\unselected-server\\share\\Source.JS");
expect(
absoluteCallerPath("C:\\CaseSensitive\\Different.js", {
platform: "win32",
}),
).toBe("C:\\CaseSensitive\\Different.js");
});