Add tests and documentation updates for log preview and regex tester hooks

- Add useLogPreview.test.ts with comprehensive test coverage
- Add useRegexTester.test.ts with comprehensive test coverage
- Update Docs/Tasks.md and Docs/Web-Development.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-23 09:14:58 +02:00
parent 3fba69970c
commit 1bcc336c9b
6 changed files with 242 additions and 56 deletions

View File

@@ -0,0 +1,126 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { ApiError } from "../../api/client";
import { useLogPreview } from "../useLogPreview";
vi.mock("../../api/config", () => ({
previewLog: vi.fn(),
}));
import { previewLog } from "../../api/config";
describe("useLogPreview", () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
});
it("returns initial state with preview and loading false", () => {
const { result } = renderHook(() => useLogPreview());
expect(result.current.preview).toBeNull();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeNull();
});
it("sets preview on successful run", async () => {
const mockResponse = {
lines: [{ line: "test", matched: true, groups: [] }],
total_lines: 1,
matched_count: 1,
regex_error: null,
};
vi.mocked(previewLog).mockResolvedValue(mockResponse);
const { result } = renderHook(() => useLogPreview());
await act(async () => {
await result.current.run({
log_path: "/var/log/test.log",
fail_regex: "pattern",
num_lines: 100,
});
});
expect(result.current.preview).toEqual(mockResponse);
expect(result.current.error).toBeNull();
expect(result.current.loading).toBe(false);
});
it("sets error for normal errors via handleFetchError", async () => {
vi.mocked(previewLog).mockRejectedValue(new Error("Network error"));
const { result } = renderHook(() => useLogPreview());
await act(async () => {
await result.current.run({
log_path: "/var/log/test.log",
fail_regex: "pattern",
num_lines: 100,
});
});
expect(result.current.error).toBe("Network error");
expect(result.current.loading).toBe(false);
});
it("ignores auth errors (401) via handleFetchError", async () => {
vi.mocked(previewLog).mockRejectedValue(new ApiError(401, "Unauthorized"));
const { result } = renderHook(() => useLogPreview());
await act(async () => {
await result.current.run({
log_path: "/var/log/test.log",
fail_regex: "pattern",
num_lines: 100,
});
});
expect(result.current.error).toBeNull();
expect(result.current.loading).toBe(false);
});
it("ignores auth errors (403) via handleFetchError", async () => {
vi.mocked(previewLog).mockRejectedValue(new ApiError(403, "Forbidden"));
const { result } = renderHook(() => useLogPreview());
await act(async () => {
await result.current.run({
log_path: "/var/log/test.log",
fail_regex: "pattern",
num_lines: 100,
});
});
expect(result.current.error).toBeNull();
expect(result.current.loading).toBe(false);
});
it("sets loading to false in finally block", async () => {
vi.mocked(previewLog).mockResolvedValue({
lines: [],
total_lines: 0,
matched_count: 0,
regex_error: null,
});
const { result } = renderHook(() => useLogPreview());
expect(result.current.loading).toBe(false);
await act(async () => {
await result.current.run({
log_path: "/var/log/test.log",
fail_regex: "pattern",
num_lines: 100,
});
});
expect(result.current.loading).toBe(false);
});
});

View File

