Files
MeshChatX/tests/frontend/DebugLogsPage.test.js

164 lines
5.3 KiB
JavaScript

import { mount } from "@vue/test-utils";
import { describe, it, expect, vi, beforeEach } from "vitest";
import DebugLogsPage from "@/components/debug/DebugLogsPage.vue";
// Mock axios
window.api = {
get: vi.fn(),
};
describe("DebugLogsPage.vue", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("fetches and displays logs", async () => {
const mockLogs = [
{ timestamp: Date.now() / 1000, level: "INFO", module: "test", message: "Hello", is_anomaly: 0 },
{
timestamp: (Date.now() - 1000) / 1000,
level: "ERROR",
module: "test",
message: "Boom",
is_anomaly: 1,
anomaly_type: "repeat",
},
];
window.api.get.mockResolvedValue({
data: {
logs: mockLogs,
total: 2,
limit: 100,
offset: 0,
},
});
const wrapper = mount(DebugLogsPage, {
global: {
stubs: ["MaterialDesignIcon"],
mocks: { $t: (key) => key },
},
});
await new Promise((resolve) => setTimeout(resolve, 10));
await wrapper.vm.$nextTick();
const container = wrapper.find(".flex-1.overflow-auto.p-4.font-mono");
const logRows = container.findAll(".border-b");
expect(logRows.length).toBe(2);
expect(wrapper.text()).toContain("Hello");
expect(wrapper.text()).toContain("Boom");
expect(wrapper.text().toLowerCase()).toContain("repeat"); // Anomaly badge
});
it("handles search input", async () => {
window.api.get.mockResolvedValue({
data: { logs: [], total: 0, limit: 100, offset: 0 },
});
const wrapper = mount(DebugLogsPage, {
global: {
stubs: ["MaterialDesignIcon"],
mocks: { $t: (key) => key },
},
});
const searchInput = wrapper.find("input[placeholder='debug.search_logs_placeholder']");
await searchInput.setValue("error");
// Wait for debounce (500ms)
await new Promise((resolve) => setTimeout(resolve, 600));
expect(window.api.get).toHaveBeenCalledWith(
expect.stringContaining("/api/v1/debug/logs"),
expect.objectContaining({
params: expect.objectContaining({ search: "error" }),
})
);
});
it("handles pagination", async () => {
window.api.get.mockResolvedValue({
data: {
logs: [],
total: 250,
limit: 100,
offset: 0,
},
});
const wrapper = mount(DebugLogsPage, {
global: {
stubs: ["MaterialDesignIcon"],
mocks: { $t: (key) => key },
},
});
await new Promise((resolve) => setTimeout(resolve, 10));
const nextButton = wrapper.findAll("button").find((b) => b.text().includes("Next"));
await nextButton.trigger("click");
expect(window.api.get).toHaveBeenCalledWith(
expect.stringContaining("/api/v1/debug/logs"),
expect.objectContaining({
params: expect.objectContaining({ offset: 100 }),
})
);
});
it("loads access attempts when switching to Access attempts tab", async () => {
window.api.get.mockImplementation((url) => {
if (url.includes("access-attempts")) {
return Promise.resolve({
data: {
attempts: [
{
id: 1,
created_at: Date.now() / 1000,
identity_hash: "ab",
client_ip: "10.0.0.1",
user_agent: "TestUA/1",
path: "/api/v1/auth/login",
method: "POST",
outcome: "failed_password",
detail: "",
},
],
total: 1,
limit: 100,
offset: 0,
},
});
}
return Promise.resolve({
data: { logs: [], total: 0, limit: 100, offset: 0 },
});
});
const wrapper = mount(DebugLogsPage, {
global: {
stubs: ["MaterialDesignIcon"],
mocks: { $t: (key) => key },
},
});
await new Promise((resolve) => setTimeout(resolve, 10));
await wrapper.vm.$nextTick();
const accessTab = wrapper.findAll("button").find((b) => b.text().includes("debug.tab_access_attempts"));
expect(accessTab).toBeTruthy();
await accessTab.trigger("click");
await new Promise((resolve) => setTimeout(resolve, 10));
await wrapper.vm.$nextTick();
expect(window.api.get).toHaveBeenCalledWith(
expect.stringContaining("/api/v1/debug/access-attempts"),
expect.any(Object)
);
expect(wrapper.text()).toContain("failed_password");
expect(wrapper.text()).toContain("10.0.0.1");
});
});