fix(workflows): keep caller-input details in workflow input errors (#1089)
* fix(managed): keep caller-input details in managed workflow errors
The managed workflows wrapped every rejected input as
`new AnalysisInputError(op, { cause })` with no issues. The projected
result said "Correct the listed arguments and retry" and listed nothing:
- `rea import-managed-reconstruction`,
`rea project-managed-application-graph` and
`rea verify-managed-native-boundaries` lost every schema issue for
malformed JSON;
- domain checks such as a reconstruction text hash mismatch, or Evidence
from the wrong operation, lost their message on CLI and MCP;
- `rea compare-managed-members` with a missing file did not say which
path failed.
Add managedInputError, which projects Zod issues with their paths and
keeps a domain TypeError's message, as the JavaScript workflows already
do. Return the BinaryTargetError from an unopenable comparison path
unchanged, with details.path, as every other path-based command does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(managed): keep nested Evidence schema issues off request paths
A ZodError raised after the request parsed comes from a nested
Evidence result, so its issue paths are relative to that result.
Projecting it against the whole request reported, for example,
artifact.sha256 as a missing request field. Keep request-relative
projection for the raw request's schema failures. Report nested schema
failures as root issues whose message names the nested location.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(managed): type the malformed nested Evidence fixture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(workflows): keep input details in the remaining workflow services
Inventory projection (project_android_application_graph and
project_apple_application_graph), build_reconstruction_obligation_ledger
and evaluate_reconstruction_coverage had the same issue-less
`AnalysisInputError({ cause })` as the managed services. For example,
evaluating coverage for an unknown boundary returned "Correct the listed
arguments" with no issues, dropping "Unknown reconstruction boundary: X"
on CLI and MCP.
Move the helpers to src/application/workflowInputError.ts and use them
for these services: requestInputError for raw request schema failures,
workflowInputError for nested schema and domain constraint failures.
Classification is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a1ed4232de
commit
289aef6838
|
|
@ -1,9 +1,6 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisProtocolError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { AnalysisProtocolError } from "../domain/analysisErrorCore.js";
|
||||
import { type AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
createEvidence,
|
||||
|
|
@ -13,6 +10,7 @@ import {
|
|||
} from "../domain/evidence.js";
|
||||
import { jsonValueSchema } from "../domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import { requestInputError, workflowInputError } from "./workflowInputError.js";
|
||||
|
||||
interface InventoryProjectionInput {
|
||||
readonly inventory_evidence: readonly Evidence[];
|
||||
|
|
@ -49,7 +47,7 @@ export const projectInventoryEvidence = <
|
|||
const parsed = options.schema.safeParse(options.rawInput);
|
||||
if (!parsed.success)
|
||||
return err(
|
||||
new AnalysisInputError(options.operation, { cause: parsed.error }),
|
||||
requestInputError(options.operation, parsed.error, options.rawInput),
|
||||
);
|
||||
try {
|
||||
const result = options.project(parsed.data);
|
||||
|
|
@ -86,7 +84,7 @@ export const projectInventoryEvidence = <
|
|||
} catch (cause: unknown) {
|
||||
return err(
|
||||
cause instanceof TypeError || cause instanceof z.ZodError
|
||||
? new AnalysisInputError(options.operation, { cause })
|
||||
? workflowInputError(options.operation, cause)
|
||||
: new AnalysisProtocolError(options.protocolError, { cause }),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { reconstructionCoverageEvaluationInputSchema } from "../domain/reconstructionCoverageInput.js";
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import { type AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { evaluateReconstructionClosure } from "../domain/reconstructionCoverage.js";
|
||||
import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import { requestInputError, workflowInputError } from "./workflowInputError.js";
|
||||
|
||||
const OPERATION = "evaluate_reconstruction_coverage";
|
||||
|
||||
/** Evaluate inline reconstruction coverage against a named boundary. */
|
||||
export const evaluateReconstructionCoverage = (
|
||||
|
|
@ -13,11 +18,7 @@ export const evaluateReconstructionCoverage = (
|
|||
const parsed =
|
||||
reconstructionCoverageEvaluationInputSchema.safeParse(rawInput);
|
||||
if (!parsed.success)
|
||||
return err(
|
||||
new AnalysisInputError("evaluate_reconstruction_coverage", {
|
||||
cause: parsed.error,
|
||||
}),
|
||||
);
|
||||
return err(requestInputError(OPERATION, parsed.error, rawInput));
|
||||
try {
|
||||
return ok(
|
||||
jsonValueSchema.parse(
|
||||
|
|
@ -30,7 +31,9 @@ export const evaluateReconstructionCoverage = (
|
|||
);
|
||||
} catch (cause: unknown) {
|
||||
return err(
|
||||
new AnalysisInputError("evaluate_reconstruction_coverage", { cause }),
|
||||
cause instanceof TypeError || cause instanceof z.ZodError
|
||||
? workflowInputError(OPERATION, cause)
|
||||
: new AnalysisInputError(OPERATION, { cause }),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisProtocolError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { AnalysisProtocolError } from "../domain/analysisErrorCore.js";
|
||||
import { type AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { createEvidence, type Evidence } from "../domain/evidence.js";
|
||||
import { jsonObjectSchema, jsonValueSchema } from "../domain/jsonValue.js";
|
||||
|
|
@ -14,6 +11,7 @@ import {
|
|||
type ReconstructionObligationLedger,
|
||||
} from "../domain/reconstructionObligationLedgerSchemas.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import { requestInputError, workflowInputError } from "./workflowInputError.js";
|
||||
import { JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER } from "./InvestigationProviders.js";
|
||||
import { deriveReconstructionObligationCandidates } from "./ReconstructionObligationCandidates.js";
|
||||
import { evaluateReconstructionObligationLedger } from "./ReconstructionObligationLedgerEvaluation.js";
|
||||
|
|
@ -27,7 +25,7 @@ export const resolveReconstructionObligationLedgerRequest = (
|
|||
const parsed = reconstructionObligationLedgerInputSchema.safeParse(input);
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: err(new AnalysisInputError(OPERATION, { cause: parsed.error }));
|
||||
: err(requestInputError(OPERATION, parsed.error, input));
|
||||
};
|
||||
|
||||
/** Build the complete deterministic ledger and wrap it in portable Evidence. */
|
||||
|
|
@ -48,7 +46,7 @@ export const buildReconstructionObligationLedgerEvidenceValidated = (
|
|||
return ok(createLedgerEvidence(input, ledger));
|
||||
} catch (cause: unknown) {
|
||||
if (cause instanceof z.ZodError)
|
||||
return err(new AnalysisInputError(OPERATION, { cause }));
|
||||
return err(workflowInputError(OPERATION, cause));
|
||||
return err(
|
||||
new AnalysisProtocolError(
|
||||
"Reconstruction obligation ledger generation failed",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisProtocolError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { AnalysisProtocolError } from "../../domain/analysisErrorCore.js";
|
||||
import { type AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
|
|
@ -16,6 +13,10 @@ import {
|
|||
} from "../../domain/managed/managedApplicationGraph.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import { MANAGED_WORKFLOW_PROVIDER } from "../InvestigationProviders.js";
|
||||
import {
|
||||
requestInputError,
|
||||
workflowInputError,
|
||||
} from "../workflowInputError.js";
|
||||
|
||||
const OPERATION = "project_managed_application_graph" as const;
|
||||
|
||||
|
|
@ -25,14 +26,14 @@ export const projectManagedApplicationGraphEvidence = (
|
|||
): Result<Evidence, AnalysisError> => {
|
||||
const parsed = projectManagedApplicationGraphInputSchema.safeParse(rawInput);
|
||||
if (!parsed.success)
|
||||
return err(new AnalysisInputError(OPERATION, { cause: parsed.error }));
|
||||
return err(requestInputError(OPERATION, parsed.error, rawInput));
|
||||
try {
|
||||
const result = projectManagedApplicationGraph(parsed.data);
|
||||
return ok(createManagedApplicationGraphEvidence(parsed.data, result));
|
||||
} catch (cause: unknown) {
|
||||
return err(
|
||||
cause instanceof TypeError || cause instanceof z.ZodError
|
||||
? new AnalysisInputError(OPERATION, { cause })
|
||||
? workflowInputError(OPERATION, cause)
|
||||
: new AnalysisProtocolError(
|
||||
"Managed application graph projection produced an invalid result",
|
||||
{ cause },
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ import {
|
|||
parseManagedMemberEvidence,
|
||||
type CompareManagedMembersInput,
|
||||
} from "../../domain/managed/managedMemberComparison.js";
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisProtocolError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { AnalysisProtocolError } from "../../domain/analysisErrorCore.js";
|
||||
import { EvidenceIntegrityError } from "../../domain/evidenceErrors.js";
|
||||
import { type AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
|
|
@ -24,6 +21,7 @@ import {
|
|||
MANAGED_STATIC_PROVIDER,
|
||||
MANAGED_WORKFLOW_PROVIDER,
|
||||
} from "../InvestigationProviders.js";
|
||||
import { workflowInputError } from "../workflowInputError.js";
|
||||
|
||||
/** Compare managed members from input parsed by a trusted adapter. */
|
||||
export const compareManagedMembersEvidenceValidated = (
|
||||
|
|
@ -67,14 +65,10 @@ export const compareManagedMemberPaths = async (
|
|||
dependencies.resolveTarget(input.leftPath),
|
||||
dependencies.resolveTarget(input.rightPath),
|
||||
]);
|
||||
if (!leftTarget.ok)
|
||||
return err(
|
||||
new AnalysisInputError(operation, { cause: leftTarget.error }),
|
||||
);
|
||||
if (!rightTarget.ok)
|
||||
return err(
|
||||
new AnalysisInputError(operation, { cause: rightTarget.error }),
|
||||
);
|
||||
// Report an unopenable path like every other path-based command does,
|
||||
// with the failed path and constraint, rather than a bare input error.
|
||||
if (!leftTarget.ok) return err(leftTarget.error);
|
||||
if (!rightTarget.ok) return err(rightTarget.error);
|
||||
const [leftBytes, rightBytes] = await Promise.all([
|
||||
dependencies.readBytes(leftTarget.value.path),
|
||||
dependencies.readBytes(rightTarget.value.path),
|
||||
|
|
@ -175,7 +169,7 @@ const workflowFailure = (
|
|||
): Result<never, AnalysisError> =>
|
||||
err(
|
||||
cause instanceof z.ZodError || cause instanceof TypeError
|
||||
? new AnalysisInputError(operation, { cause })
|
||||
? workflowInputError(operation, cause)
|
||||
: new AnalysisProtocolError(
|
||||
"Managed member comparison produced an invalid result",
|
||||
{ cause },
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisProtocolError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { AnalysisProtocolError } from "../../domain/analysisErrorCore.js";
|
||||
import { type AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
|
|
@ -15,6 +12,10 @@ import {
|
|||
} from "../../domain/managed/managedNativeVerification.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import { MANAGED_WORKFLOW_PROVIDER } from "../InvestigationProviders.js";
|
||||
import {
|
||||
requestInputError,
|
||||
workflowInputError,
|
||||
} from "../workflowInputError.js";
|
||||
|
||||
const OPERATION = "verify_managed_native_boundaries" as const;
|
||||
|
||||
|
|
@ -24,14 +25,14 @@ export const verifyManagedNativeBoundariesEvidence = (
|
|||
): Result<Evidence, AnalysisError> => {
|
||||
const parsed = managedNativeVerificationInputSchema.safeParse(rawInput);
|
||||
if (!parsed.success)
|
||||
return err(new AnalysisInputError(OPERATION, { cause: parsed.error }));
|
||||
return err(requestInputError(OPERATION, parsed.error, rawInput));
|
||||
try {
|
||||
const result = verifyManagedNativeBoundaries(parsed.data);
|
||||
return ok(createManagedNativeVerificationEvidence(parsed.data, result));
|
||||
} catch (cause: unknown) {
|
||||
return err(
|
||||
cause instanceof TypeError || cause instanceof z.ZodError
|
||||
? new AnalysisInputError(OPERATION, { cause })
|
||||
? workflowInputError(OPERATION, cause)
|
||||
: new AnalysisProtocolError(
|
||||
"Managed/native verification produced an invalid result",
|
||||
{ cause },
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from "../../domain/managed/managedReconstruction.js";
|
||||
import { managedMemberInspectionSchema } from "../../domain/managed/managedArtifact.js";
|
||||
import { createEvidence } from "../../domain/evidence.js";
|
||||
import { AnalysisInputError } from "../../domain/analysisErrorCore.js";
|
||||
import { importManagedReconstructionEvidence } from "./ManagedReconstructionService.js";
|
||||
|
||||
const exampleInput = () =>
|
||||
|
|
@ -171,3 +172,78 @@ describe("managed decompiler reconstruction import", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed reconstruction input diagnostics", () => {
|
||||
it("lists schema issues for malformed raw input", () => {
|
||||
const result = importManagedReconstructionEvidence({});
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error).toMatchObject({
|
||||
_tag: "AnalysisInputError",
|
||||
issues: expect.arrayContaining([
|
||||
expect.objectContaining({ reason: "missing_argument" }),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the failed domain constraint as an issue message", () => {
|
||||
const result = importManagedReconstructionEvidence({
|
||||
...MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE,
|
||||
methods: [
|
||||
{
|
||||
...exampleMethod(),
|
||||
reconstruction: {
|
||||
...exampleMethod().reconstruction,
|
||||
text_sha256: "0".repeat(64),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error).toMatchObject({
|
||||
_tag: "AnalysisInputError",
|
||||
issues: [
|
||||
{
|
||||
path: [],
|
||||
reason: "invalid_value",
|
||||
message: expect.stringMatching(/text hash mismatch/u),
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("reports nested Evidence schema issues without a misleading request path", () => {
|
||||
const input = exampleInput();
|
||||
const malformed = createEvidence(undefined, input.static_members.provider, {
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: {
|
||||
...managedMemberInspectionSchema.parse(
|
||||
input.static_members.normalized_result,
|
||||
),
|
||||
artifact: { sha256: 7 },
|
||||
},
|
||||
rawResult: null,
|
||||
});
|
||||
const result = importManagedReconstructionEvidence({
|
||||
...MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE,
|
||||
static_members: malformed,
|
||||
});
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error).toMatchObject({ _tag: "AnalysisInputError" });
|
||||
const issues =
|
||||
result.error instanceof AnalysisInputError ? result.error.issues : [];
|
||||
expect(issues.length).toBeGreaterThan(0);
|
||||
expect(issues.every(({ path }) => path.length === 0)).toBe(true);
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
reason: "invalid_value",
|
||||
message: expect.stringContaining(
|
||||
"nested value failed validation at artifact",
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisProtocolError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { AnalysisProtocolError } from "../../domain/analysisErrorCore.js";
|
||||
import { type AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
|
|
@ -15,6 +12,10 @@ import {
|
|||
} from "../../domain/managed/managedReconstruction.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import { MANAGED_WORKFLOW_PROVIDER } from "../InvestigationProviders.js";
|
||||
import {
|
||||
requestInputError,
|
||||
workflowInputError,
|
||||
} from "../workflowInputError.js";
|
||||
|
||||
const OPERATION = "import_managed_reconstruction" as const;
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ export const importManagedReconstructionEvidence = (
|
|||
): Result<Evidence, AnalysisError> => {
|
||||
const parsed = managedReconstructionImportInputSchema.safeParse(rawInput);
|
||||
if (!parsed.success)
|
||||
return err(new AnalysisInputError(OPERATION, { cause: parsed.error }));
|
||||
return err(requestInputError(OPERATION, parsed.error, rawInput));
|
||||
return importManagedReconstructionEvidenceValidated(parsed.data);
|
||||
};
|
||||
|
||||
|
|
@ -38,7 +39,7 @@ export const importManagedReconstructionEvidenceValidated = (
|
|||
} catch (cause: unknown) {
|
||||
return err(
|
||||
cause instanceof TypeError || cause instanceof z.ZodError
|
||||
? new AnalysisInputError(OPERATION, { cause })
|
||||
? workflowInputError(OPERATION, cause)
|
||||
: new AnalysisProtocolError(
|
||||
"Managed reconstruction import produced an invalid result",
|
||||
{ cause },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import { projectInputIssues } from "../domain/inputIssueProjection.js";
|
||||
|
||||
/** Project schema issues of the raw request onto their request paths. */
|
||||
export const requestInputError = (
|
||||
operation: string,
|
||||
cause: z.ZodError,
|
||||
input: unknown,
|
||||
): AnalysisInputError =>
|
||||
new AnalysisInputError(
|
||||
operation,
|
||||
{ cause },
|
||||
projectInputIssues(cause.issues, input),
|
||||
);
|
||||
|
||||
/**
|
||||
* Keep the failed constraint when a parsed request is rejected later. Nested
|
||||
* values such as Evidence results are parsed on their own, so their issue
|
||||
* paths are relative to that value and are reported in the message, not as
|
||||
* request paths.
|
||||
*/
|
||||
export const workflowInputError = (
|
||||
operation: string,
|
||||
cause: z.ZodError | TypeError,
|
||||
): AnalysisInputError =>
|
||||
new AnalysisInputError(
|
||||
operation,
|
||||
{ cause },
|
||||
cause instanceof z.ZodError
|
||||
? cause.issues.map((issue) => ({
|
||||
path: [],
|
||||
reason: "invalid_value" as const,
|
||||
message: `A nested value failed validation at ${issue.path.length === 0 ? "its root" : issue.path.map(String).join(".")}: ${issue.message}`,
|
||||
}))
|
||||
: [{ path: [], reason: "invalid_value", message: cause.message }],
|
||||
);
|
||||
|
|
@ -34,6 +34,28 @@ describe("reconstruction coverage MCP", () => {
|
|||
boundary_id: boundaryId,
|
||||
summary: { reasons: 0 },
|
||||
});
|
||||
const unknown = await client.callTool({
|
||||
name: "evaluate_reconstruction_coverage",
|
||||
arguments: {
|
||||
coverage: currentFixtureCoverage(boundaryId),
|
||||
boundary_id: "replacement.missing",
|
||||
},
|
||||
});
|
||||
expect(unknown.isError).toBe(true);
|
||||
expect(unknown.structuredContent).toMatchObject({
|
||||
error: {
|
||||
code: "invalid_request",
|
||||
details: {
|
||||
issues: [
|
||||
{
|
||||
path: [],
|
||||
reason: "invalid_value",
|
||||
message: "Unknown reconstruction boundary: replacement.missing",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
await client.close();
|
||||
await server.close();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js";
|
||||
import { managedMemberComparisonResultSchema } from "../../../../src/domain/managed/managedMemberComparison.js";
|
||||
import { createEvidence } from "../../../../src/domain/evidence.js";
|
||||
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
|
||||
import { jsonValueSchema } from "../../../../src/domain/jsonValue.js";
|
||||
import { compareManagedMembersInputSchema } from "../../../../src/domain/managed/managedMemberComparison.js";
|
||||
import { inspectManagedMembersBytes } from "../../../../src/dotnet/ManagedMemberInspector.js";
|
||||
|
|
@ -126,6 +127,22 @@ describe("managed member comparison path workflow", () => {
|
|||
).toBe(1);
|
||||
});
|
||||
|
||||
it("reports an unopenable path with the path that failed", async () => {
|
||||
const directory = await createTestTempDirectory("rea-managed-missing-");
|
||||
const leftPath = join(directory, "missing.dll");
|
||||
const rightPath = join(directory, "right.dll");
|
||||
await writeFile(rightPath, buildManagedPeFixture());
|
||||
|
||||
const result = await compareManagedMemberPaths({ leftPath, rightPath });
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error._tag).toBe("BinaryTargetError");
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
details: { path: expect.stringContaining("missing.dll") },
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects a file that changes after target identity is resolved", async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-managed-compare-race-",
|
||||
|
|
|
|||
Loading…
Reference in New Issue