mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
refactor(cli): share managed comparison finalization
This commit is contained in:
1 parent
634e566c09
commit
071a637a9f
2 files changed
+131
-48
No files matched your search
+23
-47
@@ -4,13 +4,10 @@ import { compareManagedMemberPaths } from "../application/managed/ManagedMemberC
|
||||
import { verifyManagedNativeBoundariesEvidence } from "../application/managed/ManagedNativeVerificationService.js";
|
||||
import { importManagedReconstructionEvidence } from "../application/managed/ManagedReconstructionService.js";
|
||||
import type { DirectAnalysis } from "../composition/directAnalysis.js";
|
||||
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
||||
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
||||
import { parseCliJsonInput } from "../cliJsonInput.js";
|
||||
import { MANAGED_STATIC_PROVIDER } from "../domain/investigationProviders.js";
|
||||
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
||||
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
import { logCliCommand } from "../cliLogging.js";
|
||||
import type { Logger } from "pino";
|
||||
@@ -19,6 +16,7 @@ import { registerProjectManagedApplicationGraph } from "./managedProjectGraphCom
|
||||
import { managedMembersInputSchema } from "../contracts/managed/managedToolContracts.js";
|
||||
import { localPathStringSchema } from "../domain/localPath.js";
|
||||
import { withCommandCancellation } from "./commandCancellation.js";
|
||||
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
|
||||
|
||||
export const registerManagedCommands = (
|
||||
cli: CliInstance,
|
||||
@@ -135,52 +133,30 @@ const registerCompareManagedMembers = (
|
||||
"Compare managed PE/CLI member inventories by exact declared type, name, and signature; names alone never form a match",
|
||||
args: compareManagedMemberArgs,
|
||||
run: ({ args }) =>
|
||||
logCliCommand(logger, "compare-managed-members", async () => {
|
||||
const resources = new ArtifactResourceScope();
|
||||
const result = await compareManagedMemberPaths(
|
||||
{
|
||||
leftPath: args.leftPath,
|
||||
rightPath: args.rightPath,
|
||||
},
|
||||
{},
|
||||
resources,
|
||||
);
|
||||
try {
|
||||
await resources.close();
|
||||
} catch (cause: unknown) {
|
||||
const cleanup = ArtifactReaderFailure.cleanupObservation(
|
||||
cause,
|
||||
withCommandCancellation((signal) =>
|
||||
logCliCommand(logger, "compare-managed-members", async () => {
|
||||
const resources = new ArtifactResourceScope();
|
||||
const result = await finishAnalysisCommand(
|
||||
() =>
|
||||
compareManagedMemberPaths(
|
||||
{
|
||||
leftPath: args.leftPath,
|
||||
rightPath: args.rightPath,
|
||||
},
|
||||
{},
|
||||
resources,
|
||||
),
|
||||
resources,
|
||||
MANAGED_STATIC_PROVIDER.id,
|
||||
"compare_managed_members",
|
||||
signal,
|
||||
"Managed member comparison resources",
|
||||
);
|
||||
const failure = new ProviderCleanupError(
|
||||
MANAGED_STATIC_PROVIDER.id,
|
||||
cleanup.resources,
|
||||
{ reason: cleanup.reason },
|
||||
{
|
||||
cause,
|
||||
operation: "compare_managed_members",
|
||||
...(result.ok ? { partialObservation: result.value } : {}),
|
||||
},
|
||||
);
|
||||
const error = result.ok
|
||||
? failure
|
||||
: analysisErrorWithCleanupFailure(
|
||||
result.error,
|
||||
failure,
|
||||
"compare_managed_members",
|
||||
);
|
||||
return {
|
||||
error: "Managed member comparison failed",
|
||||
...projectAnalysisError(error),
|
||||
};
|
||||
}
|
||||
return result.ok
|
||||
? result.value
|
||||
: {
|
||||
error: "Managed member comparison failed",
|
||||
...projectAnalysisError(result.error),
|
||||
};
|
||||
}),
|
||||
return "evidence_id" in result
|
||||
? result
|
||||
: { error: "Managed member comparison failed", ...result };
|
||||
}),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,15 @@ import { createHash } from "node:crypto";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { expect } from "vitest";
|
||||
import { expect, it, vi } from "vitest";
|
||||
|
||||
import * as comparison from "../../../src/application/managed/ManagedMemberComparisonService.js";
|
||||
import { ArtifactResourceScope } from "../../../src/artifacts/ArtifactResourceScope.js";
|
||||
import { registerManagedCommands } from "../../../src/cli/managedCommands.js";
|
||||
import { CLI_COMMANDS } from "../../../src/cliCommandNames.js";
|
||||
import { AnalysisOutputError } from "../../../src/domain/analysisErrorCore.js";
|
||||
import { err, ok } from "../../../src/domain/result.js";
|
||||
import { silentLogger } from "../../../src/logger.js";
|
||||
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { managedMemberInspectionSchema } from "../../../src/domain/managed/managedArtifact.js";
|
||||
@@ -184,3 +192,102 @@ cliTest(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const managedComparisonEvidence = async () => {
|
||||
const directory = await createTestTempDirectory("rea-managed-finalization-");
|
||||
const path = join(directory, "comparison.dll");
|
||||
await writeFile(path, buildManagedPeFixture());
|
||||
const result = await comparison.compareManagedMemberPaths({
|
||||
leftPath: path,
|
||||
rightPath: path,
|
||||
});
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
};
|
||||
|
||||
const registeredComparisonCommand = () => {
|
||||
const commands = new Map<string, { run(input: unknown): Promise<unknown> }>();
|
||||
const cli = {
|
||||
command: (
|
||||
name: string,
|
||||
definition: { run(input: unknown): Promise<unknown> },
|
||||
) => {
|
||||
commands.set(name, definition);
|
||||
return cli;
|
||||
},
|
||||
} as unknown as Parameters<typeof registerManagedCommands>[0];
|
||||
registerManagedCommands(cli, silentLogger, async () => null);
|
||||
return commands.get(CLI_COMMANDS.compareManagedMembers)!;
|
||||
};
|
||||
|
||||
it.each([true, false])(
|
||||
"retains the comparison outcome when final cleanup fails (success: %s)",
|
||||
async (success) => {
|
||||
const evidence = await managedComparisonEvidence();
|
||||
const run = vi
|
||||
.spyOn(comparison, "compareManagedMemberPaths")
|
||||
.mockResolvedValue(
|
||||
success
|
||||
? ok(evidence)
|
||||
: err(
|
||||
new AnalysisOutputError(
|
||||
"compare_managed_members",
|
||||
"primary comparison failure",
|
||||
{
|
||||
partialObservation: evidence,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
const close = vi
|
||||
.spyOn(ArtifactResourceScope.prototype, "close")
|
||||
.mockRejectedValue(new Error("comparison close denied"));
|
||||
try {
|
||||
const result = await registeredComparisonCommand().run({
|
||||
args: { leftPath: "left.dll", rightPath: "right.dll" },
|
||||
});
|
||||
expect(result).toMatchObject({
|
||||
error: "Managed member comparison failed",
|
||||
code: "cleanup_incomplete",
|
||||
details: {
|
||||
partial_observation: { evidence_id: evidence.evidence_id },
|
||||
},
|
||||
});
|
||||
expect(JSON.stringify(result)).toContain("comparison close denied");
|
||||
if (!success)
|
||||
expect(JSON.stringify(result)).toContain("primary comparison failure");
|
||||
expect(close).toHaveBeenCalledOnce();
|
||||
} finally {
|
||||
run.mockRestore();
|
||||
close.mockRestore();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("retains completed comparison Evidence when interrupted during final cleanup", async () => {
|
||||
const evidence = await managedComparisonEvidence();
|
||||
const run = vi
|
||||
.spyOn(comparison, "compareManagedMemberPaths")
|
||||
.mockResolvedValue(ok(evidence));
|
||||
const close = vi
|
||||
.spyOn(ArtifactResourceScope.prototype, "close")
|
||||
.mockImplementation(async () => {
|
||||
process.emit("SIGINT");
|
||||
});
|
||||
const previousExitCode = process.exitCode;
|
||||
try {
|
||||
const result = await registeredComparisonCommand().run({
|
||||
args: { leftPath: "left.dll", rightPath: "right.dll" },
|
||||
});
|
||||
expect(result).toMatchObject({
|
||||
error: "Managed member comparison failed",
|
||||
code: "cancelled",
|
||||
details: { partial_observation: { evidence_id: evidence.evidence_id } },
|
||||
});
|
||||
expect(process.exitCode).toBe(130);
|
||||
} finally {
|
||||
process.exitCode = previousExitCode;
|
||||
run.mockRestore();
|
||||
close.mockRestore();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user