@@ -0,0 +1,103 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { ApiError } from "../../api/client";
import { useRegexTester } from "../useRegexTester";
vi.mock("../../api/config", () => ({
testRegex: vi.fn(),
}));
import { testRegex } from "../../api/config";
describe("useRegexTester", () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
});
it("returns initial state with result and testing false", () => {
const { result } = renderHook(() => useRegexTester());
expect(result.current.result).toBeNull();
expect(result.current.testing).toBe(false);
expect(result.current.error).toBeNull();
});
it("sets result on successful test", async () => {
const mockResponse = { matched: true, groups: ["host"], error: null };
vi.mocked(testRegex).mockResolvedValue(mockResponse);
const { result } = renderHook(() => useRegexTester());
await act(async () => {
await result.current.test({ log_line: "test", fail_regex: "pattern" });
});
expect(result.current.result).toEqual(mockResponse);
expect(result.current.error).toBeNull();
expect(result.current.testing).toBe(false);
});
it("sets error for normal errors via handleFetchError", async () => {
vi.mocked(testRegex).mockRejectedValue(new Error("Network error"));
const { result } = renderHook(() => useRegexTester());
await act(async () => {
await result.current.test({ log_line: "test", fail_regex: "pattern" });
});
expect(result.current.error).toBe("Network error");
expect(result.current.testing).toBe(false);
});
it("ignores auth errors (401) via handleFetchError", async () => {
vi.mocked(testRegex).mockRejectedValue(new ApiError(401, "Unauthorized"));
const { result } = renderHook(() => useRegexTester());
await act(async () => {
await result.current.test({ log_line: "test", fail_regex: "pattern" });
});
expect(result.current.error).toBeNull();
expect(result.current.testing).toBe(false);
});
it("ignores auth errors (403) via handleFetchError", async () => {
vi.mocked(testRegex).mockRejectedValue(new ApiError(403, "Forbidden"));
const { result } = renderHook(() => useRegexTester());
await act(async () => {
await result.current.test({ log_line: "test", fail_regex: "pattern" });
});
expect(result.current.error).toBeNull();
expect(result.current.testing).toBe(false);
});
it("sets testing to false in finally block", async () => {
vi.mocked(testRegex).mockResolvedValue({
matched: false,
groups: [],
error: null,
});
const { result } = renderHook(() => useRegexTester());
expect(result.current.testing).toBe(false);
await act(async () => {
await result.current.test({
log_line: "test",
fail_regex: "pattern",
});
});
expect(result.current.testing).toBe(false);
});
});

View File

@@ -4,11 +4,13 @@
import { useCallback, useState } from "react";
import { previewLog } from "../api/config";
import { handleFetchError } from "../utils/fetchError";
import type { LogPreviewRequest, LogPreviewResponse } from "../types/config";
export interface UseLogPreviewResult {
preview: LogPreviewResponse | null;
loading: boolean;
error: string | null;
run: (req: LogPreviewRequest) => Promise<void>;
}
@@ -18,25 +20,20 @@ export interface UseLogPreviewResult {
export function useLogPreview(): UseLogPreviewResult {
const [preview, setPreview] = useState<LogPreviewResponse | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const run = useCallback(async (req: LogPreviewRequest): Promise<void> => {
setLoading(true);
try {
const resp = await previewLog(req);
setPreview(resp);
setError(null);
} catch (err: unknown) {
if (err instanceof Error) {
setPreview({
lines: [],
total_lines: 0,
matched_count: 0,
regex_error: err.message,
});
}
handleFetchError(err, setError, "Log preview failed");
} finally {
setLoading(false);
}
}, []);
return { preview, loading, run };
return { preview, loading, error, run };
}

View File

@@ -4,11 +4,13 @@
import { useCallback, useState } from "react";
import { testRegex } from "../api/config";
import { handleFetchError } from "../utils/fetchError";
import type { RegexTestRequest, RegexTestResponse } from "../types/config";
export interface UseRegexTesterResult {
result: RegexTestResponse | null;
testing: boolean;
error: string | null;
test: (req: RegexTestRequest) => Promise<void>;
}
@@ -18,20 +20,20 @@ export interface UseRegexTesterResult {
export function useRegexTester(): UseRegexTesterResult {
const [result, setResult] = useState<RegexTestResponse | null>(null);
const [testing, setTesting] = useState(false);
const [error, setError] = useState<string | null>(null);
const test = useCallback(async (req: RegexTestRequest): Promise<void> => {
setTesting(true);
try {
const resp = await testRegex(req);
setResult(resp);
setError(null);
} catch (err: unknown) {
if (err instanceof Error) {
setResult({ matched: false, groups: [], error: err.message });
}
handleFetchError(err, setError, "Regex test failed");
} finally {
setTesting(false);
}
}, []);
return { result, testing, test };
return { result, testing, error, test };
}