Files
fusion/packages/dashboard/src/__tests__/update-check-route.test.ts
Fusion 7806724cf2 feat(FN-2664): add dashboard updates check endpoint and settings flow
- Add GET /api/updates/check endpoint in dashboard server with coverage for success, failure, and disabled update scenarios
- Extend legacy dashboard API client with check-for-updates request support
- Add Settings modal update-check UI and styling for trigger, loading, and result states
- Expand Settings modal tests and document the updates check endpoint in architecture docs
2026-04-27 02:19:44 -07:00

133 lines
4.3 KiB
TypeScript

// @vitest-environment node
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, it, expect, vi, afterEach } from "vitest";
import type { TaskStore } from "@fusion/core";
import { createServer } from "../server.js";
import { get as performGet } from "../test-request.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const CLI_PACKAGE_VERSION = (() => {
const packageJsonPath = join(__dirname, "..", "..", "..", "cli", "package.json");
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
version?: unknown;
};
return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
})();
function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
return {
getTask: vi.fn(),
listTasks: vi.fn().mockResolvedValue([]),
createTask: vi.fn(),
moveTask: vi.fn(),
updateTask: vi.fn(),
deleteTask: vi.fn(),
mergeTask: vi.fn(),
archiveTask: vi.fn(),
unarchiveTask: vi.fn(),
getSettings: vi.fn().mockResolvedValue({}),
updateSettings: vi.fn(),
logEntry: vi.fn().mockResolvedValue(undefined),
getAgentLogs: vi.fn().mockResolvedValue([]),
addSteeringComment: vi.fn(),
updatePrInfo: vi.fn().mockResolvedValue(undefined),
updateIssueInfo: vi.fn().mockResolvedValue(undefined),
getRootDir: vi.fn().mockReturnValue("/fake/root"),
getFusionDir: vi.fn().mockReturnValue("/fake/root/.fusion"),
getDatabase: vi.fn().mockReturnValue({
exec: vi.fn(),
prepare: vi.fn().mockReturnValue({ run: vi.fn().mockReturnValue({ changes: 0 }), get: vi.fn(), all: vi.fn().mockReturnValue([]) }),
}),
getMissionStore: vi.fn().mockReturnValue({
listMissions: vi.fn().mockReturnValue([]),
createMission: vi.fn(),
getMissionWithHierarchy: vi.fn(),
updateMission: vi.fn(),
getMission: vi.fn(),
deleteMission: vi.fn(),
listMilestonesByMission: vi.fn().mockReturnValue([]),
createMilestone: vi.fn(),
updateMilestone: vi.fn(),
getMilestone: vi.fn(),
deleteMilestone: vi.fn(),
listTasksByMilestone: vi.fn().mockReturnValue([]),
createMissionTask: vi.fn(),
updateMissionTask: vi.fn(),
getMissionTask: vi.fn(),
deleteMissionTask: vi.fn(),
}),
on: vi.fn(),
off: vi.fn(),
...overrides,
} as unknown as TaskStore;
}
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
describe("GET /api/updates/check", () => {
it("returns updateAvailable=true when npm has a newer version", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ version: "99.0.0" }),
}),
);
const app = createServer(createMockStore());
const response = await performGet(app, "/api/updates/check");
expect(response.status).toBe(200);
expect(response.headers["cache-control"]).toBe("no-store");
expect(response.body).toEqual({
currentVersion: CLI_PACKAGE_VERSION,
latestVersion: "99.0.0",
updateAvailable: true,
});
});
it("returns updateAvailable=false when already up to date", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ version: CLI_PACKAGE_VERSION }),
}),
);
const app = createServer(createMockStore());
const response = await performGet(app, "/api/updates/check");
expect(response.status).toBe(200);
expect(response.headers["cache-control"]).toBe("no-store");
expect(response.body).toEqual({
currentVersion: CLI_PACKAGE_VERSION,
latestVersion: CLI_PACKAGE_VERSION,
updateAvailable: false,
});
});
it("gracefully returns an error payload when npm registry is unreachable", async () => {
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("network down")));
const app = createServer(createMockStore());
const response = await performGet(app, "/api/updates/check");
expect(response.status).toBe(200);
expect(response.headers["cache-control"]).toBe("no-store");
expect(response.body).toEqual({
currentVersion: CLI_PACKAGE_VERSION,
latestVersion: null,
updateAvailable: false,
error: "Failed to check for updates",
});
});
});