feat(FN-4723): complete Step 4 — gate CLI worktrunk enable
Fusion-Task-Id: FN-4723 Fusion-Task-Lineage: b79b42a0-c65d-4e02-938f-d1ffabc272ee
This commit is contained in:
committed by
gsxdsm
parent
15fdbd2cef
commit
1b93e1a8b1
@@ -25,6 +25,11 @@ vi.mock("@fusion/core", () => {
|
||||
return {
|
||||
GlobalSettingsStore: vi.fn(),
|
||||
DEFAULT_SETTINGS,
|
||||
resolveWorktrunkSettings: (globalValue: any, projectValue: any) => ({
|
||||
enabled: projectValue?.enabled ?? globalValue?.enabled ?? false,
|
||||
...(projectValue?.binaryPath ?? globalValue?.binaryPath ? { binaryPath: projectValue?.binaryPath ?? globalValue?.binaryPath } : {}),
|
||||
onFailure: projectValue?.onFailure ?? globalValue?.onFailure ?? "fail",
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -32,6 +37,16 @@ vi.mock("../../project-context.js", () => ({
|
||||
resolveProject: vi.fn(),
|
||||
}));
|
||||
|
||||
const { resolveWorktrunkBinaryMock, probeWorktrunkMock } = vi.hoisted(() => ({
|
||||
resolveWorktrunkBinaryMock: vi.fn(),
|
||||
probeWorktrunkMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@fusion/engine", () => ({
|
||||
resolveWorktrunkBinary: resolveWorktrunkBinaryMock,
|
||||
probeWorktrunk: probeWorktrunkMock,
|
||||
}));
|
||||
|
||||
import { GlobalSettingsStore, DEFAULT_SETTINGS } from "@fusion/core";
|
||||
import { resolveProject } from "../../project-context.js";
|
||||
import { runSettingsShow, runSettingsSet, parseValue, VALID_SETTINGS } from "../settings.js";
|
||||
@@ -47,6 +62,8 @@ describe("settings commands", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
resolveWorktrunkBinaryMock.mockResolvedValue({ binaryPath: "/usr/local/bin/worktrunk" });
|
||||
probeWorktrunkMock.mockResolvedValue({ ok: true, version: "1.0.0" });
|
||||
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
exitSpy = vi.spyOn(process, "exit").mockImplementation((code?: string | number | null) => {
|
||||
@@ -265,6 +282,38 @@ describe("settings commands", () => {
|
||||
expect(output).toContain("Max Parallel Steps");
|
||||
});
|
||||
|
||||
it("rejects enabling worktrunk when binary is not verified", async () => {
|
||||
const updateSettings = vi.fn();
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({ worktrunk: { enabled: false, onFailure: "fail" } }));
|
||||
(GlobalSettingsStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
updateSettings,
|
||||
getSettings,
|
||||
}));
|
||||
resolveWorktrunkBinaryMock.mockRejectedValueOnce(new Error("missing"));
|
||||
|
||||
await expect(runSettingsSet("worktrunk.enabled", "true")).rejects.toThrow("process.exit:1");
|
||||
expect(updateSettings).not.toHaveBeenCalled();
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("worktrunk.enabled cannot be set to true until the binary is installed and verified"),
|
||||
);
|
||||
});
|
||||
|
||||
it("allows disabling worktrunk without binary verification", async () => {
|
||||
const updateSettings = vi.fn().mockResolvedValue(makeSettings({ worktrunk: { enabled: false, onFailure: "fail" } }));
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({ worktrunk: { enabled: true, onFailure: "fail" } }));
|
||||
(GlobalSettingsStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
updateSettings,
|
||||
getSettings,
|
||||
}));
|
||||
await runSettingsSet("worktrunk.enabled", "false");
|
||||
|
||||
expect(updateSettings).toHaveBeenCalledWith({ worktrunk: { enabled: false, onFailure: "fail" } });
|
||||
expect(resolveWorktrunkBinaryMock).not.toHaveBeenCalled();
|
||||
expect(probeWorktrunkMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runSettingsSet supports worktrunk dotted keys in global scope", async () => {
|
||||
const updateSettings = vi.fn().mockResolvedValue(makeSettings({
|
||||
worktrunk: { enabled: true, binaryPath: "/usr/local/bin/worktrunk", onFailure: "fail" },
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { GlobalSettingsStore, type Settings, type GlobalSettings, DEFAULT_SETTINGS } from "@fusion/core";
|
||||
import {
|
||||
GlobalSettingsStore,
|
||||
type Settings,
|
||||
type GlobalSettings,
|
||||
DEFAULT_SETTINGS,
|
||||
resolveWorktrunkSettings,
|
||||
} from "@fusion/core";
|
||||
import { probeWorktrunk, resolveWorktrunkBinary } from "@fusion/engine";
|
||||
import { resolveProject } from "../project-context.js";
|
||||
|
||||
// Settings that can be updated via CLI
|
||||
@@ -350,6 +357,27 @@ export async function runSettingsSet(key: string, value: string, projectName?: s
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "worktrunk.enabled" && parsedValue === true) {
|
||||
const currentWorktrunk = store
|
||||
? await store.getSettingsByScope().then((scoped) =>
|
||||
resolveWorktrunkSettings(scoped.global?.worktrunk, scoped.project?.worktrunk),
|
||||
)
|
||||
: resolveWorktrunkSettings((await globalStore!.getSettings()).worktrunk, undefined);
|
||||
const nextWorktrunk = { ...currentWorktrunk, enabled: true };
|
||||
|
||||
try {
|
||||
const resolved = await resolveWorktrunkBinary({ settings: nextWorktrunk });
|
||||
const probe = await probeWorktrunk(resolved.binaryPath);
|
||||
if (!probe.ok) {
|
||||
throw new Error("worktrunk probe failed");
|
||||
}
|
||||
} catch {
|
||||
throw new Error(
|
||||
"worktrunk.enabled cannot be set to true until the binary is installed and verified. Install it from Settings → Worktrunk integration (or /api/worktrunk/install-request) first.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (store) {
|
||||
if (key.includes(".")) {
|
||||
const currentSettings = await store.getSettingsByScope();
|
||||
|
||||
Reference in New Issue
Block a user