Continue validating child directories observed before a parent enumeration failure. Reconcile unobserved link targets at the reader using canonical path identity and the nearest failed ancestor, preserving exclusions and cancellation. Refs: #4013, #4053, #4060 Co-authored-by: XY-code1 <1611761391@qq.com>
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
import type { Dir, Dirent } from "node:fs";
|
|
import { mkdir, symlink, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { expect, it, vi } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { importReferenceSource } from "../../support/referenceSourceResourceScope.js";
|
|
|
|
const { opendirMock } = vi.hoisted(() => ({ opendirMock: vi.fn() }));
|
|
|
|
vi.mock("node:fs/promises", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("node:fs/promises")>()),
|
|
opendir: opendirMock,
|
|
}));
|
|
|
|
const directoryHandle = (items: readonly (string | Error | null)[]): Dir => {
|
|
let index = 0;
|
|
return {
|
|
async read() {
|
|
const item = items[index++];
|
|
if (item instanceof Error) throw item;
|
|
return item === null || item === undefined
|
|
? null
|
|
: ({ name: item } as Dirent);
|
|
},
|
|
async close() {},
|
|
} as unknown as Dir;
|
|
};
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"traverses directories lstat-observed before their parent enumeration failed",
|
|
async () => {
|
|
const root = await createTestTempDirectory(
|
|
"rea-queued-source-directories-",
|
|
);
|
|
const container = join(root, "container");
|
|
const hidden = join(container, "hidden");
|
|
await mkdir(hidden, { recursive: true });
|
|
await writeFile(join(container, "visible.txt"), "visible\n");
|
|
await writeFile(join(hidden, "target.txt"), "target\n");
|
|
await symlink("hidden/target.txt", join(container, "alias-file.txt"));
|
|
await symlink("hidden", join(container, "alias-directory"));
|
|
|
|
const readFailure = Object.assign(
|
|
new Error("injected directory read failure"),
|
|
{ code: "EIO" },
|
|
);
|
|
opendirMock.mockImplementation(async (path: string) => {
|
|
if (path === root) return directoryHandle(["container", null]);
|
|
if (path === container)
|
|
return directoryHandle([
|
|
"visible.txt",
|
|
"hidden",
|
|
"alias-file.txt",
|
|
"alias-directory",
|
|
readFailure,
|
|
]);
|
|
if (path === hidden) return directoryHandle(["target.txt", null]);
|
|
throw new Error(`Unexpected directory traversal: ${path}`);
|
|
});
|
|
|
|
const imported = await importReferenceSource({
|
|
root,
|
|
caller: "queued-directory-read-failure-test",
|
|
policy: { secretPatterns: [] },
|
|
});
|
|
if (!imported.ok) throw new Error(imported.error.message);
|
|
expect(imported.value.inventory_state).toBe("partial");
|
|
expect(imported.value.entries).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
path: "container",
|
|
kind: "directory",
|
|
tree_state: "unreadable",
|
|
}),
|
|
expect.objectContaining({
|
|
path: "container/hidden",
|
|
kind: "directory",
|
|
tree_state: "enumerated",
|
|
}),
|
|
expect.objectContaining({
|
|
path: "container/visible.txt",
|
|
kind: "file",
|
|
content_state: "hashed",
|
|
}),
|
|
expect.objectContaining({
|
|
path: "container/hidden/target.txt",
|
|
kind: "file",
|
|
content_state: "hashed",
|
|
}),
|
|
expect.objectContaining({
|
|
path: "container/alias-file.txt",
|
|
kind: "symlink",
|
|
target: "hidden/target.txt",
|
|
target_state: "internal",
|
|
}),
|
|
expect.objectContaining({
|
|
path: "container/alias-directory",
|
|
kind: "symlink",
|
|
target: "hidden",
|
|
target_state: "internal",
|
|
}),
|
|
]),
|
|
);
|
|
},
|
|
);
|