- 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
133 lines
4.3 KiB
TypeScript
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",
|
|
});
|
|
});
|
|
});
|