Files
reverse-engineering-anything/tests/boundary/cli/referenceSourceSymlinkPaths.test.ts
T

173 lines
5.6 KiB
TypeScript

import {
mkdir,
readFile,
readlink,
symlink,
unlink,
writeFile,
} from "node:fs/promises";
import { join } from "node:path";
import { z } from "zod";
import { expect } from "vitest";
import { historicalSourceGraphSchema } from "../../../src/domain/referenceSourceGraph.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest.skipIf(process.platform === "win32")(
"resolves source symlink targets through native traversal across retargeting and missing files",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-reference-native-links-");
const config = await createTestTempDirectory("rea-reference-link-config-");
const outside = await createTestTempDirectory(
"rea-reference-link-outside-",
);
for (const directory of [
join(root, "first"),
join(root, "second"),
outside,
]) {
await mkdir(join(directory, "deep"), { recursive: true });
await writeFile(
join(directory, "source.js"),
"export const selected = true;\n",
);
}
await writeFile(join(root, "source.js"), "export const decoy = true;\n");
const alias = join(root, "alias");
await symlink(join(root, "first/deep"), alias);
await symlink("alias/../source.js", join(root, "selected.js"));
await symlink("alias/../../source.js", join(root, "parent.js"));
await symlink("absent/../source.js", join(root, "missing-parent.js"));
await symlink("source.js/", join(root, "directory-required.js"));
await symlink("source.js/..", join(root, "file-parent.js"));
await symlink("source.js/../source.js", join(root, "file-parent-child.js"));
const run = async () => {
const result = await cli.run({
arguments: ["import-reference-source", root, "--json"],
environment: {
HOME: config,
XDG_CONFIG_HOME: config,
XDG_CACHE_HOME: config,
},
});
expect(result.exitCode, result.stderr + JSON.stringify(result.json)).toBe(
0,
);
return historicalSourceGraphSchema.parse(result.json);
};
const first = await run();
expect(first.entries).toEqual(
expect.arrayContaining([
expect.objectContaining({
path: "selected.js",
target: "first/source.js",
target_state: "internal",
}),
expect.objectContaining({
path: "parent.js",
target: "source.js",
target_state: "internal",
}),
expect.objectContaining({
path: "missing-parent.js",
target: `${root}/absent/../source.js`,
target_state: "missing",
}),
expect.objectContaining({
path: "directory-required.js",
target: `${root}/source.js/`,
target_state: "missing",
}),
expect.objectContaining({
path: "file-parent.js",
target: `${root}/source.js/..`,
target_state: "missing",
}),
expect.objectContaining({
path: "file-parent-child.js",
target: `${root}/source.js/../source.js`,
target_state: "missing",
}),
]),
);
await unlink(alias);
await symlink(join(root, "second/deep"), alias);
const second = await run();
expect(second.entries).toContainEqual(
expect.objectContaining({
path: "selected.js",
target: "second/source.js",
target_state: "internal",
}),
);
await unlink(join(root, "second/source.js"));
const missing = await run();
expect(missing.entries).toContainEqual(
expect.objectContaining({
path: "selected.js",
target: "second/source.js",
target_state: "missing",
}),
);
await unlink(alias);
await symlink(join(outside, "deep"), alias);
const external = await run();
expect(external.entries).toContainEqual(
expect.objectContaining({
path: "selected.js",
target: join(outside, "source.js"),
target_state: "external",
}),
);
expect(
external.entries
.filter((entry) => entry.kind === "file")
.map((entry) => entry.path),
).toEqual(["first/source.js", "source.js"]);
expect(await readlink(join(root, "selected.js"))).toBe(
"alias/../source.js",
);
expect(await readFile(join(root, "source.js"), "utf8")).toBe(
"export const decoy = true;\n",
);
},
);
for (const target of ["sub/missing", "sub/missing/", "sub/"]) {
cliTest.skipIf(process.platform === "win32")(
`retains relocated source commitments for ${target}`,
async ({ cli }) => {
const work = await createTestTempDirectory("rea-reference-relocation-");
const commitments = [];
for (const side of ["left", "right"]) {
const root = join(work, side);
await mkdir(join(root, "sub"), { recursive: true });
await writeFile(join(root, "sub", "keep.txt"), "keep\n");
await symlink(target, join(root, "link"));
const reply = await cli.run({
arguments: [
"import-reference-source",
root,
"--detail=summary",
"--json",
],
});
expect(reply.exitCode, reply.stderr).toBe(0);
const parsed = z
.object({
manifest: z.object({
root_sha256: z.string(),
graph_sha256: z.string(),
manifest_id: z.string(),
}),
})
.parse(reply.json);
commitments.push(parsed.manifest);
expect(await readlink(join(root, "link"))).toBe(target);
}
expect(commitments[0]).toEqual(commitments[1]);
},
);
}