mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 18:52:48 +00:00
* fix(setup): refuse Hopper on macOS below the release's minimum Hopper setup planning accepted any macOS 12+ host as supported, and the installer never compared the host with the selected release. The current publisher-signed Hopper 6.6.1-demo requires macOS 12.4 (feed deployment_target and the app's LSMinimumSystemVersion), so setup on 12.0-12.3 installed an app the host cannot run. The installer now takes the minimum from the release that owns it: the feed's deployment_target before the package download, and the signed app's LSMinimumSystemVersion (decoded in-process after signature verification) before copying. A host below either fails with the new incompatible_host_version code and an installer detail naming both versions; an incomparable app value is a bundle failure. Planning keeps REA's own macOS 12 floor but reports support as unknown, since the release minimum is read only after approval, and the plan discloses the check. Info.plist decoding is shared with app-bundle resolution. Fixes #8502 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(setup): compare the macOS Hopper plan detail with its owning disclosure The planning test froze an explanatory sentence from the setup plan. The macOS install detail now lives in macHopperInstallDisclosure beside the commands and origins the installer owns, the plan uses it, and the test compares the plan with that constant while keeping its independent support-state assertions. An installer test also pins that the signed app's requirement is read only after both signature checks pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(setup): compare macOS Hopper refusal details with their owners The installer's refusal details were template literals inside installMacHopper, so tests copied whole sentences. The installer now formats them through exported macHopperHostVersionDetail and macHopperIncomparableMinimumDetail. Tests compare each diagnostic and the setup remediation with those owners and SetupInstallFailure's recovery, and separately assert the URL, versions, setting name, failure code and request order. The Info.plist type refusal is checked by error type, key and path instead of a sentence fragment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
4.0 KiB
TypeScript
96 lines
4.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { HOPPER_PUBLISHER } from "../domain/hopperPublisher.js";
|
|
import { setupInstallFailure } from "./SetupInstallFailure.js";
|
|
|
|
describe("macOS Hopper setup failure projection", () => {
|
|
it("projects a publisher mismatch onto its own code and recovery", () => {
|
|
expect(setupInstallFailure("publisher_verification_failed")).toEqual({
|
|
status: "failed",
|
|
code: "publisher_verification_failed",
|
|
remediation: `The mounted Hopper application is not signed by the publisher REA pins (${HOPPER_PUBLISHER.bundleIdentifier}, Apple team ${HOPPER_PUBLISHER.teamIdentifier}); nothing was copied or launched. Install Hopper yourself and set HOPPER_LAUNCHER_PATH to its launcher, then rerun rea setup.`,
|
|
});
|
|
});
|
|
|
|
it("keeps a broken bundle distinct from a publisher mismatch", () => {
|
|
expect(setupInstallFailure("bundle")).toEqual({
|
|
status: "failed",
|
|
code: "bundle_invalid",
|
|
remediation:
|
|
"The mounted package did not contain the expected Hopper application bundle. Retry setup; if it repeats, update REA before installing.",
|
|
});
|
|
});
|
|
|
|
it("keeps a host below the release minimum distinct from an unsupported host", () => {
|
|
const incompatible = setupInstallFailure("incompatible_host_version");
|
|
expect(incompatible).toMatchObject({
|
|
status: "failed",
|
|
code: "incompatible_host_version",
|
|
});
|
|
expect(incompatible.remediation).toContain("HOPPER_LAUNCHER_PATH");
|
|
const unsupported = setupInstallFailure("unsupported_host");
|
|
expect(unsupported.code).toBe("unsupported_host");
|
|
expect(incompatible.remediation).not.toBe(unsupported.remediation);
|
|
});
|
|
});
|
|
|
|
describe("Linux package-manager setup failure projection", () => {
|
|
it("quotes the failing command and a sanitized bounded diagnostic", () => {
|
|
const result = setupInstallFailure("authorization_or_package_manager", {
|
|
packageFailure: {
|
|
command: 'pkexec pacman -U "/tmp/hopper package.pkg.tar.xz"',
|
|
exitCode: 1,
|
|
stdout: "",
|
|
stderr: "\u001b[31merror:\u001b[0m package conflict\nrefused",
|
|
outputTruncated: false,
|
|
},
|
|
});
|
|
expect(result).toMatchObject({
|
|
status: "failed",
|
|
code: "authorization_or_package_manager_failed",
|
|
});
|
|
if (result.status !== "failed") throw new Error("expected failure");
|
|
expect(result.remediation).toContain(
|
|
'pkexec pacman -U "/tmp/hopper package.pkg.tar.xz"; exit 1',
|
|
);
|
|
expect(result.remediation).toContain("error: package conflict refused");
|
|
expect(result.remediation).not.toContain("\u001b");
|
|
expect(result.remediation).not.toContain("\n");
|
|
});
|
|
|
|
it("preserves the established remediation if no package output exists", () => {
|
|
expect(setupInstallFailure("authorization_or_package_manager")).toEqual({
|
|
status: "failed",
|
|
code: "authorization_or_package_manager_failed",
|
|
remediation:
|
|
"Allow the system package-manager operation or install the planned Hopper package and Linux runtime dependencies, then rerun rea setup.",
|
|
});
|
|
});
|
|
|
|
it("keeps truncated package output within its excerpt budget", () => {
|
|
const result = setupInstallFailure("authorization_or_package_manager", {
|
|
packageFailure: {
|
|
command: "pacman -U hopper.pkg.tar.xz",
|
|
exitCode: 1,
|
|
stdout: "",
|
|
stderr: `${"warning ".repeat(100)}final package conflict`,
|
|
outputTruncated: true,
|
|
},
|
|
});
|
|
if (result.status !== "failed") throw new Error("expected failure");
|
|
const excerpt = result.remediation
|
|
.split("its output: ")[1]
|
|
?.split(" Resolve")[0];
|
|
expect(excerpt).toContain("output truncated");
|
|
expect(excerpt?.length).toBeLessThanOrEqual(400);
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
["destination_inspection", "destination_inspection_failed"],
|
|
["archive_write", "archive_write_failed"],
|
|
["launcher_inspection", "launcher_inspection_failed"],
|
|
] as const)("retains %s as a distinct setup failure", (reason, code) => {
|
|
expect(setupInstallFailure(reason).code).toBe(code);
|
|
});
|