Files
BanGUI/frontend/src/hooks/__tests__/useJailDetail.test.ts
Lukas 7627ae7edb Add jail control actions to useJailDetail hook
Implement TASK F-2: Wrap JailDetailPage jail-control API calls in a hook.

Changes:
- Add start(), stop(), reload(), and setIdle() methods to useJailDetail hook
- Update JailDetailPage to use hook control methods instead of direct API imports
- Update error handling to remove dependency on ApiError type
- Add comprehensive tests for new control methods (8 tests)
- Update existing test to include new hook methods in mock

The control methods handle refetching jail data after each operation,
consistent with the pattern used in useJails hook.
2026-03-20 13:58:01 +01:00

208 lines
5.7 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import * as jailsApi from "../../api/jails";
import { useJailDetail } from "../useJails";
import type { Jail } from "../../types/jail";
// Mock the API module
vi.mock("../../api/jails");
const mockJail: Jail = {
name: "sshd",
running: true,
idle: false,
backend: "pyinotify",
log_paths: ["/var/log/auth.log"],
fail_regex: ["^\\[.*\\]\\s.*Failed password"],
ignore_regex: [],
date_pattern: "%b %d %H:%M:%S",
log_encoding: "UTF-8",
actions: [],
find_time: 600,
ban_time: 600,
max_retry: 5,
status: null,
bantime_escalation: null,
};
describe("useJailDetail control methods", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(jailsApi.fetchJail).mockResolvedValue({
jail: mockJail,
ignore_list: [],
ignore_self: false,
});
});
it("calls start() and refetches jail data", async () => {
vi.mocked(jailsApi.startJail).mockResolvedValue(undefined);
const { result } = renderHook(() => useJailDetail("sshd"));
// Wait for initial fetch
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
expect(result.current.jail?.name).toBe("sshd");
expect(jailsApi.startJail).not.toHaveBeenCalled();
// Call start()
await act(async () => {
await result.current.start();
});
expect(jailsApi.startJail).toHaveBeenCalledWith("sshd");
expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2); // Initial fetch + refetch after start
});
it("calls stop() and refetches jail data", async () => {
vi.mocked(jailsApi.stopJail).mockResolvedValue(undefined);
const { result } = renderHook(() => useJailDetail("sshd"));
// Wait for initial fetch
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
// Call stop()
await act(async () => {
await result.current.stop();
});
expect(jailsApi.stopJail).toHaveBeenCalledWith("sshd");
expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2); // Initial fetch + refetch after stop
});
it("calls reload() and refetches jail data", async () => {
vi.mocked(jailsApi.reloadJail).mockResolvedValue(undefined);
const { result } = renderHook(() => useJailDetail("sshd"));
// Wait for initial fetch
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
// Call reload()
await act(async () => {
await result.current.reload();
});
expect(jailsApi.reloadJail).toHaveBeenCalledWith("sshd");
expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2); // Initial fetch + refetch after reload
});
it("calls setIdle() with correct parameter and refetches jail data", async () => {
vi.mocked(jailsApi.setJailIdle).mockResolvedValue(undefined);
const { result } = renderHook(() => useJailDetail("sshd"));
// Wait for initial fetch
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
// Call setIdle(true)
await act(async () => {
await result.current.setIdle(true);
});
expect(jailsApi.setJailIdle).toHaveBeenCalledWith("sshd", true);
expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2);
// Reset mock to verify second call
vi.mocked(jailsApi.setJailIdle).mockClear();
vi.mocked(jailsApi.fetchJail).mockResolvedValue({
jail: { ...mockJail, idle: true },
ignore_list: [],
ignore_self: false,
});
// Call setIdle(false)
await act(async () => {
await result.current.setIdle(false);
});
expect(jailsApi.setJailIdle).toHaveBeenCalledWith("sshd", false);
});
it("propagates errors from start()", async () => {
const error = new Error("Failed to start jail");
vi.mocked(jailsApi.startJail).mockRejectedValue(error);
const { result } = renderHook(() => useJailDetail("sshd"));
// Wait for initial fetch
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
// Call start() and expect it to throw
await expect(
act(async () => {
await result.current.start();
}),
).rejects.toThrow("Failed to start jail");
});
it("propagates errors from stop()", async () => {
const error = new Error("Failed to stop jail");
vi.mocked(jailsApi.stopJail).mockRejectedValue(error);
const { result } = renderHook(() => useJailDetail("sshd"));
// Wait for initial fetch
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
// Call stop() and expect it to throw
await expect(
act(async () => {
await result.current.stop();
}),
).rejects.toThrow("Failed to stop jail");
});
it("propagates errors from reload()", async () => {
const error = new Error("Failed to reload jail");
vi.mocked(jailsApi.reloadJail).mockRejectedValue(error);
const { result } = renderHook(() => useJailDetail("sshd"));
// Wait for initial fetch
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
// Call reload() and expect it to throw
await expect(
act(async () => {
await result.current.reload();
}),
).rejects.toThrow("Failed to reload jail");
});
it("propagates errors from setIdle()", async () => {
const error = new Error("Failed to set idle mode");
vi.mocked(jailsApi.setJailIdle).mockRejectedValue(error);
const { result } = renderHook(() => useJailDetail("sshd"));
// Wait for initial fetch
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
// Call setIdle() and expect it to throw
await expect(
act(async () => {
await result.current.setIdle(true);
}),
).rejects.toThrow("Failed to set idle mode");
});
});