mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
fix(test): use eligible bundle targets for UI availability
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { rm } from "node:fs/promises";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import type { Client } from "@modelcontextprotocol/client";
|
||||
import type { CallToolResult } from "@modelcontextprotocol/server";
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
} from "../../../src/domain/apple/interfaceBuilderGraph.js";
|
||||
import { inspectNativeDispatchMetadata } from "../../../src/domain/native/objcSwiftMetadata.js";
|
||||
import { nativeInvestigationTraceSchema } from "../../../src/domain/native/nativeInvestigationGraph.js";
|
||||
import { thinMach } from "../../../src/domain/binaryTarget.fixture.js";
|
||||
import type { JsonValue } from "../../../src/domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../../../src/domain/result.js";
|
||||
import type { AnalysisOperation } from "../../../src/application/AnalysisProvider.js";
|
||||
@@ -33,13 +35,24 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { createSessionMcpHarness } from "./sessionMcpHarness.js";
|
||||
|
||||
const resources: Array<{ close(): Promise<unknown> }> = [];
|
||||
let directory: string | undefined;
|
||||
|
||||
const writeEligibleAppBundle = async (root: string): Promise<string> => {
|
||||
const app = join(root, "Eligible.app");
|
||||
const contents = join(app, "Contents");
|
||||
await mkdir(join(contents, "MacOS"), { recursive: true });
|
||||
await writeFile(
|
||||
join(contents, "MacOS", "Fixture"),
|
||||
thinMach(0xfeedfacf, 0x0100000c),
|
||||
);
|
||||
await writeFile(
|
||||
join(contents, "Info.plist"),
|
||||
"<plist><dict><key>CFBundleExecutable</key><string>Fixture</string></dict></plist>",
|
||||
);
|
||||
return app;
|
||||
};
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(resources.splice(0).map((resource) => resource.close()));
|
||||
if (directory !== undefined)
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
directory = undefined;
|
||||
});
|
||||
|
||||
const identity = {
|
||||
@@ -205,16 +218,19 @@ describe("native UI action availability through available routes", () => {
|
||||
it.each(routeScenarios)(
|
||||
"reports the $name route available when its observed path succeeds",
|
||||
async (scenario) => {
|
||||
directory = await createTestTempDirectory("rea-native-ui-availability-");
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-native-ui-availability-",
|
||||
);
|
||||
const calls: ProviderOperation[] = [];
|
||||
const { mcp, first } = await createSessionMcpHarness(
|
||||
const { mcp } = await createSessionMcpHarness(
|
||||
directory,
|
||||
() => provider(scenario, calls),
|
||||
resources,
|
||||
);
|
||||
const app = await writeEligibleAppBundle(directory);
|
||||
const opened = await mcp.callTool({
|
||||
name: "open_binary",
|
||||
arguments: { path: first },
|
||||
arguments: { path: app },
|
||||
});
|
||||
expect(opened.isError).not.toBe(true);
|
||||
|
||||
@@ -264,16 +280,19 @@ describe("native UI action availability for unmet routes", () => {
|
||||
])(
|
||||
"reports unmet route sources for $name without hiding partial results",
|
||||
async (scenario) => {
|
||||
directory = await createTestTempDirectory("rea-native-ui-no-metadata-");
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-native-ui-no-metadata-",
|
||||
);
|
||||
const calls: ProviderOperation[] = [];
|
||||
const { mcp, first } = await createSessionMcpHarness(
|
||||
const { mcp } = await createSessionMcpHarness(
|
||||
directory,
|
||||
() => provider(scenario, calls),
|
||||
resources,
|
||||
);
|
||||
const app = await writeEligibleAppBundle(directory);
|
||||
const opened = await mcp.callTool({
|
||||
name: "open_binary",
|
||||
arguments: { path: first },
|
||||
arguments: { path: app },
|
||||
});
|
||||
expect(opened.isError).not.toBe(true);
|
||||
|
||||
@@ -378,20 +397,21 @@ describe("native UI action availability for failures and target gates", () => {
|
||||
it.each([metadataRuntimeFailureScenario, decodeCancelledScenario])(
|
||||
"preserves runtime failures for $name without trying another declared source",
|
||||
async (scenario) => {
|
||||
directory = await createTestTempDirectory(
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-native-ui-source-failure-",
|
||||
);
|
||||
const calls: ProviderOperation[] = [];
|
||||
const { mcp, first } = await createSessionMcpHarness(
|
||||
const { mcp } = await createSessionMcpHarness(
|
||||
directory,
|
||||
() => provider(scenario, calls),
|
||||
resources,
|
||||
);
|
||||
const app = await writeEligibleAppBundle(directory);
|
||||
expect(
|
||||
(
|
||||
await mcp.callTool({
|
||||
name: "open_binary",
|
||||
arguments: { path: first },
|
||||
arguments: { path: app },
|
||||
})
|
||||
).isError,
|
||||
).not.toBe(true);
|
||||
@@ -426,7 +446,7 @@ describe("native UI action availability for failures and target gates", () => {
|
||||
);
|
||||
|
||||
it("reports target_required before querying provider operations", async () => {
|
||||
directory = await createTestTempDirectory("rea-native-ui-no-target-");
|
||||
const directory = await createTestTempDirectory("rea-native-ui-no-target-");
|
||||
const calls: ProviderOperation[] = [];
|
||||
const { mcp } = await createSessionMcpHarness(
|
||||
directory,
|
||||
@@ -441,6 +461,44 @@ describe("native UI action availability for failures and target gates", () => {
|
||||
});
|
||||
expect(calls).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps authored UI dispatch unavailable for a non-bundle target", async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-native-ui-non-bundle-",
|
||||
);
|
||||
const calls: ProviderOperation[] = [];
|
||||
const { mcp } = await createSessionMcpHarness(
|
||||
directory,
|
||||
() => provider(authoredUiDirectScenario, calls),
|
||||
resources,
|
||||
);
|
||||
const executable = join(directory, "Standalone");
|
||||
await writeFile(executable, thinMach(0xfeedfacf, 0x0100000c));
|
||||
expect(
|
||||
(
|
||||
await mcp.callTool({
|
||||
name: "open_binary",
|
||||
arguments: { path: executable },
|
||||
})
|
||||
).isError,
|
||||
).not.toBe(true);
|
||||
|
||||
try {
|
||||
const entry = dispatchAvailability(await readInventory(mcp));
|
||||
const authored = entry?.modes?.find(
|
||||
(mode) => mode.name === "authored_ui_dispatch",
|
||||
);
|
||||
expect(authored).toMatchObject({
|
||||
available: false,
|
||||
missing_operations: ["decode_interface_builder"],
|
||||
});
|
||||
expect(calls).toEqual([]);
|
||||
} finally {
|
||||
expect(
|
||||
(await mcp.callTool({ name: "close_binary", arguments: {} })).isError,
|
||||
).not.toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const readInventory = async (mcp: Client) => {
|
||||
|
||||
Reference in New Issue
Block a user