Require a caller-owned artifact resource scope for source traversal and Git metadata reads. Preserve cancellation, filesystem reasons, partial entries and bytes, and cleanup observations through import and the CLI. Transfer exact failed file owners at regular-file admission and close boundaries, including multiple failures encountered by Git reference resolution. Validation: 169 focused tests passed across 26 files; two Windows-only cases skipped. Build, typecheck, lint, formatting, knip, architecture guards, discovery and docs checks passed. BREAKING CHANGE: Reference source readers and imports require an ArtifactResourceScope. VCS metadata reads return Result, and CLI import failures include observed details and partial evidence.
84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import {
|
|
importReferenceSource,
|
|
readReferenceSource,
|
|
} from "../../support/referenceSourceResourceScope.js";
|
|
|
|
const importTree = (root: string) =>
|
|
importReferenceSource({
|
|
root,
|
|
caller: "native-unreadable-directory-test",
|
|
policy: { secretPatterns: [] },
|
|
});
|
|
|
|
const permissionChecksUnavailable =
|
|
process.platform === "win32" || process.getuid?.() === 0;
|
|
|
|
describe("reference source native unreadable directories", () => {
|
|
it.skipIf(permissionChecksUnavailable).each(["blocked", "src/blocked"])(
|
|
"preserves a partial graph when %s cannot be enumerated",
|
|
async (blocked) => {
|
|
const root = await createTestTempDirectory("rea-source-unreadable-dir-");
|
|
const deniedPath = join(root, blocked);
|
|
await mkdir(deniedPath, { recursive: true });
|
|
await writeFile(join(root, "main.ts"), "export const value = 1;\n");
|
|
await chmod(deniedPath, 0);
|
|
try {
|
|
const read = await readReferenceSource(root);
|
|
if (!read.ok) throw new Error(read.error.message);
|
|
const directory = read.value.entries.filter(
|
|
({ path }) => path === blocked,
|
|
);
|
|
expect(directory).toHaveLength(1);
|
|
expect(directory[0]).toMatchObject({
|
|
status: "failed",
|
|
kind: "directory",
|
|
code: "io",
|
|
message: expect.stringContaining("EACCES"),
|
|
});
|
|
const imported = await importTree(root);
|
|
if (!imported.ok) throw new Error(imported.error.message);
|
|
expect(imported.value.inventory_state).toBe("partial");
|
|
expect(
|
|
imported.value.entries.filter(({ path }) => path === blocked),
|
|
).toEqual([
|
|
expect.objectContaining({
|
|
kind: "directory",
|
|
tree_state: "unreadable",
|
|
limitations: [expect.any(String)],
|
|
}),
|
|
]);
|
|
expect(
|
|
imported.value.entries.find(({ path }) => path === "main.ts"),
|
|
).toMatchObject({ content_state: "hashed" });
|
|
} finally {
|
|
await chmod(deniedPath, 0o700);
|
|
}
|
|
},
|
|
);
|
|
|
|
it("keeps readable and empty directories enumerated exactly once", async () => {
|
|
const root = await createTestTempDirectory("rea-source-readable-dirs-");
|
|
await mkdir(join(root, "src", "empty"), { recursive: true });
|
|
await writeFile(join(root, "src", "main.ts"), "export const value = 1;\n");
|
|
const imported = await importTree(root);
|
|
if (!imported.ok) throw new Error(imported.error.message);
|
|
for (const path of ["src", "src/empty"]) {
|
|
expect(
|
|
imported.value.entries.filter((entry) => entry.path === path),
|
|
).toEqual([
|
|
expect.objectContaining({
|
|
kind: "directory",
|
|
tree_state: "enumerated",
|
|
limitations: [],
|
|
}),
|
|
]);
|
|
}
|
|
});
|
|
});
|