159 lines
5.7 KiB
TypeScript
159 lines
5.7 KiB
TypeScript
import { mkdir, writeFile } from "node:fs/promises";
|
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
import { expect, it } from "vitest";
|
|
import { LocalWebScriptArtifacts } from "../../../src/browser/assets/LocalWebScriptArtifacts.js";
|
|
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { webModuleArtifactsFixture } from "../../fixtures/webModuleTrace.js";
|
|
|
|
const materialize = async () => {
|
|
const root = join(
|
|
await createTestTempDirectory("rea-script-manifest-path-"),
|
|
"export with spaces",
|
|
);
|
|
const artifact = webModuleArtifactsFixture();
|
|
await mkdir(join(root, "files", "modules"), { recursive: true });
|
|
await mkdir(join(root, "nested"));
|
|
const sourcePath = join(root, "files", "modules", "main.js");
|
|
const manifestPath = join(root, "manifest.json");
|
|
await writeFile(sourcePath, artifact.source);
|
|
await writeFile(manifestPath, JSON.stringify(artifact.manifest));
|
|
return { root, artifact, sourcePath, manifestPath };
|
|
};
|
|
|
|
it("reads equivalent local manifest spellings without changing reported identity", async () => {
|
|
const fixture = await materialize();
|
|
const paths = new Set([
|
|
fixture.manifestPath,
|
|
fixture.manifestPath.replaceAll("\\", "/"),
|
|
`${fixture.root}${sep}nested${sep}..${sep}manifest.json`,
|
|
]);
|
|
const fromWorkingDirectory = relative(process.cwd(), fixture.manifestPath);
|
|
// A target on another Windows drive has no relative path from this cwd.
|
|
if (!isAbsolute(fromWorkingDirectory))
|
|
paths.add(`.${sep}${fromWorkingDirectory}`);
|
|
for (const operation of [
|
|
"trace_web_module_imports",
|
|
"trace_web_source_location",
|
|
] as const) {
|
|
for (const manifestPath of paths) {
|
|
const response = await new LocalWebScriptArtifacts(operation).load({
|
|
manifest_path: manifestPath,
|
|
script_index: 0,
|
|
});
|
|
if (!response.ok) throw response.error;
|
|
expect(response.value.source).toBe(fixture.artifact.source);
|
|
expect(response.value.sourceFile).toEqual({
|
|
...fixture.artifact.sourceFile,
|
|
path: fixture.sourcePath,
|
|
});
|
|
expect(response.value.manifestFile.path).toBe(manifestPath);
|
|
expect(response.value.manifest.output_directory).toBe(
|
|
fixture.artifact.manifest.output_directory,
|
|
);
|
|
expect(response.value.manifest.scripts[0]?.url).toBe(
|
|
fixture.artifact.manifest.scripts[0]?.url,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("still rejects changed source bytes after normalizing the selected manifest path", async () => {
|
|
const fixture = await materialize();
|
|
await writeFile(fixture.sourcePath, "tampered source");
|
|
const response = await new LocalWebScriptArtifacts(
|
|
"trace_web_module_imports",
|
|
).load({
|
|
manifest_path: `${fixture.root}/nested/../manifest.json`.replaceAll(
|
|
"\\",
|
|
"/",
|
|
),
|
|
script_index: 0,
|
|
});
|
|
if (response.ok) throw new Error("Expected source integrity mismatch");
|
|
expect(projectAnalysisError(response.error).code).toBe(
|
|
"artifact_integrity_mismatch",
|
|
);
|
|
});
|
|
|
|
const literalManifest = async () => {
|
|
const fixture = await materialize();
|
|
const selected = fixture.artifact.manifest.scripts[0];
|
|
if (selected?.content.state !== "exported")
|
|
throw new Error("Expected exported source");
|
|
selected.content.relative_path = "..\\sibling.js";
|
|
await writeFile(join(fixture.root, "sibling.js"), fixture.artifact.source);
|
|
if (process.platform !== "win32")
|
|
await writeFile(
|
|
join(fixture.root, "files", selected.content.relative_path),
|
|
fixture.artifact.source,
|
|
);
|
|
await writeFile(
|
|
fixture.manifestPath,
|
|
JSON.stringify(fixture.artifact.manifest),
|
|
);
|
|
return fixture;
|
|
};
|
|
|
|
it.each(["trace_web_module_imports", "trace_web_source_location"] as const)(
|
|
"refuses Windows backslash source interpretation for %s",
|
|
async (operation) => {
|
|
const fixture = await literalManifest();
|
|
const originalPlatform = Object.getOwnPropertyDescriptor(
|
|
process,
|
|
"platform",
|
|
);
|
|
const artifacts = new LocalWebScriptArtifacts(operation);
|
|
let response;
|
|
try {
|
|
// Exercise the Windows admission branch; this is not native Windows IO.
|
|
Object.defineProperty(process, "platform", { value: "win32" });
|
|
response = await artifacts.load({
|
|
manifest_path: fixture.manifestPath,
|
|
script_index: 0,
|
|
});
|
|
} finally {
|
|
if (originalPlatform !== undefined)
|
|
Object.defineProperty(process, "platform", originalPlatform);
|
|
await artifacts.close();
|
|
}
|
|
if (response.ok) throw new Error("Expected Windows source path refusal");
|
|
expect(projectAnalysisError(response.error)).toMatchObject({
|
|
code: "invalid_request",
|
|
details: {
|
|
issues: [
|
|
{
|
|
path: ["script_index"],
|
|
reason: "invalid_value",
|
|
message: expect.stringContaining(
|
|
"Windows source path cannot contain backslashes",
|
|
),
|
|
},
|
|
],
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"reads the same saved POSIX literal name without selecting its sibling",
|
|
async () => {
|
|
const fixture = await literalManifest();
|
|
await writeFile(join(fixture.root, "sibling.js"), "wrong sibling source");
|
|
const artifacts = new LocalWebScriptArtifacts("trace_web_module_imports");
|
|
try {
|
|
const result = await artifacts.load({
|
|
manifest_path: fixture.manifestPath,
|
|
script_index: 0,
|
|
});
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.source).toBe(fixture.artifact.source);
|
|
expect(result.value.sourceFile.path).toBe(
|
|
join(fixture.root, "files", "..\\sibling.js"),
|
|
);
|
|
} finally {
|
|
await artifacts.close();
|
|
}
|
|
},
|
|
);
|