mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
BinaryTargetResolver kept a private copy of StableFileHash's descriptor read loop because it must hash the same descriptor it classified, admitted with symlinks rejected, while hashStableFile opens its own descriptor and follows symlinks. The loop is now hashStableRegularFileDescriptor, which takes the admitted descriptor and the caller's symlink setting; readStableFile and the resolver both use it. The resolver keeps symlinks: reject and maps a change to its existing BinaryTargetError and cancellation to AnalysisCancelledError.
81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { appendFile, symlink, truncate, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import {
|
|
RegularFileChangedError,
|
|
openRegularFile,
|
|
} from "../../../src/filesystem/RegularFile.js";
|
|
import { hashStableRegularFileDescriptor } from "../../../src/filesystem/StableFileHash.js";
|
|
|
|
const admitted = async (contents: string) => {
|
|
const directory = await createTestTempDirectory("rea-stable-descriptor-");
|
|
const path = join(directory, "target.bin");
|
|
await writeFile(path, contents);
|
|
const source = await openRegularFile(path, { symlinks: "reject" });
|
|
return { directory, path, source };
|
|
};
|
|
|
|
describe("hashStableRegularFileDescriptor", () => {
|
|
it("hashes the admitted bytes and captures the requested prefix", async () => {
|
|
const { path, source } = await admitted("abcdef");
|
|
try {
|
|
expect(
|
|
await hashStableRegularFileDescriptor(source, path, {
|
|
symlinks: "reject",
|
|
prefixBytes: 2,
|
|
}),
|
|
).toEqual({
|
|
sha256: createHash("sha256").update("abcdef").digest("hex"),
|
|
bytes: 6,
|
|
prefix: Buffer.from("ab"),
|
|
});
|
|
} finally {
|
|
await source.handle.close();
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
["grows", (path: string) => appendFile(path, "more")],
|
|
["shrinks", (path: string) => truncate(path, 2)],
|
|
])("refuses a file that %s after admission", async (_change, mutate) => {
|
|
const { path, source } = await admitted("abcdef");
|
|
try {
|
|
await mutate(path);
|
|
await expect(
|
|
hashStableRegularFileDescriptor(source, path, {
|
|
symlinks: "reject",
|
|
}),
|
|
).rejects.toBeInstanceOf(RegularFileChangedError);
|
|
} finally {
|
|
await source.handle.close();
|
|
}
|
|
});
|
|
|
|
// Target admission refuses a symbolic-link final component, while generic
|
|
// hashing follows one; the descriptor owner applies the caller's setting.
|
|
it.skipIf(process.platform === "win32")(
|
|
"applies the caller's symlink setting to a path that names a link",
|
|
async () => {
|
|
const admittedFile = await admitted("abcdef");
|
|
await admittedFile.source.handle.close();
|
|
const { directory, path } = admittedFile;
|
|
const link = join(directory, "link.bin");
|
|
await symlink(path, link);
|
|
const source = await openRegularFile(link, { symlinks: "follow" });
|
|
try {
|
|
await expect(
|
|
hashStableRegularFileDescriptor(source, link, { symlinks: "follow" }),
|
|
).resolves.toMatchObject({ bytes: 6 });
|
|
await expect(
|
|
hashStableRegularFileDescriptor(source, link, { symlinks: "reject" }),
|
|
).rejects.toBeInstanceOf(RegularFileChangedError);
|
|
} finally {
|
|
await source.handle.close();
|
|
}
|
|
},
|
|
);
|
|
});
|