Files
rea/tests/boundary/cli/adbWindowFocus.test.ts
morluto 3d028dbc74 fix(adb): distinguish absent focus from missing declarations
Parse window focus into declaration records, normalize explicit null references once, and preserve original named lines in Evidence. Keep fallback based on declaration presence and retain unknown focus when both sections omit fields.

Validation: 86 focused tests including public CLI/MCP with advertised schemas, plus docs checks and five conformance tests. Fake ADB only; no physical device verified. Refs #4608
2026-10-17 08:38:33 +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,
});
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]);
},
);
}