feat(FN-3935): align engineer role routing with explicit assignment and del

Centralized routing policy helpers in core and aligned both direct assignment and delegation to route engineer-role agents consistently, ensuring assigned tasks respect explicit engineer routing the same way delegated tasks do. Added comprehensive test coverage across core, CLI, dashboard, and engin

Fusion-Task-Id: FN-3935

Fusion-Task-Lineage: 069f4d54-f7a8-4bd3-a2c3-4de830de042f
This commit is contained in:
Fusion
2026-05-11 20:20:43 -07:00
committed by gsxdsm
parent f76867301c
commit e4ec922212
18 changed files with 342 additions and 29 deletions

View File

@@ -0,0 +1,114 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// Hoist mocks so they are evaluated before module imports
const { mockGetDatabase, mockVacuum, mockResolveProject } = vi.hoisted(() => ({
mockGetDatabase: vi.fn(),
mockVacuum: vi.fn(),
mockResolveProject: vi.fn(),
}));
vi.mock("@fusion/core", () => ({
TaskStore: vi.fn().mockImplementation(() => ({
init: vi.fn(),
getDatabase: mockGetDatabase,
})),
}));
vi.mock("../../project-context.js", () => ({
resolveProject: mockResolveProject,
}));
import { runDbVacuum } from "../db.ts";
describe("runDbVacuum", () => {
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("resolves project store and calls vacuum", async () => {
mockResolveProject.mockResolvedValue({
projectId: "proj-1",
projectName: "demo-project",
projectPath: "/projects/demo",
isRegistered: true,
store: { getDatabase: mockGetDatabase },
});
mockGetDatabase.mockReturnValue({
vacuum: mockVacuum.mockReturnValue({
beforeSize: 10_485_760,
afterSize: 7_340_416,
durationMs: 123,
}),
getPath: () => "/projects/demo/.fusion/fusion.db",
});
await expect(runDbVacuum("demo-project")).rejects.toThrow("process.exit:0");
expect(mockResolveProject).toHaveBeenCalledWith("demo-project");
expect(mockVacuum).toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("VACUUM"));
});
it("exits 1 on vacuum error", async () => {
mockResolveProject.mockResolvedValue({
projectId: "proj-1",
projectName: "demo-project",
projectPath: "/projects/demo",
isRegistered: true,
store: { getDatabase: mockGetDatabase },
});
mockGetDatabase.mockReturnValue({
vacuum: mockVacuum.mockRejectedValue(new Error("database locked")),
getPath: () => "/projects/demo/.fusion/fusion.db",
});
await expect(runDbVacuum("demo-project")).rejects.toThrow("process.exit:1");
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("database locked"));
});
it("falls back to cwd TaskStore when resolveProject fails", async () => {
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue("/fallback/project");
mockResolveProject.mockRejectedValue(new Error("no project"));
const mockStore = { init: vi.fn(), getDatabase: mockGetDatabase };
mockGetDatabase.mockReturnValue({
vacuum: mockVacuum.mockReturnValue({ beforeSize: 0, afterSize: 0, durationMs: 0 }),
getPath: () => "/fallback/project/.fusion/fusion.db",
});
await expect(runDbVacuum("missing")).rejects.toThrow("process.exit:0");
expect(mockResolveProject).toHaveBeenCalledWith("missing");
cwdSpy.mockRestore();
});
it("skips vacuum on in-memory database (returns zero sizes)", async () => {
mockResolveProject.mockResolvedValue({
projectId: "proj-1",
projectName: "mem-project",
projectPath: "/mem",
isRegistered: true,
store: { getDatabase: mockGetDatabase },
});
mockGetDatabase.mockReturnValue({
vacuum: mockVacuum.mockReturnValue({ beforeSize: 0, afterSize: 0, durationMs: 0 }),
getPath: () => ":memory:",
});
await expect(runDbVacuum("mem-project")).rejects.toThrow("process.exit:0");
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("in-memory"));
});
});