- Rename npm packages from @kb/* to @hai/* and update all workspace references - Rename CLI binary from kb to hai and config directory from .kb to .hai - Update dashboard UI branding, titles, and references from kb to hai - Update all test files, CI workflows, and documentation to reflect new naming - Run comprehensive grep verification to ensure no stale kb references remain
145 lines
4.0 KiB
TypeScript
145 lines
4.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { renderHook, act, waitFor } from "@testing-library/react";
|
|
import { useAgentLogs } from "../useAgentLogs";
|
|
import { fetchAgentLogs } from "../../api";
|
|
|
|
// Mock the api module
|
|
vi.mock("../../api", () => ({
|
|
fetchAgentLogs: vi.fn().mockResolvedValue([]),
|
|
}));
|
|
|
|
const mockFetchAgentLogs = vi.mocked(fetchAgentLogs);
|
|
|
|
// Mock EventSource
|
|
class MockEventSource {
|
|
static instances: MockEventSource[] = [];
|
|
url: string;
|
|
listeners: Record<string, ((e: any) => void)[]> = {};
|
|
readyState = 0;
|
|
close = vi.fn();
|
|
|
|
constructor(url: string) {
|
|
this.url = url;
|
|
this.readyState = 1;
|
|
MockEventSource.instances.push(this);
|
|
}
|
|
|
|
addEventListener(event: string, fn: (e: any) => void) {
|
|
if (!this.listeners[event]) this.listeners[event] = [];
|
|
this.listeners[event].push(fn);
|
|
}
|
|
|
|
// Helper to simulate a server event
|
|
_emit(event: string, data: any) {
|
|
for (const fn of this.listeners[event] || []) {
|
|
fn({ data: JSON.stringify(data) });
|
|
}
|
|
}
|
|
}
|
|
|
|
const originalEventSource = globalThis.EventSource;
|
|
|
|
beforeEach(() => {
|
|
MockEventSource.instances = [];
|
|
(globalThis as any).EventSource = MockEventSource;
|
|
mockFetchAgentLogs.mockReset().mockResolvedValue([]);
|
|
});
|
|
|
|
afterEach(() => {
|
|
(globalThis as any).EventSource = originalEventSource;
|
|
});
|
|
|
|
describe("useAgentLogs", () => {
|
|
it("does not fetch or connect when enabled=false", () => {
|
|
const { result } = renderHook(() => useAgentLogs("KB-001", false));
|
|
|
|
expect(mockFetchAgentLogs).not.toHaveBeenCalled();
|
|
expect(MockEventSource.instances).toHaveLength(0);
|
|
expect(result.current.entries).toEqual([]);
|
|
});
|
|
|
|
it("fetches historical logs and opens SSE when enabled=true", async () => {
|
|
const historicalLogs = [
|
|
{ timestamp: "2026-01-01T00:00:00Z", taskId: "KB-001", text: "old", type: "text" as const },
|
|
];
|
|
mockFetchAgentLogs.mockResolvedValueOnce(historicalLogs);
|
|
|
|
const { result } = renderHook(() => useAgentLogs("KB-001", true));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.entries).toEqual(historicalLogs);
|
|
});
|
|
|
|
expect(mockFetchAgentLogs).toHaveBeenCalledWith("KB-001");
|
|
expect(MockEventSource.instances).toHaveLength(1);
|
|
expect(MockEventSource.instances[0].url).toBe("/api/tasks/KB-001/logs/stream");
|
|
});
|
|
|
|
it("appends live SSE entries to historical entries", async () => {
|
|
mockFetchAgentLogs.mockResolvedValueOnce([
|
|
{ timestamp: "2026-01-01T00:00:00Z", taskId: "KB-001", text: "old", type: "text" as const },
|
|
]);
|
|
|
|
const { result } = renderHook(() => useAgentLogs("KB-001", true));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.entries).toHaveLength(1);
|
|
});
|
|
|
|
const es = MockEventSource.instances[0];
|
|
act(() => {
|
|
es._emit("agent:log", {
|
|
timestamp: "2026-01-01T00:01:00Z",
|
|
taskId: "KB-001",
|
|
text: "new",
|
|
type: "text",
|
|
});
|
|
});
|
|
|
|
expect(result.current.entries).toHaveLength(2);
|
|
expect(result.current.entries[1].text).toBe("new");
|
|
});
|
|
|
|
it("closes SSE when enabled changes to false", async () => {
|
|
mockFetchAgentLogs.mockResolvedValueOnce([]);
|
|
|
|
const { rerender } = renderHook(
|
|
({ enabled }) => useAgentLogs("KB-001", enabled),
|
|
{ initialProps: { enabled: true } },
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(MockEventSource.instances).toHaveLength(1);
|
|
});
|
|
|
|
const es = MockEventSource.instances[0];
|
|
|
|
rerender({ enabled: false });
|
|
|
|
expect(es.close).toHaveBeenCalled();
|
|
});
|
|
|
|
it("closes SSE on unmount", async () => {
|
|
mockFetchAgentLogs.mockResolvedValueOnce([]);
|
|
|
|
const { unmount } = renderHook(() => useAgentLogs("KB-001", true));
|
|
|
|
await waitFor(() => {
|
|
expect(MockEventSource.instances).toHaveLength(1);
|
|
});
|
|
|
|
const es = MockEventSource.instances[0];
|
|
|
|
unmount();
|
|
|
|
expect(es.close).toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not fetch when taskId is null", () => {
|
|
renderHook(() => useAgentLogs(null, true));
|
|
|
|
expect(mockFetchAgentLogs).not.toHaveBeenCalled();
|
|
expect(MockEventSource.instances).toHaveLength(0);
|
|
});
|
|
});
|