mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 18:52:48 +00:00
* refactor: remove redundant seams and preserve cleanup ownership * fix: validate PE resource extents before budget checks
275 lines
9.2 KiB
TypeScript
275 lines
9.2 KiB
TypeScript
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { expect, it, vi } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
|
|
import { createDeferred } from "../../tests/fixtures/binarySession.js";
|
|
import { awaitAcknowledgement } from "../../tests/fixtures/lifecycleAcknowledgement.js";
|
|
import { providerSnapshot } from "../../tests/support/reverseEngineeringCompositionFixtures.js";
|
|
import * as owned from "../process/OwnedCommand.js";
|
|
import { systemDoctorHost } from "../application/Doctor.js";
|
|
import { PRODUCT_IDENTITY } from "../identity.js";
|
|
import { systemDesktopBuildSetupHost } from "./DesktopBuildSetupHost.js";
|
|
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import { DESKTOP_EXPORT_FRAMEWORK_VERSION } from "../domain/desktop/appExport.js";
|
|
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
|
|
|
const program = (path: string, body: string) =>
|
|
writeFile(path, `#!/bin/sh\n${body}\n`, { mode: 0o755 });
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"preserves the selected archive through symlink and parent traversal",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-setup-selected-archive-");
|
|
await mkdir(join(root, "elsewhere", "child"), { recursive: true });
|
|
await symlink(join(root, "elsewhere", "child"), join(root, "link"));
|
|
await writeFile(join(root, "archive.tar.xz"), "wrong archive");
|
|
await writeFile(
|
|
join(root, "elsewhere", "archive.tar.xz"),
|
|
"selected archive",
|
|
);
|
|
const selected = `${root}/link/../archive.tar.xz`;
|
|
const resources = new ArtifactResourceScope();
|
|
try {
|
|
const facts = await systemDesktopBuildSetupHost(
|
|
{ ...process.env, REA_BUILD_COMPUTER_REA_ARCHIVE: selected },
|
|
process.platform,
|
|
undefined,
|
|
resources,
|
|
).facts([]);
|
|
expect(facts.settings.REA_BUILD_COMPUTER_REA_ARCHIVE).toBe(selected);
|
|
expect(
|
|
await readFile(facts.settings.REA_BUILD_COMPUTER_REA_ARCHIVE!, "utf8"),
|
|
).toBe("selected archive");
|
|
} finally {
|
|
await resources.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("retains installer cancellation, command outcome and output", async () => {
|
|
const resources = new ArtifactResourceScope();
|
|
const controller = new AbortController();
|
|
const reason = new Error("cancel installer");
|
|
const entered = createDeferred<void>();
|
|
const uv = "/recorded/uv";
|
|
const command = vi
|
|
.spyOn(owned, "runOwnedCommand")
|
|
.mockImplementation(async (spawn, _limits, options) => {
|
|
expect(spawn.command).toBe(uv);
|
|
const signal = options?.signal;
|
|
if (signal === undefined)
|
|
throw new Error("Expected setup cancellation signal");
|
|
entered.resolve();
|
|
await new Promise<void>((resolve) => {
|
|
signal.addEventListener("abort", () => resolve(), { once: true });
|
|
if (signal.aborted) resolve();
|
|
});
|
|
throw new owned.OwnedCommandFailure(
|
|
"cancelled",
|
|
"Installer cancelled",
|
|
providerSnapshot({
|
|
stderr: {
|
|
text: "installer diagnostic",
|
|
bytes: 20,
|
|
observedBytes: 20,
|
|
},
|
|
signal: "SIGTERM",
|
|
exitCode: null,
|
|
}),
|
|
null,
|
|
{ cancellation: { reason: signal.reason } },
|
|
);
|
|
});
|
|
const pending = systemDesktopBuildSetupHost(
|
|
{},
|
|
process.platform,
|
|
undefined,
|
|
resources,
|
|
).installAgentenv(uv, controller.signal);
|
|
void pending.catch(() => undefined);
|
|
try {
|
|
await awaitAcknowledgement(
|
|
entered.promise,
|
|
pending,
|
|
"provider lifecycle boundary",
|
|
);
|
|
controller.abort(reason);
|
|
const failure: unknown = await pending.catch((cause: unknown) => cause);
|
|
if (!(failure instanceof AnalysisCancelledError)) throw failure;
|
|
expect(failure.cause).toBe(reason);
|
|
expect(failure.capturedOutput?.stderr).toContain("installer diagnostic");
|
|
expect(failure.diagnostics).toMatchObject({
|
|
command_failure: "cancelled",
|
|
setup_failure: expect.stringContaining("installer diagnostic"),
|
|
});
|
|
expect(failure.secondaryFailures).toEqual([]);
|
|
} finally {
|
|
controller.abort();
|
|
await Promise.allSettled([pending]);
|
|
try {
|
|
await resources.close();
|
|
} finally {
|
|
command.mockRestore();
|
|
}
|
|
}
|
|
});
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"runs the requested pinned installer as an owned command",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-owned-agentenv-install-");
|
|
const uv = join(root, "uv");
|
|
const log = join(root, "arguments");
|
|
await program(uv, 'printf "%s\\n" "$@" > "$REA_TEST_UV_LOG"');
|
|
const resources = new ArtifactResourceScope();
|
|
try {
|
|
const host = systemDesktopBuildSetupHost(
|
|
{ ...process.env, REA_TEST_UV_LOG: log },
|
|
process.platform,
|
|
undefined,
|
|
resources,
|
|
);
|
|
expect(await host.installAgentenv(uv)).toBeUndefined();
|
|
expect(await readFile(log, "utf8")).toBe(
|
|
`tool\ninstall\nagentenv-framework==${DESKTOP_EXPORT_FRAMEWORK_VERSION}\n`,
|
|
);
|
|
expect(resources.hasPending).toBe(false);
|
|
} finally {
|
|
await resources.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32").each([
|
|
["an archive setup packed for another version", false, true],
|
|
["an archive setup packed for another version, since deleted", false, false],
|
|
["an archive the user chose", true, true],
|
|
] as const)(
|
|
"replaces only setup's own archive in a registration: %s",
|
|
async (_, kept, present) => {
|
|
const root = await createTestTempDirectory("rea-desktop-setup-facts-");
|
|
const bin = join(root, "bin");
|
|
const home = join(root, "home");
|
|
const cache = join(root, "cache");
|
|
const owned = join(cache, "rea/build-computer");
|
|
await mkdir(bin);
|
|
await mkdir(home);
|
|
await mkdir(owned, { recursive: true });
|
|
await program(join(bin, "docker"), "exit 0");
|
|
await program(join(bin, "agent-env"), 'echo "agent-env 0.9.1485"');
|
|
await program(
|
|
join(bin, "uv"),
|
|
`[ "$1 $2 $3" = "tool dir --bin" ] && echo ${join(home, ".local/bin")}`,
|
|
);
|
|
const previous = join(owned, "rea-6.7.0.tar.xz");
|
|
if (present) await writeFile(previous, "packed by an earlier setup");
|
|
await writeFile(
|
|
join(home, ".claude.json"),
|
|
JSON.stringify({
|
|
mcpServers: {
|
|
rea: {
|
|
command: process.execPath,
|
|
args: ["/opt/rea/scripts/rea.mjs", "mcp"],
|
|
env: {
|
|
REA_DOCKER_COMMAND: "/custom/docker",
|
|
REA_BUILD_COMPUTER_REA_ARCHIVE: kept
|
|
? "/srv/rea-mine.tar.xz"
|
|
: previous,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
const environment = {
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
PATH: bin,
|
|
XDG_CACHE_HOME: cache,
|
|
};
|
|
const doctor = systemDoctorHost({ environment, platform: "linux" });
|
|
try {
|
|
const facts = await systemDesktopBuildSetupHost(
|
|
environment,
|
|
"linux",
|
|
doctor.clientRegistrations,
|
|
new ArtifactResourceScope(),
|
|
).facts(["claude_code"]);
|
|
|
|
expect(facts).toMatchObject({
|
|
settings: {
|
|
REA_AGENT_ENV_COMMAND: join(bin, "agent-env"),
|
|
REA_DOCKER_COMMAND: join(bin, "docker"),
|
|
REA_BUILD_COMPUTER_REA_ARCHIVE: join(
|
|
owned,
|
|
`rea-${PRODUCT_IDENTITY.packageVersion}.tar.xz`,
|
|
),
|
|
},
|
|
archivePacked: false,
|
|
agentEnvVersion: "0.9.1485",
|
|
uv: {
|
|
path: join(bin, "uv"),
|
|
agentEnv: join(home, ".local/bin/agent-env"),
|
|
},
|
|
recorded: {
|
|
claude_code: kept
|
|
? ["REA_DOCKER_COMMAND", "REA_BUILD_COMPUTER_REA_ARCHIVE"]
|
|
: ["REA_DOCKER_COMMAND"],
|
|
},
|
|
});
|
|
} finally {
|
|
await doctor.close?.();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"counts the runner as unrecorded where a registration lacks it and it is not installed yet",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-desktop-setup-runner-");
|
|
const bin = join(root, "bin");
|
|
const home = join(root, "home");
|
|
await mkdir(bin);
|
|
await mkdir(home);
|
|
await program(join(bin, "docker"), "exit 0");
|
|
await writeFile(
|
|
join(home, ".claude.json"),
|
|
JSON.stringify({
|
|
mcpServers: {
|
|
rea: {
|
|
command: process.execPath,
|
|
args: ["/opt/rea/scripts/rea.mjs", "mcp"],
|
|
env: {
|
|
REA_DOCKER_COMMAND: "/custom/docker",
|
|
REA_BUILD_COMPUTER_REA_ARCHIVE: "/srv/rea-mine.tar.xz",
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
const environment = {
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
PATH: bin,
|
|
XDG_CACHE_HOME: join(root, "cache"),
|
|
};
|
|
const doctor = systemDoctorHost({ environment, platform: "linux" });
|
|
try {
|
|
const facts = await systemDesktopBuildSetupHost(
|
|
environment,
|
|
"linux",
|
|
doctor.clientRegistrations,
|
|
new ArtifactResourceScope(),
|
|
).facts(["claude_code"]);
|
|
|
|
expect(facts.settings).not.toHaveProperty("REA_AGENT_ENV_COMMAND");
|
|
expect(facts.recorded).toEqual({
|
|
claude_code: ["REA_DOCKER_COMMAND", "REA_BUILD_COMPUTER_REA_ARCHIVE"],
|
|
});
|
|
} finally {
|
|
await doctor.close?.();
|
|
}
|
|
},
|
|
);
|