Files
rea/tests/boundary/cli/desktopApplicationCli.test.ts
T
Kaoru0822andClaude Opus 5.5 0cfef92ae1 fix(desktop): classify rejected desktop roots as target failures (#5577)
inspect-desktop-application reported a regular-file, missing or read-denied
root as artifact_operation_failed/execution_failure, leaving the ENOTDIR,
ENOENT or EACCES cause only in message text.

DirectoryArtifactReader now marks root admission and root-open failures with
DirectoryArtifactRootFailure. DesktopApplicationReader, which owns the caller's
selection, maps a non-directory root to unsupported_target and host failures to
the shared BinaryTargetError (target_unavailable, or access_denied with
read-access recovery), keeping any partial observation. Child-entry failures
and other DirectoryArtifactReader consumers are unchanged.

Fixes #5513

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:54:40 -07:00

169 lines
5.5 KiB
TypeScript

import { readEvidenceBundle } from "../../../src/application/EvidenceBundleFiles.js";
import { chmod, mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { cliTest } from "../../support/cli/cliFixture.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { createDesktopApplicationService } from "../../../src/composition/desktop.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
cliTest(
"shares directory facts and exact retained-field semantics with the public CLI",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-desktop-cli-");
const installation = join(root, "installation");
await mkdir(join(installation, "resources"), { recursive: true });
await writeFile(
join(installation, "resources/app.asar"),
"path convention",
);
const refused = await cli.run({
arguments: [
"inspect-desktop-application",
join(root, "must-not-be-read"),
"--detail",
"summary",
"--json",
],
});
expect(refused.exitCode).not.toBe(0);
expect(refused.json).toMatchObject({
code: "invalid_request",
category: "invalid_input",
details: {
operation: "inspect_desktop_application",
issues: [{ path: ["evidence_output"] }],
},
});
const response = await cli.run({
arguments: ["inspect-desktop-application", installation, "--json"],
});
expect(response.exitCode).toBe(0);
const evidence = parseEvidence(response.json);
const service = createDesktopApplicationService();
try {
const source = await service.inspect({ path: installation });
if (!source.ok) throw source.error;
expect(evidence).toEqual(source.value);
} finally {
await service.close();
}
const query = join(root, "view.json");
await writeFile(
query,
JSON.stringify({
source: { kind: "inline", evidence },
view: {
kind: "desktop-application",
collection: "candidates",
families: ["electron"],
fields: ["family"],
limit: 1,
},
}),
);
const page = await cli.run({
arguments: ["inspect-analysis-view", query, "--json"],
});
expect(page.exitCode).toBe(0);
expect(parseEvidence(page.json).normalized_result).toMatchObject({
items: [{ candidate_id: expect.any(String), family: "electron" }],
coverage: { total: 1, exhausted: true },
});
const bundlePath = join(root, "complete-evidence.json");
const compact = await cli.run({
arguments: [
"inspect-desktop-application",
installation,
"--detail",
"summary",
"--evidence_output",
bundlePath,
"--json",
],
});
expect(compact.exitCode).toBe(0);
expect(compact.json).toMatchObject({
evidence_bundle: { path: bundlePath, bytes: expect.any(Number) },
summary: {
normalized_result: {
kind: "summary",
parent_evidence_id: evidence.evidence_id,
},
},
});
const saved = await readEvidenceBundle(bundlePath);
if (!saved.ok) throw saved.error;
expect(saved.value.records).toEqual([evidence]);
const overwrite = await cli.run({
arguments: [
"inspect-desktop-application",
installation,
"--detail",
"summary",
"--evidence_output",
bundlePath,
"--json",
],
});
expect(overwrite.exitCode).not.toBe(0);
},
);
cliTest(
"refuses a non-directory or missing root as a target selection",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-desktop-cli-root-");
const file = join(root, "regular-file");
await writeFile(file, "fixture\n");
const notDirectory = await cli.run({
arguments: ["inspect-desktop-application", file, "--json"],
});
expect(notDirectory.exitCode).toBe(1);
expect(notDirectory.json).toMatchObject({
code: "unsupported_target",
category: "unsupported_target",
details: { operation: "inspect_desktop_application", path: file },
});
const missingPath = join(root, "missing-directory");
const missing = await cli.run({
arguments: ["inspect-desktop-application", missingPath, "--json"],
});
expect(missing.exitCode).toBe(1);
expect(missing.json).toMatchObject({
code: "target_unavailable",
category: "unavailable",
details: { path: missingPath, reason: expect.stringContaining("ENOENT") },
});
},
);
cliTest.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
"reports a read-denied root as host access denial with read-access recovery",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-desktop-cli-denied-");
const locked = join(root, "locked-directory");
await mkdir(locked);
await chmod(locked, 0o000);
try {
const denied = await cli.run({
arguments: ["inspect-desktop-application", locked, "--json"],
});
expect(denied.exitCode).toBe(1);
expect(denied.json).toMatchObject({
code: "access_denied",
category: "unavailable",
remediation: { action: expect.stringContaining("read access") },
details: {
path: locked,
system_code: "EACCES",
boundary: "filesystem-read",
partial_observation: expect.any(Object),
},
});
} finally {
await chmod(locked, 0o700);
}
},
);