mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 15:26:25 +00:00
fix: report provider admission failures as typed codes (#8066)
* fix: report provider admission failures as typed codes * fix(evm): keep limiter path on admission diagnostics * fix: assert WABT and CBOR admission codes * fix: keep admission details on returned failures * fix(evm): detect repeated CBOR keys in the decoder --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
1 parent
3b647132c6
commit
317bf3fe23
63 files changed
+1021
-432
No files matched your search
@@ -82,8 +82,12 @@ it("rejects a relative override and non-file without probing an alternate comman
|
||||
);
|
||||
expect(relative).toMatchObject({
|
||||
status: "unavailable",
|
||||
diagnostics: { limiter_path: "prlimit" },
|
||||
reason: expect.stringContaining("absolute executable path"),
|
||||
reason: "prlimit_not_absolute",
|
||||
diagnostics: {
|
||||
limiter_path: "prlimit",
|
||||
code: "prlimit_not_absolute",
|
||||
setting: "REA_EVM_PRLIMIT_COMMAND",
|
||||
},
|
||||
});
|
||||
|
||||
const directory = await evmLimiterAvailability(
|
||||
@@ -98,7 +102,8 @@ it("rejects a relative override and non-file without probing an alternate comman
|
||||
);
|
||||
expect(directory).toMatchObject({
|
||||
status: "unavailable",
|
||||
reason: expect.stringContaining("regular executable file"),
|
||||
reason: "prlimit_not_file",
|
||||
diagnostics: expect.objectContaining({ code: "prlimit_not_file" }),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -982,7 +982,15 @@ it.runIf(!unsupportedHost).each(["directory", "missing-interpreter"])(
|
||||
category: "unsupported_provider",
|
||||
details: { provider_id: "evmole-interface" },
|
||||
});
|
||||
expect(result.error.message).toContain(limiter);
|
||||
if (scenario === "directory")
|
||||
expect(result.error).toMatchObject({
|
||||
reason: "prlimit_not_file",
|
||||
diagnostics: {
|
||||
limiter_path: limiter,
|
||||
code: "prlimit_not_file",
|
||||
},
|
||||
});
|
||||
else expect(result.error.message).toContain(limiter);
|
||||
if (ownedRoot !== undefined)
|
||||
await expect(access(ownedRoot)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
expect(await readFile(path, "utf8")).toBe("0x60006000f3\n");
|
||||
|
||||
@@ -94,7 +94,17 @@ it.each([
|
||||
if (kind.startsWith("duplicate nested")) {
|
||||
expect(metadata.entries).toEqual([]);
|
||||
expect(metadata.compiler_version).toBeNull();
|
||||
expect(metadata.diagnostic).toContain("repeat map key");
|
||||
expect(metadata.diagnostic).toBe("repeated_map_key");
|
||||
expect(metadata.admission).toMatchObject({ code: "repeated_map_key" });
|
||||
if (kind === "duplicate nested text keys")
|
||||
expect(metadata.admission?.detail).toEqual({
|
||||
key: JSON.stringify({ kind: "text", value: "a" }),
|
||||
});
|
||||
else
|
||||
expect(
|
||||
metadata.admission?.detail?.key ??
|
||||
metadata.admission?.detail?.key_length,
|
||||
).toBeDefined();
|
||||
expect(metadata.bytecode_offset).toBe(1);
|
||||
expect(metadata.cbor_bytes).toBe(19);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ const admissionCases: readonly AdmissionCase[] = [
|
||||
{
|
||||
name: "malformed Unicode escape",
|
||||
properties: "application.version=12.1.4\nother=\\u00xx\n",
|
||||
error: "Malformed Unicode escape in Java properties file",
|
||||
error: "malformed_unicode_escape",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -603,7 +603,12 @@ it.each(["seed", "extension"] as const)(
|
||||
],
|
||||
},
|
||||
});
|
||||
else expect(result.error.diagnostics?.reason).toContain("not a JAR");
|
||||
else
|
||||
expect(result.error.diagnostics).toMatchObject({
|
||||
code: "not_jar",
|
||||
reason: "not_jar",
|
||||
path,
|
||||
});
|
||||
allowClose = true;
|
||||
await scope.retryCleanup();
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
|
||||
@@ -241,14 +241,27 @@ describe.skipIf(process.platform === "win32")(
|
||||
if (missing.ok) throw new Error("Missing installation was accepted");
|
||||
expect(missing.error.message).toContain("PSP_EXTENSION_UNAVAILABLE");
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe.skipIf(process.platform === "win32")(
|
||||
"PSP extension admission codes",
|
||||
() => {
|
||||
it.each([
|
||||
["name=other\nversion=12.1.3\n", "does not match"],
|
||||
["name=ghidra-allegrex\nversion=12.1.4\n", "does not match"],
|
||||
["name=ghidra-allegrex\nversion=12.1.\\uZZZZ\n", "Unicode escape"],
|
||||
])(
|
||||
["name=other\nversion=12.1.3\n", "metadata_mismatch", undefined],
|
||||
[
|
||||
"name=ghidra-allegrex\nversion=12.1.4\n",
|
||||
"metadata_mismatch",
|
||||
undefined,
|
||||
],
|
||||
[
|
||||
"name=ghidra-allegrex\nversion=12.1.\\uZZZZ\n",
|
||||
"properties_invalid",
|
||||
"malformed_unicode_escape",
|
||||
],
|
||||
] as const)(
|
||||
"rejects mismatched or malformed extension metadata %s",
|
||||
async (properties, reason) => {
|
||||
async (properties, code, parserCode) => {
|
||||
const { root } = await installation();
|
||||
await writeFile(join(root, "extension.properties"), properties);
|
||||
await expect(
|
||||
@@ -257,7 +270,14 @@ describe.skipIf(process.platform === "win32")(
|
||||
"12.1.3",
|
||||
new ArtifactResourceScope(),
|
||||
),
|
||||
).rejects.toThrow(reason);
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: {
|
||||
code,
|
||||
...(parserCode === undefined
|
||||
? { ghidra_version: "12.1.3" }
|
||||
: { parser_code: parserCode }),
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -269,7 +289,12 @@ describe.skipIf(process.platform === "win32")(
|
||||
);
|
||||
await expect(
|
||||
inspectGhidraPspExtension(root, "12.1.3", new ArtifactResourceScope()),
|
||||
).rejects.toThrow("Module.manifest");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: {
|
||||
code: "component_missing",
|
||||
component: "Module.manifest",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects symlinked code instead of hashing an untracked external producer", async () => {
|
||||
@@ -279,7 +304,9 @@ describe.skipIf(process.platform === "win32")(
|
||||
await symlink(join(directory, "external.jar"), path);
|
||||
await expect(
|
||||
inspectGhidraPspExtension(root, "12.1.3", new ArtifactResourceScope()),
|
||||
).rejects.toThrow("regular files");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: { code: "entry_kind", relative: "lib/extension.jar" },
|
||||
});
|
||||
});
|
||||
|
||||
it("bounds sparse extension input before reading or allocating its declared extent", async () => {
|
||||
@@ -287,7 +314,12 @@ describe.skipIf(process.platform === "win32")(
|
||||
await truncate(join(root, "lib/extension.jar"), 64 * 1024 * 1024 + 1);
|
||||
await expect(
|
||||
inspectGhidraPspExtension(root, "12.1.3", new ArtifactResourceScope()),
|
||||
).rejects.toThrow("inspection limit");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: {
|
||||
code: "bytes_exceed_limit",
|
||||
max_bytes: 64 * 1024 * 1024,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -314,7 +346,9 @@ describe.skipIf(process.platform === "win32")(
|
||||
"12.1.3",
|
||||
new ArtifactResourceScope(),
|
||||
),
|
||||
).rejects.toThrow("inventory exceeds the inspection limit");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: { code: "inventory_exceeds_limit", max_entries: 1024 },
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -571,7 +605,12 @@ describe.skipIf(process.platform === "win32")(
|
||||
const languageOverride = { languageId: "x86:LE:64:default" };
|
||||
expect(() =>
|
||||
ghidraHeadlessArguments({ ...paths, psp: true, languageOverride }),
|
||||
).toThrow("PSP ELF import cannot be combined");
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "psp_combined",
|
||||
}),
|
||||
);
|
||||
expect(ghidraHeadlessArguments({ ...paths, languageOverride })).toEqual(
|
||||
expect.arrayContaining(["-processor", "x86:LE:64:default"]),
|
||||
);
|
||||
@@ -903,11 +942,14 @@ describe.skipIf(process.platform === "win32")(
|
||||
const result = await provider.resolveAnalysisProfile(target);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("Changed file admitted");
|
||||
expect(result.error.message).toContain(
|
||||
change === "growth"
|
||||
? "changed during inspection"
|
||||
: "Truncated Allegrex component",
|
||||
);
|
||||
expect(result.error).toMatchObject({
|
||||
diagnostics: {
|
||||
code:
|
||||
change === "growth" ? "component_changed" : "component_truncated",
|
||||
relative:
|
||||
change === "growth" ? "Module.manifest" : "extension.properties",
|
||||
},
|
||||
});
|
||||
expect(closes).toBe(1);
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
expect(provider.hasPendingOwnedResources()).toBe(false);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { OwnedFileHandle } from "../../../src/filesystem/OwnedFileHandle.js";
|
||||
import { OwnedStagedOutput } from "../../../src/filesystem/OwnedStagedOutput.js";
|
||||
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
||||
import { writeTextParts } from "../../../src/application/JsonFiles.js";
|
||||
import { StagedOutputError } from "../../../src/filesystem/StagedOutputError.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const allocation = vi.hoisted(() => ({
|
||||
@@ -70,7 +71,10 @@ it("closes the descriptor and retains staged content when its final identity is
|
||||
expect(result.error.cause).toMatchObject({
|
||||
outcome: {
|
||||
ok: false,
|
||||
error: { message: "Cannot verify staged file identity" },
|
||||
error: {
|
||||
_tag: "StagedOutputError",
|
||||
code: "identity_unverified",
|
||||
},
|
||||
},
|
||||
});
|
||||
const stage = result.error.cleanupResources[0];
|
||||
@@ -457,7 +461,11 @@ it("refuses to remove unexpected files from an owned staging directory", async (
|
||||
const unexpected = join(directory, "unexpected");
|
||||
writeFileSync(unexpected, "unowned");
|
||||
try {
|
||||
await expect(owner.close()).rejects.toThrow(/Unexpected staging entries/u);
|
||||
await expect(owner.close()).rejects.toMatchObject({
|
||||
_tag: "StagedOutputError",
|
||||
code: "unexpected_entries",
|
||||
detail: { count: 1, entries: "unexpected" },
|
||||
});
|
||||
expect(await fs.readFile(unexpected, "utf8")).toBe("unowned");
|
||||
} finally {
|
||||
await fs.rm(unexpected, { force: true });
|
||||
@@ -466,6 +474,37 @@ it("refuses to remove unexpected files from an owned staging directory", async (
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps unexpected staging names on the publication error", async () => {
|
||||
const root = await createTestTempDirectory("rea-staged-public-entries-");
|
||||
const destination = join(root, "out.json");
|
||||
const resources = new ArtifactResourceScope();
|
||||
const close = vi
|
||||
.spyOn(OwnedStagedOutput.prototype, "close")
|
||||
.mockImplementation(async () => {
|
||||
throw new StagedOutputError("unexpected_entries", {
|
||||
count: 1,
|
||||
entries: "unexpected",
|
||||
});
|
||||
});
|
||||
try {
|
||||
const result = await writeTextParts(["{}"], destination, false, resources);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error).toMatchObject({
|
||||
_tag: "EvidenceFileError",
|
||||
diagnostics: {
|
||||
code: "unexpected_entries",
|
||||
count: 1,
|
||||
entries: "unexpected",
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
close.mockRestore();
|
||||
await resources.close().catch(() => undefined);
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("retains cleanup when its staging directory was renamed", async () => {
|
||||
const root = await createTestTempDirectory("rea-staged-renamed-dir-");
|
||||
const directory = join(root, ".rea-write-owned");
|
||||
|
||||
@@ -141,7 +141,11 @@ it.each(["bin", "hop"])(
|
||||
code: "capability_unavailable",
|
||||
details: {
|
||||
operation: "inspect_wasm_artifact",
|
||||
reason: expect.stringContaining("REA_WABT_BIN_DIRECTORY"),
|
||||
reason: "directory_not_absolute",
|
||||
diagnostics: {
|
||||
code: "directory_not_absolute",
|
||||
setting: "REA_WABT_BIN_DIRECTORY",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -152,7 +156,13 @@ it.each(["bin", "hop"])(
|
||||
expect(parseMcpToolError(summary)).toMatchObject({
|
||||
error: {
|
||||
code: "capability_unavailable",
|
||||
details: { reason: expect.stringContaining("REA_WABT_BIN_DIRECTORY") },
|
||||
details: {
|
||||
reason: "directory_not_absolute",
|
||||
diagnostics: {
|
||||
code: "directory_not_absolute",
|
||||
setting: "REA_WABT_BIN_DIRECTORY",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await client.ping();
|
||||
|
||||
@@ -1040,7 +1040,10 @@ it.skipIf(process.platform === "win32")(
|
||||
throw new Error("closed reader compilation unexpectedly succeeded");
|
||||
expect(outcome.cause).toMatchObject({ name: "AbortError" });
|
||||
await expect(access(state.root)).rejects.toThrow();
|
||||
await expect(reader.prepare()).rejects.toThrow(/reader is closed/u);
|
||||
await expect(reader.prepare()).rejects.toMatchObject({
|
||||
_tag: "ProcessAdmissionError",
|
||||
code: "reader_closed",
|
||||
});
|
||||
await expect(reader.close()).resolves.toBeUndefined();
|
||||
} finally {
|
||||
await Promise.allSettled([preparation]);
|
||||
|
||||
@@ -506,7 +506,10 @@ describe("provider process memory limits", () => {
|
||||
platform: "linux",
|
||||
memoryLimitBytes: 704 * 1024 * 1024,
|
||||
}),
|
||||
).rejects.toThrow("require the Windows Job Object launcher");
|
||||
).rejects.toMatchObject({
|
||||
_tag: "ProcessAdmissionError",
|
||||
code: "memory_limit_unsupported",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -452,6 +452,13 @@ describe.skipIf(process.platform === "win32")(
|
||||
cause: { code: 3 },
|
||||
},
|
||||
});
|
||||
if (mode === "env-malformed")
|
||||
expect(cause).toMatchObject({
|
||||
cause: {
|
||||
code: "invalid_environment_entry",
|
||||
detail: { check: "separator", line: "invalid environment" },
|
||||
},
|
||||
});
|
||||
if (mode === "vm-malformed")
|
||||
expect(cause).toMatchObject({
|
||||
launch_support: expect.arrayContaining([
|
||||
@@ -474,9 +481,8 @@ describe.skipIf(process.platform === "win32")(
|
||||
if (launched.ok)
|
||||
throw new Error("Expected a JVM platform override failure");
|
||||
expect(launched.error.cause).toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
"may not override os.arch or os.name",
|
||||
),
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "platform_override",
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -499,9 +505,8 @@ describe.skipIf(process.platform === "win32")(
|
||||
if (launched.ok)
|
||||
throw new Error("Expected a JVM platform override failure");
|
||||
expect(launched.error.cause).toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
"may not override os.arch or os.name",
|
||||
),
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "platform_override",
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -578,9 +583,8 @@ describe.skipIf(process.platform === "win32")(
|
||||
if (launched.ok)
|
||||
throw new Error("Expected an effective platform override refusal");
|
||||
expect(launched.error.cause).toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
"may not override os.arch or os.name",
|
||||
),
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "platform_override",
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -600,7 +604,8 @@ describe.skipIf(process.platform === "win32")(
|
||||
if (launched.ok)
|
||||
throw new Error("Expected a JVM platform override failure");
|
||||
expect(launched.error.cause).toMatchObject({
|
||||
message: expect.stringContaining("may not override os.arch or os.name"),
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "platform_override",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -634,7 +639,9 @@ describe.skipIf(process.platform === "win32")(
|
||||
if (launched.ok)
|
||||
throw new Error("Expected an unreadable argument-file failure");
|
||||
expect(launched.error.cause).toMatchObject({
|
||||
message: expect.stringContaining("argument file is unreadable"),
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "argument_file_unreadable",
|
||||
detail: { path: optionsFile },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -657,7 +664,8 @@ describe.skipIf(process.platform === "win32")(
|
||||
if (launched.ok)
|
||||
throw new Error("Expected a nested argument-file failure");
|
||||
expect(launched.error.cause).toMatchObject({
|
||||
message: expect.stringContaining("may not nest @ references"),
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "nested_argument_file",
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -823,9 +831,8 @@ describe.skipIf(process.platform === "win32")(
|
||||
);
|
||||
if (cleanup.fileCleanupFailure.outcome.kind === "failed")
|
||||
expect(cleanup.fileCleanupFailure.outcome.cause).toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
"may not override os.arch or os.name",
|
||||
),
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "platform_override",
|
||||
});
|
||||
await resources.retryCleanup();
|
||||
expect(resources.hasPending).toBe(false);
|
||||
|
||||
@@ -260,7 +260,12 @@ describe("Ghidra GameCube DOL loader", () => {
|
||||
sectionFilePath: "/tmp/gamecube-dol-sections.txt",
|
||||
},
|
||||
}),
|
||||
).toThrow(/cannot be combined with another loader, language override/u);
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "gamecube_dol_combined",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -346,7 +351,12 @@ describe("Ghidra headless launcher", () => {
|
||||
])
|
||||
expect(() =>
|
||||
ghidraHeadlessArguments({ ...base, mipsSoftFloat: true, ...conflict }),
|
||||
).toThrow(/generic MIPS ELF/u);
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "mips_soft_float_scope",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -395,7 +405,12 @@ describe("Ghidra configured import language and seeds", () => {
|
||||
dosMz: true,
|
||||
languageOverride: { languageId: "x86:LE:32:default" },
|
||||
}),
|
||||
).toThrow(/cannot be combined with a DOS import/u);
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "language_override_dos",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("never combines a configured language with a raw-binary import", () => {
|
||||
@@ -409,7 +424,12 @@ describe("Ghidra configured import language and seeds", () => {
|
||||
baseAddr: "0x8000000",
|
||||
},
|
||||
}),
|
||||
).toThrow(/cannot be combined with a raw-binary import/u);
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "language_override_raw_binary",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it.each(["darwin", "win32"] as const)(
|
||||
@@ -579,30 +599,41 @@ describe("Ghidra existing project", () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ dosMz: true as const },
|
||||
{ dosCom: true as const },
|
||||
{
|
||||
rawBinary: {
|
||||
languageId: "ARM:LE:32:v7",
|
||||
compilerSpecId: "default",
|
||||
baseAddr: "0x8000000",
|
||||
[{ dosMz: true as const }, "existing_project_dos"],
|
||||
[{ dosCom: true as const }, "existing_project_dos"],
|
||||
[
|
||||
{
|
||||
rawBinary: {
|
||||
languageId: "ARM:LE:32:v7",
|
||||
compilerSpecId: "default",
|
||||
baseAddr: "0x8000000",
|
||||
},
|
||||
},
|
||||
},
|
||||
{ languageOverride: { languageId: "x86:LE:32:default" } },
|
||||
{ psp: true as const },
|
||||
{ mipsSoftFloat: true as const },
|
||||
{ seedPath: "/tmp/runtime/seeds/seeds.tsv" },
|
||||
{ disabledAnalyzers: ["Decompiler Parameter ID"] },
|
||||
])(
|
||||
"existing_project_raw_binary",
|
||||
],
|
||||
[
|
||||
{ languageOverride: { languageId: "x86:LE:32:default" } },
|
||||
"existing_project_language_override",
|
||||
],
|
||||
[{ psp: true as const }, "existing_project_psp"],
|
||||
[{ mipsSoftFloat: true as const }, "existing_project_mips_soft_float"],
|
||||
[{ seedPath: "/tmp/runtime/seeds/seeds.tsv" }, "existing_project_analysis"],
|
||||
[
|
||||
{ disabledAnalyzers: ["Decompiler Parameter ID"] },
|
||||
"existing_project_analysis",
|
||||
],
|
||||
] as const)(
|
||||
"rejects the import-only option %o with an existing project",
|
||||
(conflict) => {
|
||||
(conflict, code) => {
|
||||
expect(() =>
|
||||
ghidraHeadlessArguments({
|
||||
...base,
|
||||
existingProject: { programName: "game.exe" },
|
||||
...conflict,
|
||||
}),
|
||||
).toThrow(/existing (Ghidra )?project|fresh import/u);
|
||||
).toThrow(
|
||||
expect.objectContaining({ _tag: "GhidraAdmissionError", code }),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -618,7 +649,12 @@ describe("Ghidra existing project", () => {
|
||||
),
|
||||
existingProject: { programName: "game.exe" },
|
||||
}),
|
||||
).toThrow(/Windows/u);
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "existing_project_windows",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("requires a program name for -process", () => {
|
||||
@@ -627,7 +663,12 @@ describe("Ghidra existing project", () => {
|
||||
...base,
|
||||
existingProject: { programName: " " },
|
||||
}),
|
||||
).toThrow(/program name/u);
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "GhidraAdmissionError",
|
||||
code: "existing_project_program_name",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user