test(FN-1283): expand CLI settings and routing coverage
- Add comprehensive settings-import tests for global/project scope handling and error paths - Add settings-export tests covering output targets, filtering, and serialization behavior - Expand project-resolver test matrix for flag/default/CWD resolution and fallback cases - Extend bin entrypoint tests for command routing, argument forwarding, and failure handling
This commit is contained in:
146
packages/cli/src/commands/__tests__/settings-export.test.ts
Normal file
146
packages/cli/src/commands/__tests__/settings-export.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
import { TaskStore, exportSettings, generateExportFilename } from "@fusion/core";
|
||||
import { resolveProject } from "../../project-context.js";
|
||||
|
||||
const mockStoreInit = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
vi.mock("node:fs/promises", () => ({
|
||||
writeFile: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@fusion/core", () => ({
|
||||
TaskStore: vi.fn().mockImplementation(() => ({
|
||||
init: mockStoreInit,
|
||||
})),
|
||||
exportSettings: vi.fn(),
|
||||
generateExportFilename: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../project-context.js", () => ({
|
||||
resolveProject: vi.fn(),
|
||||
}));
|
||||
|
||||
import { runSettingsExport } from "../settings-export.js";
|
||||
|
||||
describe("runSettingsExport", () => {
|
||||
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation((code?: string | number | null) => {
|
||||
if ((code ?? 0) === 0) {
|
||||
return undefined as never;
|
||||
}
|
||||
throw new Error(`process.exit:${code ?? 0}`);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo",
|
||||
projectPath: "/tmp/demo",
|
||||
isRegistered: true,
|
||||
store: {} as any,
|
||||
});
|
||||
|
||||
vi.mocked(generateExportFilename).mockReturnValue("fusion-settings-2026-04-08-120000.json");
|
||||
vi.mocked(exportSettings).mockResolvedValue({
|
||||
version: 1,
|
||||
exportedAt: "2026-04-08T12:00:00.000Z",
|
||||
global: { ntfyEnabled: true, defaultProvider: "anthropic" },
|
||||
project: { maxConcurrent: 3, maxWorktrees: 4 },
|
||||
} as any);
|
||||
vi.mocked(writeFile).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("exports successfully with an auto-generated filename", async () => {
|
||||
await runSettingsExport();
|
||||
|
||||
const expectedPath = join(process.cwd(), "fusion-settings-2026-04-08-120000.json");
|
||||
expect(generateExportFilename).toHaveBeenCalled();
|
||||
expect(writeFile).toHaveBeenCalledWith(expectedPath, expect.any(String));
|
||||
expect(logSpy).toHaveBeenCalledWith(` ✓ Settings exported to ${expectedPath}`);
|
||||
expect(logSpy).toHaveBeenCalledWith(" Exported: 2 global setting(s), 2 project setting(s)");
|
||||
expect(exitSpy).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it("exports successfully with a custom --output path", async () => {
|
||||
await runSettingsExport({ output: "./tmp/exported.json" });
|
||||
|
||||
expect(writeFile).toHaveBeenCalledWith(resolve("./tmp/exported.json"), expect.any(String));
|
||||
});
|
||||
|
||||
it("exports only global settings with --scope global", async () => {
|
||||
vi.mocked(exportSettings).mockResolvedValue({
|
||||
version: 1,
|
||||
exportedAt: "2026-04-08T12:00:00.000Z",
|
||||
global: { ntfyEnabled: true },
|
||||
} as any);
|
||||
|
||||
await runSettingsExport({ scope: "global" });
|
||||
|
||||
expect(exportSettings).toHaveBeenCalledWith(expect.any(Object), { scope: "global" });
|
||||
expect(logSpy).toHaveBeenCalledWith(" Exported: 1 global setting(s)");
|
||||
expect(logSpy).not.toHaveBeenCalledWith(expect.stringContaining("project setting"));
|
||||
});
|
||||
|
||||
it("exports only project settings with --scope project", async () => {
|
||||
vi.mocked(exportSettings).mockResolvedValue({
|
||||
version: 1,
|
||||
exportedAt: "2026-04-08T12:00:00.000Z",
|
||||
project: { maxConcurrent: 6 },
|
||||
} as any);
|
||||
|
||||
await runSettingsExport({ scope: "project" });
|
||||
|
||||
expect(exportSettings).toHaveBeenCalledWith(expect.any(Object), { scope: "project" });
|
||||
expect(logSpy).toHaveBeenCalledWith(" Exported: 1 project setting(s)");
|
||||
expect(logSpy).not.toHaveBeenCalledWith(expect.stringContaining("global setting"));
|
||||
});
|
||||
|
||||
it("handles export with empty global/project settings", async () => {
|
||||
vi.mocked(exportSettings).mockResolvedValue({
|
||||
version: 1,
|
||||
exportedAt: "2026-04-08T12:00:00.000Z",
|
||||
global: {},
|
||||
project: {},
|
||||
} as any);
|
||||
|
||||
await runSettingsExport();
|
||||
|
||||
expect(logSpy).not.toHaveBeenCalledWith(expect.stringContaining("Exported:"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it("prints an error and exits when exportSettings throws", async () => {
|
||||
vi.mocked(exportSettings).mockRejectedValue(new Error("export failed"));
|
||||
|
||||
await expect(runSettingsExport()).rejects.toThrow("process.exit:1");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith("Error: export failed");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("prints an error and exits when writing the file fails", async () => {
|
||||
vi.mocked(writeFile).mockRejectedValue(new Error("permission denied"));
|
||||
|
||||
await expect(runSettingsExport()).rejects.toThrow("process.exit:1");
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith("Error: permission denied");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("resolves project name and initializes store at the resolved path", async () => {
|
||||
await runSettingsExport({ projectName: "alpha" });
|
||||
|
||||
expect(resolveProject).toHaveBeenCalledWith("alpha");
|
||||
expect(TaskStore).toHaveBeenCalledWith("/tmp/demo");
|
||||
expect(mockStoreInit).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user