feat(KB-503): merge kb/kb-503
- feat(KB-503): complete Step 5 other command project support - fix(KB-503): align task resolution with shared project context - fix(KB-503): preserve local task command fallback - test(KB-503): complete Step 4 task command coverage - test(KB-503): harden Step 3 CLI parser coverage - fix(KB-503): keep settings set scope explicit - fix(KB-503): allow implicit project resolution for project settings - fix(KB-503): enforce explicit settings scope rules - fix(KB-503): restore shared settings resolution behavior - fix(KB-503): align settings scope and project flag behavior - feat(KB-503): complete Step 5 — add project-aware settings, git, and backup behavior - fix(KB-503): enforce settings scope for global and project modes - fix(KB-503): preserve project-scoped settings and cwd-aware git helpers - fix(KB-503): add scoped settings behavior and git project tests - feat(KB-503): complete Step 5 — add project-aware git and backup coverage - test(KB-503): cover remaining project-aware task command paths - fix(KB-503): use shared resolution flow for all task commands - fix(KB-503): restore legacy task fallback and correct task mocks - fix(KB-503): align task resolution order and kb storage paths - fix(KB-503): restore local task store fallback without project flag - test(KB-503): cover remaining project-aware task handlers - fix(KB-503): preserve branch naming and avoid duplicate log resolution - test(KB-503): expand project-aware task command coverage - fix(KB-503): restore cwd fallback for path-based task commands - fix(KB-503): add project-aware task output and tests - fix(KB-503): harden project command output and coverage - fix(KB-503): preserve legacy task resolution without project flag - test(KB-503): add bin routing coverage for project flag parsing - feat(KB-503): complete Step 3 — CLI argument parsing updates - fix(KB-503): add defaultProjectId to GlobalSettings type and fix TypeScript errors - test(KB-503): fix resolveProject mock in task tests for runTaskLogs follow mode - docs(KB-503): add multi-project CLI documentation and changeset - test(KB-503): add project-context mock to task tests - test(KB-503): update git tests for new cwd parameter - feat(KB-503): Step 3 — CLI argument parsing updates with --project flag support - feat(KB-503): Steps 1-2 — project context utilities and project subcommands
This commit is contained in:
110
packages/cli/src/commands/backup.test.ts
Normal file
110
packages/cli/src/commands/backup.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
const mockListBackups = vi.fn();
|
||||
const mockRestoreBackup = vi.fn();
|
||||
const mockCleanupOldBackups = vi.fn();
|
||||
const mockGetSettings = vi.fn();
|
||||
const mockRunBackupCommand = vi.fn();
|
||||
const mockResolveProject = vi.fn();
|
||||
|
||||
vi.mock("@fusion/core", () => ({
|
||||
BackupManager: vi.fn(),
|
||||
TaskStore: vi.fn().mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
getSettings: mockGetSettings,
|
||||
kbDir: "/cwd/.kb",
|
||||
})),
|
||||
createBackupManager: vi.fn(() => ({
|
||||
listBackups: mockListBackups,
|
||||
restoreBackup: mockRestoreBackup,
|
||||
cleanupOldBackups: mockCleanupOldBackups,
|
||||
})),
|
||||
runBackupCommand: mockRunBackupCommand,
|
||||
}));
|
||||
|
||||
vi.mock("../project-context.js", () => ({
|
||||
resolveProject: mockResolveProject,
|
||||
}));
|
||||
|
||||
import { TaskStore } from "@fusion/core";
|
||||
import { runBackupCreate, runBackupList, runBackupRestore, runBackupCleanup } from "./backup.js";
|
||||
|
||||
describe("backup 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}`);
|
||||
});
|
||||
mockGetSettings.mockResolvedValue({ autoBackupDir: ".kb/backups" });
|
||||
mockRunBackupCommand.mockResolvedValue({ success: true, output: "backup created" });
|
||||
mockListBackups.mockResolvedValue([]);
|
||||
mockRestoreBackup.mockResolvedValue(undefined);
|
||||
mockCleanupOldBackups.mockResolvedValue(0);
|
||||
mockResolveProject.mockResolvedValue({
|
||||
projectId: "proj-1",
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { getSettings: mockGetSettings, kbDir: "/projects/demo/.kb" },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
logSpy.mockRestore();
|
||||
errorSpy.mockRestore();
|
||||
exitSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("runBackupCreate uses resolved project store with --project", async () => {
|
||||
await expect(runBackupCreate("demo-project")).rejects.toThrow("process.exit:0");
|
||||
expect(mockResolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(mockRunBackupCommand).toHaveBeenCalledWith("/projects/demo/.kb", expect.anything());
|
||||
});
|
||||
|
||||
it("runBackupList uses resolved project store with --project", async () => {
|
||||
mockListBackups.mockResolvedValue([{ filename: "kb.db.bak", size: 1024, createdAt: new Date().toISOString() }]);
|
||||
await runBackupList("demo-project");
|
||||
expect(mockResolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("Found 1 backup"));
|
||||
});
|
||||
|
||||
it("runBackupRestore uses resolved project store with --project", async () => {
|
||||
await runBackupRestore("kb.db.bak", "demo-project");
|
||||
expect(mockResolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(mockRestoreBackup).toHaveBeenCalledWith("kb.db.bak", { createPreRestoreBackup: true });
|
||||
});
|
||||
|
||||
it("runBackupCleanup uses resolved project store with --project", async () => {
|
||||
mockCleanupOldBackups.mockResolvedValue(2);
|
||||
await runBackupCleanup("demo-project");
|
||||
expect(mockResolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(logSpy).toHaveBeenCalledWith("Removed 2 old backup(s).");
|
||||
});
|
||||
|
||||
it("runBackupList without project uses shared resolution flow", async () => {
|
||||
await runBackupList();
|
||||
expect(mockResolveProject).toHaveBeenCalledWith(undefined);
|
||||
expect(TaskStore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runBackupList without project falls back to current cwd task store when resolution fails", async () => {
|
||||
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue("/local/project");
|
||||
mockResolveProject.mockRejectedValueOnce(new Error("No kb project found"));
|
||||
await runBackupList();
|
||||
expect(mockResolveProject).toHaveBeenCalledWith(undefined);
|
||||
expect(TaskStore).toHaveBeenCalledWith("/local/project");
|
||||
cwdSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("propagates project resolution errors for project-targeted backup commands", async () => {
|
||||
mockResolveProject.mockRejectedValue(new Error("Project 'missing' not found. Run 'kb project list' to see registered projects."));
|
||||
|
||||
await expect(runBackupList("missing")).rejects.toThrow("Project 'missing' not found");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user