Files
languard-servers-manager/frontend/src/__tests__/useGames.test.tsx
Tran G. (Revernomad) Khoa 6511353b55 feat: implement full backend + frontend server detail, settings, and create server pages
Backend:
- Complete FastAPI backend with 42+ REST endpoints (auth, servers, config,
  players, bans, missions, mods, games, system)
- Game adapter architecture with Arma 3 as first-class adapter
- WebSocket real-time events for status, metrics, logs, players
- Background thread system (process monitor, metrics, log tail, RCon poller)
- Fernet encryption for sensitive config fields at rest
- JWT auth with admin/viewer roles, bcrypt password hashing
- SQLite with WAL mode, parameterized queries, migration system
- APScheduler cleanup jobs for logs, metrics, events

Frontend:
- Server Detail page with 7 tabs (overview, config, players, bans,
  missions, mods, logs)
- Settings page with password change and admin user management
- Create Server wizard (4-step; known bug: silent validation failure)
- New hooks: useServerDetail, useAuth, useGames
- New components: ServerHeader, ConfigEditor, PlayerTable, BanTable,
  MissionList, ModList, LogViewer, PasswordChange, UserManager
- WebSocket onEvent callback for real-time log accumulation
- 120 unit tests passing (Vitest + React Testing Library)

Docs:
- Added .gitignore, CLAUDE.md, README.md
- Updated FRONTEND.md, ARCHITECTURE.md with current implementation state
- Added .env.example for backend configuration

Known issues:
- Create Server form: "Next" buttons don't validate before advancing,
  causing silent submit failure when fields are invalid
- Config sub-tabs need UX redesign for non-technical users
2026-04-17 11:58:34 +07:00

137 lines
4.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { ReactNode } from "react";
import {
useGamesList,
useGameDetail,
useGameConfigSchema,
useGameDefaults,
} from "@/hooks/useGames";
vi.mock("@/lib/api", () => ({
apiClient: {
get: vi.fn(),
},
}));
import { apiClient } from "@/lib/api";
function createWrapper() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return function Wrapper({ children }: { children: ReactNode }) {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
}
describe("useGamesList", () => {
beforeEach(() => {
vi.mocked(apiClient.get).mockReset();
});
it("should fetch games list", async () => {
const mockGames = [
{ game_type: "arma3", display_name: "Arma 3", version: "1.0", capabilities: ["rcon", "mods"] },
];
vi.mocked(apiClient.get).mockResolvedValueOnce({
data: { success: true, data: mockGames },
});
const { result } = renderHook(() => useGamesList(), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockGames);
expect(apiClient.get).toHaveBeenCalledWith("/api/games");
});
});
describe("useGameDetail", () => {
beforeEach(() => {
vi.mocked(apiClient.get).mockReset();
});
it("should fetch game detail", async () => {
const mockDetail = {
game_type: "arma3",
display_name: "Arma 3",
version: "1.0",
schema_version: 1,
config_sections: ["server", "basic", "profile", "launch", "rcon"],
capabilities: ["rcon", "mods", "missions"],
allowed_executables: ["arma3server_x64.exe"],
};
vi.mocked(apiClient.get).mockResolvedValueOnce({
data: { success: true, data: mockDetail },
});
const { result } = renderHook(() => useGameDetail("arma3"), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockDetail);
expect(apiClient.get).toHaveBeenCalledWith("/api/games/arma3");
});
it("should not fetch when gameType is empty", () => {
renderHook(() => useGameDetail(""), { wrapper: createWrapper() });
expect(apiClient.get).not.toHaveBeenCalled();
});
});
describe("useGameConfigSchema", () => {
beforeEach(() => {
vi.mocked(apiClient.get).mockReset();
});
it("should fetch config schema", async () => {
const mockSchema = { server: { type: "object", properties: {} } };
vi.mocked(apiClient.get).mockResolvedValueOnce({
data: { success: true, data: mockSchema },
});
const { result } = renderHook(() => useGameConfigSchema("arma3"), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockSchema);
expect(apiClient.get).toHaveBeenCalledWith("/api/games/arma3/config-schema");
});
});
describe("useGameDefaults", () => {
beforeEach(() => {
vi.mocked(apiClient.get).mockReset();
});
it("should fetch game defaults", async () => {
const mockDefaults = {
server: { hostname: "Arma 3 Server", max_players: 64 },
basic: { min_bandwidth: 131072 },
};
vi.mocked(apiClient.get).mockResolvedValueOnce({
data: { success: true, data: mockDefaults },
});
const { result } = renderHook(() => useGameDefaults("arma3"), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockDefaults);
expect(apiClient.get).toHaveBeenCalledWith("/api/games/arma3/defaults");
});
it("should not fetch when gameType is empty", () => {
renderHook(() => useGameDefaults(""), { wrapper: createWrapper() });
expect(apiClient.get).not.toHaveBeenCalled();
});
});