chore: consolidate test files into __tests__/ dirs and clean stray engine artifacts
- Move all co-located *.test.* files into sibling __tests__/ directories so the
layout is consistent across packages (159 renames + content-rewrite moves).
Updates relative imports, vi.mock specifiers, and __dirname/import.meta.url
path resolutions where tests read fixtures from disk.
- Drop tracked tsc-emit alongside engine .ts sources (auth-storage/logger/
skill-resolver/context-limit-detector/pi.{js,d.ts,*.map}). These were
accidentally committed in a merge and the stale pi.js was masking a real
test-mock vs source mismatch (tests imported "../pi.js" and vite preferred
the stale build over pi.ts).
- Add packages/engine/.gitignore to block future src/*.{js,d.ts,map}.
- Refactor plugin pi-module seams (openclaw/paperclip/hermes) to ESM-import
createFnAgent / promptWithFallback / describeModel from @fusion/engine
instead of require()-ing packages/engine/src/pi.js. Adds @fusion/engine to
the two plugin package.jsons that were missing it; exports describeModel
from the engine public API.
- Fix engine test mocks now that they run against current pi.ts: add
ModelRegistry.create static to mocks in pi.test.ts and pi-create-fn-agent
.test.ts; switch three boundary-result toEqual assertions to toMatchObject
so the new content/isError fields don't trip exact-match comparison.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
208
packages/cli/src/commands/__tests__/settings.test.ts
Normal file
208
packages/cli/src/commands/__tests__/settings.test.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
vi.mock("@fusion/core", () => {
|
||||
const DEFAULT_SETTINGS = {
|
||||
maxConcurrent: 2,
|
||||
maxWorktrees: 4,
|
||||
autoResolveConflicts: true,
|
||||
smartConflictResolution: true,
|
||||
requirePlanApproval: false,
|
||||
ntfyEnabled: false,
|
||||
ntfyTopic: undefined,
|
||||
worktreeNaming: "random",
|
||||
githubTokenConfigured: false,
|
||||
defaultProvider: undefined,
|
||||
defaultModelId: undefined,
|
||||
};
|
||||
|
||||
return {
|
||||
GlobalSettingsStore: vi.fn(),
|
||||
DEFAULT_SETTINGS,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../project-context.js", () => ({
|
||||
resolveProject: vi.fn(),
|
||||
}));
|
||||
|
||||
import { GlobalSettingsStore, DEFAULT_SETTINGS } from "@fusion/core";
|
||||
import { resolveProject } from "../../project-context.js";
|
||||
import { runSettingsShow, runSettingsSet, parseValue, VALID_SETTINGS } from "../settings.js";
|
||||
|
||||
function makeSettings(overrides: Record<string, unknown> = {}) {
|
||||
return { ...DEFAULT_SETTINGS, ...overrides };
|
||||
}
|
||||
|
||||
describe("settings commands", () => {
|
||||
let logSpy: ReturnType<typeof vi.spyOn>;
|
||||
let errorSpy: ReturnType<typeof vi.spyOn>;
|
||||
let exitSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
exitSpy = vi.spyOn(process, "exit").mockImplementation((code?: string | number | null) => {
|
||||
throw new Error(`process.exit:${code ?? 0}`);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
logSpy.mockRestore();
|
||||
errorSpy.mockRestore();
|
||||
exitSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("exposes expected valid settings and parser behavior", () => {
|
||||
expect(VALID_SETTINGS).toContain("maxConcurrent");
|
||||
expect(parseValue("ntfyEnabled", "yes")).toBe(true);
|
||||
expect(parseValue("maxConcurrent", "4")).toBe(4);
|
||||
expect(parseValue("worktreeNaming", "task-id")).toBe("task-id");
|
||||
});
|
||||
|
||||
it("runSettingsShow without project uses global settings even if a project could resolve", async () => {
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({ ntfyEnabled: true }));
|
||||
(GlobalSettingsStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
getSettings,
|
||||
}));
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { getSettings: vi.fn() } as any,
|
||||
});
|
||||
|
||||
await runSettingsShow();
|
||||
|
||||
expect(getSettings).toHaveBeenCalled();
|
||||
expect(resolveProject).not.toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(" fn Global Settings");
|
||||
});
|
||||
|
||||
it("runSettingsShow with project uses project store", async () => {
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({ maxConcurrent: 5 }));
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { getSettings } as any,
|
||||
});
|
||||
|
||||
await runSettingsShow("demo-project");
|
||||
|
||||
expect(resolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(logSpy).toHaveBeenCalledWith(" fn Settings for project 'demo-project'");
|
||||
});
|
||||
|
||||
it("runSettingsSet without project updates global-only settings", async () => {
|
||||
const updateSettings = vi.fn().mockResolvedValue(makeSettings({ ntfyEnabled: true }));
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({ ntfyEnabled: true }));
|
||||
(GlobalSettingsStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
updateSettings,
|
||||
getSettings,
|
||||
}));
|
||||
|
||||
await runSettingsSet("ntfyEnabled", "true");
|
||||
|
||||
expect(updateSettings).toHaveBeenCalledWith({ ntfyEnabled: true });
|
||||
expect(resolveProject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runSettingsSet with project updates project-only settings", async () => {
|
||||
const updateSettings = vi.fn().mockResolvedValue(makeSettings({ maxConcurrent: 6 }));
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({ maxConcurrent: 6 }));
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { updateSettings, getSettings } as any,
|
||||
});
|
||||
|
||||
await runSettingsSet("maxConcurrent", "6", "demo-project");
|
||||
|
||||
expect(resolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(updateSettings).toHaveBeenCalledWith({ maxConcurrent: 6 });
|
||||
});
|
||||
|
||||
it("rejects global-only settings for project scope", async () => {
|
||||
await expect(runSettingsSet("ntfyEnabled", "true", "demo-project")).rejects.toThrow("process.exit:1");
|
||||
expect(errorSpy).toHaveBeenCalledWith('Error: Setting "ntfyEnabled" is global-only. Omit --project to update it.');
|
||||
});
|
||||
|
||||
it("rejects project-only settings without explicit project scope", async () => {
|
||||
await expect(runSettingsSet("maxConcurrent", "4")).rejects.toThrow("process.exit:1");
|
||||
expect(errorSpy).toHaveBeenCalledWith('Error: Setting "maxConcurrent" is project-only. Use --project or run from a project directory.');
|
||||
expect(resolveProject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runSettingsSet with project updates runStepsInNewSessions", async () => {
|
||||
const updateSettings = vi.fn().mockResolvedValue(makeSettings({ runStepsInNewSessions: true }));
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({ runStepsInNewSessions: true }));
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { updateSettings, getSettings } as any,
|
||||
});
|
||||
|
||||
await runSettingsSet("runStepsInNewSessions", "true", "demo-project");
|
||||
|
||||
expect(updateSettings).toHaveBeenCalledWith({ runStepsInNewSessions: true });
|
||||
});
|
||||
|
||||
it("runSettingsSet with project updates maxParallelSteps", async () => {
|
||||
const updateSettings = vi.fn().mockResolvedValue(makeSettings({ maxParallelSteps: 3 }));
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({ maxParallelSteps: 3 }));
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { updateSettings, getSettings } as any,
|
||||
});
|
||||
|
||||
await runSettingsSet("maxParallelSteps", "3", "demo-project");
|
||||
|
||||
expect(updateSettings).toHaveBeenCalledWith({ maxParallelSteps: 3 });
|
||||
});
|
||||
|
||||
it("rejects maxParallelSteps values outside range", async () => {
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { updateSettings: vi.fn(), getSettings: vi.fn() } as any,
|
||||
});
|
||||
|
||||
await expect(runSettingsSet("maxParallelSteps", "5", "demo-project")).rejects.toThrow("process.exit:1");
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("Value out of range for maxParallelSteps"));
|
||||
});
|
||||
|
||||
it("runSettingsShow displays Execution section with step-session settings", async () => {
|
||||
const getSettings = vi.fn().mockResolvedValue(makeSettings({
|
||||
runStepsInNewSessions: true,
|
||||
maxParallelSteps: 3,
|
||||
}));
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { getSettings } as any,
|
||||
});
|
||||
|
||||
await runSettingsShow("demo-project");
|
||||
|
||||
const output = logSpy.mock.calls.map((args) => args.join(" ")).join("\n");
|
||||
expect(output).toContain("Execution");
|
||||
expect(output).toContain("Run Steps In New Sessions");
|
||||
expect(output).toContain("Max Parallel Steps");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user