refactor(javascript): share the reconstruction input contract

Remove the reconstruction-only request schema and consume the canonical parsed
JavaScript application input. Production already owns parsing at the service
boundary; direct test seams now use that same schema instead of a weaker path
contract. Preserve format, filesystem, source selection and lifecycle validation.

Add a NUL-path regression for canonical admission and typed input-error
projection. Verified 63 focused cases plus static, build, docs, discovery and
duplication checks; no target execution or real-provider claims.
This commit is contained in:
morluto committed 2026-10-24 15:02:26 +08:00
1 parent d68d03e88f
commit bdf15b9bd5
5 files changed
+49 -31

No files matched your search

@@ -8,6 +8,7 @@ import { expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import {
analyzeJavaScriptApplication,
reconstructJavaScriptArtifact,
readJavaScriptArtifactFiles,
} from "../../support/javascriptApplicationScope.js";
@@ -384,6 +385,35 @@ it("retains every repeated content observation and containment path", async () =
});
}, 15_000);
it("rejects NUL paths through the public request contract before reconstruction", async () => {
const input = { input_path: "invalid\u0000.asar" };
await expect(reconstructJavaScriptArtifact(input)).rejects.toMatchObject({
name: "ZodError",
issues: [
{
code: "invalid_format",
path: ["input_path"],
message: "Local filesystem paths cannot contain NUL",
},
],
});
const result = await analyzeJavaScriptApplication(input);
if (result.ok) throw new Error("Expected invalid input path");
expect(result.error).toMatchObject({
_tag: "AnalysisInputError",
operation: "analyze_javascript_application",
issues: [
{
path: ["input_path"],
reason: "invalid_format",
expected: "regex",
message: "Local filesystem paths cannot contain NUL",
},
],
});
});
it("rejects traversal from a production reader seam and malformed ASAR containers", async () => {
const root = await fixtureDirectory();
const snapshot = await scanArtifactInventory(root);
@@ -8,6 +8,7 @@ import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js
import { ArtifactResourceScope } from "../../../src/artifacts/ArtifactResourceScope.js";
import { ArtifactReaderFailure } from "../../../src/artifacts/ArtifactReader.js";
import { reconstructJavaScriptArtifact } from "../../../src/application/javascript/JavaScriptArtifactReconstruction.js";
import { analyzeJavaScriptApplicationInputSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it("retains a failed nested reader through blocked work and scope cleanup", async () => {
@@ -40,7 +41,9 @@ it("retains a failed nested reader through blocked work and scope cleanup", asyn
},
);
const input = { input_path: bundle };
const input = analyzeJavaScriptApplicationInputSchema.parse({
input_path: bundle,
});
const firstFailure = await reconstructJavaScriptArtifact(input, scope).catch(
(cause: unknown) => cause,
);
+7 -2
View File
@@ -1,10 +1,10 @@
import { withArtifactResourceScope } from "../fixtures/artifactInventory.js";
import type { ExecutionOptions } from "../../src/application/AnalysisProvider.js";
import {
analyzeJavaScriptApplication as analyzeOwned,
type JavaScriptApplicationOptions,
} from "../../src/application/javascript/JavaScriptApplicationService.js";
import { reconstructJavaScriptArtifact as reconstructOwned } from "../../src/application/javascript/JavaScriptArtifactReconstruction.js";
import { analyzeJavaScriptApplicationInputSchema } from "../../src/domain/javascript/javascriptApplicationAnalysis.js";
import type { ProgressReporter } from "../../src/application/ProgressReporter.js";
import { readJavaScriptArtifactFiles as readOwned } from "../../src/artifacts/javascript/JavaScriptArtifactFiles.js";
import type { ArtifactReader } from "../../src/artifacts/ArtifactReader.js";
@@ -34,5 +34,10 @@ export const reconstructJavaScriptArtifact = (
progress?: ProgressReporter,
) =>
withArtifactResourceScope((scope) =>
reconstructOwned(rawInput, scope, signal, progress),
reconstructOwned(
analyzeJavaScriptApplicationInputSchema.parse(rawInput),
scope,
signal,
progress,
),
);