Files
rea/tests/boundary/cli/adbWindowFocus.test.ts
morluto 52c84f6f58 test(cli): bound ADB MCP validation to the advertised tool
Reuse the exact selected advertised descriptor for SDK output validation instead of compiling the entire cached catalog for every fresh parity-test client. Retain actual catalog discovery, schema validation, and all result assertions under the existing worker heap limit.
2026-10-21 06:23:20 +08:00

115 lines
4.1 KiB
TypeScript

import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, onTestFinished } from "vitest";
import { AdbProvider } from "../../../src/adb/AdbProvider.js";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
import { advertisedMcpToolSchemas } from "../../fixtures/advertisedMcpTool.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
for (const { serial, section, focused, missing } of [
{ serial: "null-device", section: "windows", focused: false, missing: false },
{
serial: "fallback-null-device",
section: "displays",
focused: false,
missing: false,
},
{
serial: "focused-device",
section: "windows",
focused: true,
missing: false,
},
{
serial: "missing-device",
section: "displays",
focused: false,
missing: true,
},
]) {
cliTest.skipIf(process.platform === "win32")(
`preserves focus presence through CLI and MCP: ${serial}`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-adb-window-focus-");
const binary = join(root, "adb");
await writeFile(
binary,
`#!/bin/sh
if [ "$1" = version ]; then
printf 'Android Debug Bridge version 1.0.41\\nVersion 34.0.5-fixture\\nInstalled as %s\\n' "$0"
exit 0
fi
if [ "$1" = -s ] && [ "$3" = shell ] && [ "$4" = dumpsys ] && [ "$5" = window ]; then
printf '%s\\n' "$6" >> "$0.calls"
if [ "$2" = missing-device ]; then printf 'no focus declarations\\n'; exit 0; fi
if [ "$2" = fallback-null-device ] && [ "$6" = windows ]; then printf 'no focus declarations\\n'; exit 0; fi
if [ "$2" = focused-device ]; then
printf ' mCurrentFocus=Window{current}\\n mFocusedApp=ActivityRecord{current}\\n'
else
printf ' mCurrentFocus=null\\n mFocusedApp=null\\n'
fi
exit 0
fi
exit 1
`,
{ mode: 0o755 },
);
const output = await cli.run({
arguments: ["inspect-adb-window", serial, "--json"],
environment: { REA_ADB_PATH: binary },
});
expect(output.exitCode).toBe(0);
const provider = new AdbProvider({
environment: { REA_ADB_PATH: binary },
});
const harness = await createApplicationMcpHarness({
serverOptions: { adbDeviceAnalysis: provider },
});
onTestFinished(() => harness.close());
const schemas = await advertisedMcpToolSchemas(
harness.client,
"inspect_adb_window",
);
const args = { serial };
expect(schemas.ajv.validate(schemas.inputSchema, args)).toBe(true);
const response = await harness.client.callTool(
{ name: schemas.name, arguments: args },
{ toolDefinition: schemas.toolDefinition },
);
expect(response.isError).not.toBe(true);
expect(
schemas.ajv.validate(schemas.outputSchema, response.structuredContent),
).toBe(true);
for (const raw of [output.json, response.structuredContent]) {
const evidence = parseEvidence(
toolContract("inspect_adb_window").outputSchema.parse(raw),
);
expect(evidence.normalized_result).toMatchObject({
focused_window: focused ? "Window{current}" : null,
focused_app: focused ? "ActivityRecord{current}" : null,
observed_lines: missing
? []
: [
` mCurrentFocus=${focused ? "Window{current}" : "null"}`,
` mFocusedApp=${focused ? "ActivityRecord{current}" : "null"}`,
],
});
expect(evidence.limitations).toContainEqual(
missing
? expect.stringContaining("focus is unknown, not absent")
: `Focus declarations were read from \`dumpsys window ${section}\``,
);
}
const sections =
section === "windows" ? ["windows"] : ["windows", "displays"];
expect(
(await readFile(`${binary}.calls`, "utf8")).trim().split("\n"),
).toEqual([...sections, ...sections]);
},
);
}