Files
rea/src/application/javascript/JavaScriptApplicationService.test.ts
T
Kaoru0822 0a14bbbf05 fix(javascript): classify application input selection failures by cause
analyze_javascript_application reported every input-selection problem as
an artifact fault:

- a missing input path: "Artifact could not be read or written", with no
  path or reason;
- an unreadable selected directory: the same generic io failure;
- a regular file that is neither a directory nor an .asar, and a
  --artifact-format that contradicts the selection: "Artifact is invalid
  or has changed. Get a fresh copy and try again."

Report a missing path as an invalid input_path, an unreadable selection
as access_denied, a non-directory non-.asar file as unsupported_target
with the accepted inputs as remediation, and a contradicting format as an
invalid format with the observed kind as expected value. Failures inside
a selected artifact keep their existing classification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:13:17 +00:00

156 lines
5.5 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { analyzeJavaScriptApplication } from "./JavaScriptApplicationService.js";
describe("JavaScript application failure diagnostics", () => {
it("identifies the rejected result field in caller-visible diagnostics", async () => {
const inputPath = await createTestTempDirectory("rea-js-schema-failure-");
await writeFile(join(inputPath, "main.js"), "export const value = 1;\n");
const cause = new z.ZodError([
{
code: "custom",
path: ["semantic_graph", "nodes", 42, "application_node_ids", 0],
message: "Semantic node references an absent application node",
},
]);
const result = await analyzeJavaScriptApplication(
{ input_path: inputPath, format: "directory" },
{
progress: {
report: async (event) => {
if (event.terminal) throw cause;
},
},
},
);
if (result.ok) throw new Error("Expected analysis failure");
expect(result.error.cause).toBe(cause);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "unreadable_output",
details: {
operation: "analyze_javascript_application",
reason:
"Result schema rejected 1 issue at /semantic_graph/nodes/42/application_node_ids/0 (custom)",
},
});
});
it("retains unexpected failures and their input identity in caller-visible diagnostics", async () => {
const inputPath = await createTestTempDirectory("rea-js-failure-");
await writeFile(join(inputPath, "main.js"), "export const value = 1;\n");
const cause = new RangeError("Invalid string length");
// Retain the original failure locally, but never serialize its object graph.
cause.cause = cause;
const result = await analyzeJavaScriptApplication(
{ input_path: inputPath, format: "directory" },
{
progress: {
report: async (event) => {
if (event.terminal) throw cause;
},
},
},
);
if (result.ok) throw new Error("Expected analysis failure");
expect(result.error.cause).toBe(cause);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "execution_failure",
details: {
provider_id: "rea-javascript-application",
operation: "analyze_javascript_application",
diagnostics: {
input_path: inputPath,
error_name: "RangeError",
error_message: "Invalid string length",
},
},
});
const projection = projectAnalysisError(result.error);
expect(JSON.parse(JSON.stringify(projection))).toEqual(projection);
});
it("reports a missing selection as invalid input, not an engine failure", async () => {
const root = await createTestTempDirectory("rea-js-missing-");
const result = await analyzeJavaScriptApplication({
input_path: join(root, "missing"),
format: "directory",
});
if (result.ok) throw new Error("Expected missing artifact failure");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "invalid_request",
details: {
operation: "analyze_javascript_application",
issues: [{ path: ["input_path"], reason: "invalid_value" }],
},
});
});
it.each(["reported failure", null])(
"retains non-Error failures (%s)",
async (cause) => {
const inputPath = await createTestTempDirectory("rea-js-non-error-");
await writeFile(join(inputPath, "main.js"), "export const value = 1;\n");
const result = await analyzeJavaScriptApplication(
{ input_path: inputPath, format: "directory" },
{
progress: {
report: async (event) => {
if (event.terminal) throw cause;
},
},
},
);
if (result.ok) throw new Error("Expected analysis failure");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "execution_failure",
details: {
diagnostics: {
error_name: "UnknownError",
error_message:
typeof cause === "string"
? cause
: "JavaScript analysis failed with a non-Error value",
},
},
});
},
);
it.each([new Error("x".repeat(10_000)), "y".repeat(10_000)])(
"preserves long failure messages through JSON projection (%#)",
async (cause) => {
const inputPath = await createTestTempDirectory("rea-js-oversized-");
await writeFile(join(inputPath, "main.js"), "export const value = 1;\n");
const message = cause instanceof Error ? cause.message : cause;
const result = await analyzeJavaScriptApplication(
{ input_path: inputPath, format: "directory" },
{
progress: {
report: async (event) => {
if (event.terminal) throw cause;
},
},
},
);
if (result.ok) throw new Error("Expected analysis failure");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "execution_failure",
details: {
diagnostics: {
error_message: message,
},
},
});
const projection = projectAnalysisError(result.error);
expect(JSON.parse(JSON.stringify(projection))).toEqual(projection);
},
);
});