Refactor jail detail hooks: split into useJailData and useJailCommands

- Split monolithic useJailDetail hook into separate concerns
- Created useJailData for fetching and managing jail data
- Created useJailCommands for jail operations (power, console, etc.)
- Updated JailDetailPage to use new hooks
- Updated tests to reflect new hook structure
- Removed old useJailDetail hook

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-25 19:14:16 +02:00
parent 8d30a81346
commit 8bd5713d38
7 changed files with 299 additions and 279 deletions

View File

@@ -8,7 +8,8 @@
import { Button, MessageBar, MessageBarBody, Spinner, Text } from "@fluentui/react-components";
import { ArrowLeftRegular } from "@fluentui/react-icons";
import { Link, useParams } from "react-router-dom";
import { useJailDetail } from "../hooks/useJailDetail";
import { useJailData } from "../hooks/useJailData";
import { useJailCommands } from "../hooks/useJailCommands";
import { useJailBannedIps } from "../hooks/useJailBannedIps";
import { BannedIpsSection } from "../components/jail/BannedIpsSection";
import { JailInfoSection } from "./jail/JailInfoSection";
@@ -20,8 +21,8 @@ import { useJailDetailPageStyles } from "./jail/jailDetailPageStyles";
export function JailDetailPage(): React.JSX.Element {
const styles = useJailDetailPageStyles();
const { name = "" } = useParams<{ name: string }>();
const { jail, ignoreList, ignoreSelf, loading, error, refresh, addIp, removeIp, toggleIgnoreSelf, start, stop, reload, setIdle } =
useJailDetail(name);
const { jail, ignoreList, ignoreSelf, loading, error, refresh } = useJailData(name);
const { addIp, removeIp, toggleIgnoreSelf, start, stop, reload, setIdle } = useJailCommands(name, refresh);
const {
items,
total,

View File

@@ -15,7 +15,8 @@ import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { JailDetailPage } from "../JailDetailPage";
import type { Jail } from "../../types/jail";
import type { UseJailDetailResult } from "../../hooks/useJailDetail";
import type { UseJailDataResult } from "../../hooks/useJailData";
import type { UseJailCommandsResult } from "../../hooks/useJailCommands";
// ---------------------------------------------------------------------------
// Module mocks
@@ -38,9 +39,14 @@ const {
mockRefresh: vi.fn(),
}));
// Mock the jail detail hook — tests control the returned state directly.
vi.mock("../../hooks/useJailDetail", () => ({
useJailDetail: vi.fn(),
// Mock the jail data hook — tests control the returned state directly.
vi.mock("../../hooks/useJailData", () => ({
useJailData: vi.fn(),
}));
// Mock the jail commands hook — tests control the returned functions directly.
vi.mock("../../hooks/useJailCommands", () => ({
useJailCommands: vi.fn(),
}));
vi.mock("../../hooks/useJailBannedIps", () => ({
@@ -79,7 +85,8 @@ vi.mock("../../components/jail/BannedIpsSection", () => ({
// Helpers
// ---------------------------------------------------------------------------
import { useJailDetail } from "../../hooks/useJailDetail";
import { useJailData } from "../../hooks/useJailData";
import { useJailCommands } from "../../hooks/useJailCommands";
/** Minimal `Jail` fixture. */
function makeJail(): Jail {
@@ -107,15 +114,19 @@ function makeJail(): Jail {
};
}
/** Wire `useJailDetail` to return the given `ignoreSelf` value. */
function mockHook(ignoreSelf: boolean): void {
const result: UseJailDetailResult = {
/** Wire `useJailData` and `useJailCommands` to return the given `ignoreSelf` value. */
function mockHooks(ignoreSelf: boolean): void {
const dataResult: UseJailDataResult = {
jail: makeJail(),
ignoreList: ["10.0.0.0/8"],
ignoreSelf,
loading: false,
error: null,
refresh: mockRefresh,
};
vi.mocked(useJailData).mockReturnValue(dataResult);
const commandsResult: UseJailCommandsResult = {
addIp: mockAddIp,
removeIp: mockRemoveIp,
toggleIgnoreSelf: mockToggleIgnoreSelf,
@@ -124,7 +135,7 @@ function mockHook(ignoreSelf: boolean): void {
reload: vi.fn().mockResolvedValue(undefined),
setIdle: vi.fn().mockResolvedValue(undefined),
};
vi.mocked(useJailDetail).mockReturnValue(result);
vi.mocked(useJailCommands).mockReturnValue(commandsResult);
}
/** Render the JailDetailPage with a fake `/jails/sshd` route. */
@@ -151,7 +162,7 @@ describe("JailDetailPage — ignore self toggle", () => {
});
it("renders the switch checked when ignoreSelf is true", async () => {
mockHook(true);
mockHooks(true);
renderPage();
const switchEl = await screen.findByRole("switch", { name: /ignore self/i });
@@ -159,7 +170,7 @@ describe("JailDetailPage — ignore self toggle", () => {
});
it("renders the switch unchecked when ignoreSelf is false", async () => {
mockHook(false);
mockHooks(false);
renderPage();
const switchEl = await screen.findByRole("switch", { name: /ignore self/i });
@@ -167,7 +178,7 @@ describe("JailDetailPage — ignore self toggle", () => {
});
it("calls toggleIgnoreSelf(false) when switch is toggled off", async () => {
mockHook(true);
mockHooks(true);
renderPage();
const user = userEvent.setup();
@@ -181,7 +192,7 @@ describe("JailDetailPage — ignore self toggle", () => {
});
it("calls toggleIgnoreSelf(true) when switch is toggled on", async () => {
mockHook(false);
mockHooks(false);
renderPage();
const user = userEvent.setup();
@@ -195,7 +206,7 @@ describe("JailDetailPage — ignore self toggle", () => {
});
it("shows an error message bar when toggleIgnoreSelf rejects", async () => {
mockHook(false);
mockHooks(false);
mockToggleIgnoreSelf.mockRejectedValue(new Error("Connection refused"));
renderPage();
const user = userEvent.setup();