Merges FN-3110 to add Docker provisioning support across the stack: TypeScript types and DockerProvisioningService in core, API routes in dashboard for provisioning operations, a useDockerProvisioning hook for frontend integration, and a DockerProvisioningStatus UI component — all covered by unit an Fusion-Task-Id: FN-3110
198 lines
5.8 KiB
TypeScript
198 lines
5.8 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
import { renderHook, act } from "@testing-library/react";
|
|
|
|
// Must be called before importing the hook
|
|
const fetchMock = vi.fn();
|
|
global.fetch = fetchMock;
|
|
|
|
// Import the hook after mock setup
|
|
import { useDockerProvisioning } from "../useDockerProvisioning";
|
|
|
|
function jsonOk(body: unknown) {
|
|
return Promise.resolve({
|
|
ok: true,
|
|
status: 200,
|
|
json: () => Promise.resolve(body),
|
|
});
|
|
}
|
|
|
|
function jsonError(status: number, body: unknown) {
|
|
return Promise.resolve({
|
|
ok: false,
|
|
status,
|
|
json: () => Promise.resolve(body),
|
|
});
|
|
}
|
|
|
|
describe("useDockerProvisioning", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
fetchMock.mockReset();
|
|
});
|
|
|
|
describe("provision", () => {
|
|
it("posts to /api/docker/provision and returns result", async () => {
|
|
const result = {
|
|
success: true,
|
|
containerId: "abc",
|
|
containerName: "fusion-test-abc12345",
|
|
apiKey: "fn_key",
|
|
portMapping: "4040:49152",
|
|
durationMs: 1000,
|
|
};
|
|
fetchMock.mockReturnValue(jsonOk(result));
|
|
|
|
const { result: hookResult } = renderHook(() => useDockerProvisioning());
|
|
|
|
let provisionResult;
|
|
await act(async () => {
|
|
provisionResult = await hookResult.current.provision({
|
|
nodeName: "test",
|
|
hostConfig: {},
|
|
imageConfig: { image: "runfusion/fusion", tag: "latest", pullImage: true },
|
|
autoGenerateApiKey: true,
|
|
});
|
|
});
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"/api/docker/provision",
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
expect(provisionResult!.success).toBe(true);
|
|
expect(hookResult.current.provisionResult).toEqual(result);
|
|
expect(hookResult.current.isProvisioning).toBe(false);
|
|
});
|
|
|
|
it("sets provisionError on non-ok response", async () => {
|
|
fetchMock.mockReturnValue(
|
|
jsonError(500, { success: false, error: "internal error" }),
|
|
);
|
|
|
|
const { result: hookResult } = renderHook(() => useDockerProvisioning());
|
|
|
|
await act(async () => {
|
|
await hookResult.current.provision({
|
|
nodeName: "test",
|
|
hostConfig: {},
|
|
imageConfig: { image: "runfusion/fusion", tag: "latest", pullImage: true },
|
|
autoGenerateApiKey: true,
|
|
});
|
|
});
|
|
|
|
expect(hookResult.current.provisionError).toBe("internal error");
|
|
});
|
|
|
|
it("sets provisionError on fetch rejection", async () => {
|
|
fetchMock.mockRejectedValue(new Error("network error"));
|
|
|
|
const { result: hookResult } = renderHook(() => useDockerProvisioning());
|
|
|
|
await act(async () => {
|
|
await hookResult.current.provision({
|
|
nodeName: "test",
|
|
hostConfig: {},
|
|
imageConfig: { image: "runfusion/fusion", tag: "latest", pullImage: true },
|
|
autoGenerateApiKey: true,
|
|
});
|
|
});
|
|
|
|
expect(hookResult.current.provisionError).toBe("network error");
|
|
});
|
|
});
|
|
|
|
describe("deprovision", () => {
|
|
it("posts to /api/docker/deprovision with correct body", async () => {
|
|
fetchMock.mockReturnValue(jsonOk({ success: true }));
|
|
|
|
const { result: hookResult } = renderHook(() => useDockerProvisioning());
|
|
|
|
await act(async () => {
|
|
await hookResult.current.deprovision("abc123", { host: "tcp://1.2.3.4:2376" }, true);
|
|
});
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"/api/docker/deprovision",
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
containerId: "abc123",
|
|
hostConfig: { host: "tcp://1.2.3.4:2376" },
|
|
removeVolumes: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(hookResult.current.isDeprovisioning).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("startContainer", () => {
|
|
it("posts to the correct URL", async () => {
|
|
fetchMock.mockReturnValue(jsonOk({ success: true }));
|
|
|
|
const { result: hookResult } = renderHook(() => useDockerProvisioning());
|
|
|
|
await act(async () => {
|
|
await hookResult.current.startContainer("abc123", {});
|
|
});
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"/api/docker/containers/abc123/start",
|
|
expect.objectContaining({ method: "POST" }),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("stopContainer", () => {
|
|
it("posts to the correct URL", async () => {
|
|
fetchMock.mockReturnValue(jsonOk({ success: true }));
|
|
|
|
const { result: hookResult } = renderHook(() => useDockerProvisioning());
|
|
|
|
await act(async () => {
|
|
await hookResult.current.stopContainer("abc123", {});
|
|
});
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"/api/docker/containers/abc123/stop",
|
|
expect.objectContaining({ method: "POST" }),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("restartContainer", () => {
|
|
it("posts to the correct URL", async () => {
|
|
fetchMock.mockReturnValue(jsonOk({ success: true }));
|
|
|
|
const { result: hookResult } = renderHook(() => useDockerProvisioning());
|
|
|
|
await act(async () => {
|
|
await hookResult.current.restartContainer("abc123", {});
|
|
});
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"/api/docker/containers/abc123/restart",
|
|
expect.objectContaining({ method: "POST" }),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("getDefaultImage", () => {
|
|
it("fetches from /api/docker/default-image", async () => {
|
|
fetchMock.mockReturnValue(jsonOk({ image: "runfusion/fusion", tag: "latest" }));
|
|
|
|
const { result: hookResult } = renderHook(() => useDockerProvisioning());
|
|
|
|
let result;
|
|
await act(async () => {
|
|
result = await hookResult.current.getDefaultImage();
|
|
});
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith("/api/docker/default-image");
|
|
expect(result).toEqual({ image: "runfusion/fusion", tag: "latest" });
|
|
});
|
|
});
|
|
});
|