FN-5812: add onboard autolaunch backcompat guard coverage
Protect onboarding autolaunch behavior so legacy projects and agent-run commands are not blocked. - add end-to-end CLI test coverage for onboarding autolaunch backcompat guard behavior - document the onboarding autolaunch compatibility behavior in the CLI reference - add a patch changeset for @runfusion/fusion release tracking Files changed: .changeset/fn-5812-onboard-backcompat-guard.md | 5 + docs/cli-reference.md | 6 + packages/cli/src/commands/__tests__/onboard-autolaunch-backcompat-e2e.test.ts | 161 +++++++++++++++++++++ 3 files changed, 172 insertions(+) Fusion-Task-Id: FN-5812 Fusion-Task-Lineage: d735ef0e-1abc-4c22-bd11-07c2de69ace1
This commit is contained in:
5
.changeset/fn-5812-onboard-backcompat-guard.md
Normal file
5
.changeset/fn-5812-onboard-backcompat-guard.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Add orchestrator-level regression coverage and CLI docs that guarantee onboarding auto-launch never blocks existing projects, non-TTY/headless workflows, or agent-run `fn` commands.
|
||||
@@ -67,6 +67,12 @@ fn onboard --force
|
||||
The command is safe to re-run and only updates the settings you confirm during
|
||||
prompts.
|
||||
|
||||
Backward compatibility guarantee: onboarding auto-launch never interrupts
|
||||
existing setups and never blocks headless usage. It is skipped when both the
|
||||
central DB and local project DB already exist, and it never prompts in non-TTY,
|
||||
`serve`, `daemon`, `--skip-onboarding`, `FUSION_SKIP_ONBOARDING`, piped, or
|
||||
agent-run invocations.
|
||||
|
||||
---
|
||||
|
||||
## `fn update`
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { maybeAutoLaunchOnboarding } from "../onboard-autolaunch.js";
|
||||
|
||||
describe("maybeAutoLaunchOnboarding backward-compat e2e guard", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("does not prompt onboarded interactive users with central and project DB present", async () => {
|
||||
const runOnboard = vi.fn();
|
||||
const pathExists = vi.fn((path: string) =>
|
||||
path === "/virtual/central.db" || path.endsWith("/.fusion/fusion.db"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
maybeAutoLaunchOnboarding({
|
||||
command: "task",
|
||||
args: ["task", "list"],
|
||||
centralDbPath: "/virtual/central.db",
|
||||
cwd: "/repo/demo",
|
||||
isTTY: true,
|
||||
pathExists,
|
||||
runOnboard,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(runOnboard).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("never blocks agent-run non-TTY task commands when nothing is initialized", async () => {
|
||||
const runOnboard = vi.fn();
|
||||
|
||||
await expect(
|
||||
maybeAutoLaunchOnboarding({
|
||||
command: "task",
|
||||
args: ["task", "list"],
|
||||
centralDbPath: "/virtual/central.db",
|
||||
isTTY: false,
|
||||
pathExists: () => false,
|
||||
runOnboard,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(runOnboard).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips serve and daemon commands even with missing DB", async () => {
|
||||
for (const command of ["serve", "daemon"]) {
|
||||
const runOnboard = vi.fn();
|
||||
|
||||
await expect(
|
||||
maybeAutoLaunchOnboarding({
|
||||
command,
|
||||
args: [command],
|
||||
centralDbPath: "/virtual/central.db",
|
||||
isTTY: true,
|
||||
pathExists: () => false,
|
||||
runOnboard,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(runOnboard).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it("honors --skip-onboarding end-to-end", async () => {
|
||||
const runOnboard = vi.fn();
|
||||
|
||||
await expect(
|
||||
maybeAutoLaunchOnboarding({
|
||||
command: "task",
|
||||
args: ["task", "list", "--skip-onboarding"],
|
||||
centralDbPath: "/virtual/central.db",
|
||||
isTTY: true,
|
||||
pathExists: () => false,
|
||||
runOnboard,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(runOnboard).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("honors FUSION_SKIP_ONBOARDING end-to-end", async () => {
|
||||
const runOnboard = vi.fn();
|
||||
|
||||
await expect(
|
||||
maybeAutoLaunchOnboarding({
|
||||
command: "task",
|
||||
args: ["task", "list"],
|
||||
centralDbPath: "/virtual/central.db",
|
||||
isTTY: true,
|
||||
env: { FUSION_SKIP_ONBOARDING: "true" },
|
||||
pathExists: () => false,
|
||||
runOnboard,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(runOnboard).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("isolates runOnboard failures as non-fatal with one diagnostic", async () => {
|
||||
const runOnboard = vi.fn().mockRejectedValue(new Error("boom"));
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
||||
|
||||
await expect(
|
||||
maybeAutoLaunchOnboarding({
|
||||
command: "task",
|
||||
args: ["task", "list"],
|
||||
centralDbPath: "/virtual/central.db",
|
||||
isTTY: true,
|
||||
pathExists: () => false,
|
||||
runOnboard,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("[onboard-autolaunch] non-fatal onboard launch failure: boom"),
|
||||
);
|
||||
});
|
||||
|
||||
it("launches onboarding once for legitimate first-run interactive commands", async () => {
|
||||
const runOnboard = vi.fn();
|
||||
|
||||
await expect(
|
||||
maybeAutoLaunchOnboarding({
|
||||
command: "task",
|
||||
args: ["task", "list"],
|
||||
centralDbPath: "/virtual/central.db",
|
||||
isTTY: true,
|
||||
pathExists: () => false,
|
||||
runOnboard,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(runOnboard).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("derives projectInitialized from cwd/pathExists seam without real filesystem", async () => {
|
||||
const runOnboard = vi.fn();
|
||||
const pathExists = vi.fn((path: string) =>
|
||||
path === "/virtual/central.db" || path === "/workspace/demo/.fusion/fusion.db",
|
||||
);
|
||||
|
||||
await expect(
|
||||
maybeAutoLaunchOnboarding({
|
||||
command: "task",
|
||||
args: ["task", "list"],
|
||||
centralDbPath: "/virtual/central.db",
|
||||
cwd: "/workspace/demo",
|
||||
isTTY: true,
|
||||
pathExists,
|
||||
runOnboard,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(pathExists).toHaveBeenCalledWith("/workspace/demo/.fusion/fusion.db");
|
||||
expect(runOnboard).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user