456 lines
13 KiB
JavaScript
456 lines
13 KiB
JavaScript
const LogReaderService = require("../../../../src/services/logReader");
|
|
|
|
// Mock the LogReaderService
|
|
jest.mock("../../../../src/services/logReader");
|
|
|
|
describe("LogViewerScreen - Search and Filtering", () => {
|
|
let mockLogReader;
|
|
let mockPaginatedData;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
// Setup mock data with various entry types for testing
|
|
mockPaginatedData = {
|
|
entries: [
|
|
{
|
|
id: "entry_1",
|
|
type: "operation_start",
|
|
timestamp: new Date("2025-08-06T20:30:00Z"),
|
|
level: "INFO",
|
|
message: "Price Update Operation Started",
|
|
title: "Price Update Operation",
|
|
details: "Target Tag: summer-sale\nPrice Adjustment: -10%",
|
|
configuration: {
|
|
"Target Tag": "summer-sale",
|
|
"Price Adjustment": "-10%",
|
|
},
|
|
},
|
|
{
|
|
id: "entry_2",
|
|
type: "product_update",
|
|
timestamp: new Date("2025-08-06T20:30:30Z"),
|
|
level: "SUCCESS",
|
|
message: "Updated The Hidden Snowboard",
|
|
title: "Product Update: The Hidden Snowboard",
|
|
details:
|
|
"Product ID: gid://shopify/Product/8116504920355\nPrice: $749.99 → $674.99",
|
|
productTitle: "The Hidden Snowboard",
|
|
productId: "gid://shopify/Product/8116504920355",
|
|
},
|
|
{
|
|
id: "entry_3",
|
|
type: "error",
|
|
timestamp: new Date("2025-08-06T20:31:00Z"),
|
|
level: "ERROR",
|
|
message: "Failed to update Product XYZ",
|
|
title: "Error: Product XYZ",
|
|
details: "Product ID: xyz123\nError: Rate limit exceeded",
|
|
productTitle: "Product XYZ",
|
|
productId: "xyz123",
|
|
},
|
|
{
|
|
id: "entry_4",
|
|
type: "rollback",
|
|
timestamp: new Date(),
|
|
level: "INFO",
|
|
message: "Rollback Operation Started",
|
|
title: "Rollback Operation",
|
|
details: "Rolling back previous changes",
|
|
configuration: { "Operation Mode": "rollback" },
|
|
},
|
|
],
|
|
pagination: {
|
|
currentPage: 0,
|
|
pageSize: 10,
|
|
totalEntries: 4,
|
|
totalPages: 1,
|
|
hasNextPage: false,
|
|
hasPreviousPage: false,
|
|
startIndex: 1,
|
|
endIndex: 4,
|
|
},
|
|
filters: {
|
|
levelFilter: "ALL",
|
|
searchTerm: "",
|
|
},
|
|
};
|
|
|
|
// Setup LogReaderService mock
|
|
mockLogReader = {
|
|
getPaginatedEntries: jest.fn().mockResolvedValue(mockPaginatedData),
|
|
getLogStatistics: jest.fn().mockResolvedValue({}),
|
|
clearCache: jest.fn(),
|
|
watchFile: jest.fn().mockReturnValue(() => {}),
|
|
};
|
|
|
|
LogReaderService.mockImplementation(() => mockLogReader);
|
|
});
|
|
|
|
describe("Level Filtering", () => {
|
|
test("supports filtering by ERROR level", async () => {
|
|
const errorOnlyData = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) => e.level === "ERROR"),
|
|
filters: { ...mockPaginatedData.filters, levelFilter: "ERROR" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(errorOnlyData);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
levelFilter: "ERROR",
|
|
});
|
|
|
|
expect(mockLogReader.getPaginatedEntries).toHaveBeenCalledWith(
|
|
expect.objectContaining({ levelFilter: "ERROR" })
|
|
);
|
|
expect(result.filters.levelFilter).toBe("ERROR");
|
|
});
|
|
|
|
test("supports filtering by SUCCESS level", async () => {
|
|
const successOnlyData = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) => e.level === "SUCCESS"),
|
|
filters: { ...mockPaginatedData.filters, levelFilter: "SUCCESS" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(successOnlyData);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
levelFilter: "SUCCESS",
|
|
});
|
|
|
|
expect(result.filters.levelFilter).toBe("SUCCESS");
|
|
});
|
|
|
|
test("supports filtering by INFO level", async () => {
|
|
const infoOnlyData = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) => e.level === "INFO"),
|
|
filters: { ...mockPaginatedData.filters, levelFilter: "INFO" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(infoOnlyData);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
levelFilter: "INFO",
|
|
});
|
|
|
|
expect(result.filters.levelFilter).toBe("INFO");
|
|
});
|
|
|
|
test("supports showing all levels", async () => {
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
levelFilter: "ALL",
|
|
});
|
|
|
|
expect(result.filters.levelFilter).toBe("ALL");
|
|
expect(result.entries.length).toBe(4); // All entries
|
|
});
|
|
});
|
|
|
|
describe("Text Search", () => {
|
|
test("searches in message content", async () => {
|
|
const searchResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) =>
|
|
e.message.toLowerCase().includes("snowboard")
|
|
),
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "snowboard" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(searchResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "snowboard",
|
|
});
|
|
|
|
expect(mockLogReader.getPaginatedEntries).toHaveBeenCalledWith(
|
|
expect.objectContaining({ searchTerm: "snowboard" })
|
|
);
|
|
expect(result.filters.searchTerm).toBe("snowboard");
|
|
});
|
|
|
|
test("searches in title content", async () => {
|
|
const searchResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) =>
|
|
e.title.toLowerCase().includes("error")
|
|
),
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "error" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(searchResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "error",
|
|
});
|
|
|
|
expect(result.filters.searchTerm).toBe("error");
|
|
});
|
|
|
|
test("searches in details content", async () => {
|
|
const searchResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) =>
|
|
e.details.toLowerCase().includes("rate limit")
|
|
),
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "rate limit" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(searchResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "rate limit",
|
|
});
|
|
|
|
expect(result.filters.searchTerm).toBe("rate limit");
|
|
});
|
|
|
|
test("searches in product titles", async () => {
|
|
const searchResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter(
|
|
(e) =>
|
|
e.productTitle && e.productTitle.toLowerCase().includes("hidden")
|
|
),
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "hidden" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(searchResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "hidden",
|
|
});
|
|
|
|
expect(result.filters.searchTerm).toBe("hidden");
|
|
});
|
|
|
|
test("handles case-insensitive search", async () => {
|
|
const searchResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) =>
|
|
e.message.toLowerCase().includes("update")
|
|
),
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "UPDATE" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(searchResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "UPDATE",
|
|
});
|
|
|
|
expect(result.filters.searchTerm).toBe("UPDATE");
|
|
});
|
|
});
|
|
|
|
describe("Advanced Search Features", () => {
|
|
test("searches by operation type", async () => {
|
|
const searchResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) => e.type === "rollback"),
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "rollback" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(searchResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "rollback",
|
|
});
|
|
|
|
expect(result.filters.searchTerm).toBe("rollback");
|
|
});
|
|
|
|
test("searches in configuration values", async () => {
|
|
const searchResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter(
|
|
(e) =>
|
|
e.configuration &&
|
|
Object.values(e.configuration).some((v) =>
|
|
v.includes("summer-sale")
|
|
)
|
|
),
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "summer-sale" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(searchResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "summer-sale",
|
|
});
|
|
|
|
expect(result.filters.searchTerm).toBe("summer-sale");
|
|
});
|
|
|
|
test("supports date-based search for today", async () => {
|
|
const todayResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter((e) => {
|
|
const today = new Date();
|
|
return e.timestamp.toDateString() === today.toDateString();
|
|
}),
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "today" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(todayResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "today",
|
|
});
|
|
|
|
expect(result.filters.searchTerm).toBe("today");
|
|
});
|
|
|
|
test("returns empty results for non-matching search", async () => {
|
|
const emptyResults = {
|
|
...mockPaginatedData,
|
|
entries: [],
|
|
pagination: {
|
|
...mockPaginatedData.pagination,
|
|
totalEntries: 0,
|
|
totalPages: 0,
|
|
endIndex: 0,
|
|
},
|
|
filters: { ...mockPaginatedData.filters, searchTerm: "nonexistent" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(emptyResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "nonexistent",
|
|
});
|
|
|
|
expect(result.entries).toHaveLength(0);
|
|
expect(result.pagination.totalEntries).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("Combined Filtering", () => {
|
|
test("supports combining level filter and search", async () => {
|
|
const combinedResults = {
|
|
...mockPaginatedData,
|
|
entries: mockPaginatedData.entries.filter(
|
|
(e) =>
|
|
e.level === "ERROR" && e.message.toLowerCase().includes("failed")
|
|
),
|
|
filters: { levelFilter: "ERROR", searchTerm: "failed" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(combinedResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
levelFilter: "ERROR",
|
|
searchTerm: "failed",
|
|
});
|
|
|
|
expect(result.filters.levelFilter).toBe("ERROR");
|
|
expect(result.filters.searchTerm).toBe("failed");
|
|
});
|
|
|
|
test("resets pagination when applying filters", async () => {
|
|
const logReader = new LogReaderService();
|
|
|
|
// Apply filter and verify page resets to 0
|
|
await logReader.getPaginatedEntries({
|
|
levelFilter: "ERROR",
|
|
page: 0, // Should reset to 0 when filtering
|
|
});
|
|
|
|
expect(mockLogReader.getPaginatedEntries).toHaveBeenCalledWith(
|
|
expect.objectContaining({ page: 0 })
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Filter Persistence", () => {
|
|
test("maintains filters across pagination", async () => {
|
|
const logReader = new LogReaderService();
|
|
|
|
// Apply filters and navigate to page 2
|
|
await logReader.getPaginatedEntries({
|
|
levelFilter: "INFO",
|
|
searchTerm: "update",
|
|
page: 1,
|
|
});
|
|
|
|
expect(mockLogReader.getPaginatedEntries).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
levelFilter: "INFO",
|
|
searchTerm: "update",
|
|
page: 1,
|
|
})
|
|
);
|
|
});
|
|
|
|
test("clears filters when requested", async () => {
|
|
const clearedResults = {
|
|
...mockPaginatedData,
|
|
filters: { levelFilter: "ALL", searchTerm: "" },
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(clearedResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
levelFilter: "ALL",
|
|
searchTerm: "",
|
|
page: 0,
|
|
});
|
|
|
|
expect(result.filters.levelFilter).toBe("ALL");
|
|
expect(result.filters.searchTerm).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("Performance Considerations", () => {
|
|
test("handles large result sets efficiently", async () => {
|
|
const largeResults = {
|
|
...mockPaginatedData,
|
|
pagination: {
|
|
...mockPaginatedData.pagination,
|
|
totalEntries: 1000,
|
|
totalPages: 50,
|
|
pageSize: 20,
|
|
},
|
|
};
|
|
|
|
mockLogReader.getPaginatedEntries.mockResolvedValue(largeResults);
|
|
|
|
const logReader = new LogReaderService();
|
|
const result = await logReader.getPaginatedEntries({
|
|
searchTerm: "update",
|
|
pageSize: 20,
|
|
});
|
|
|
|
expect(result.pagination.totalEntries).toBe(1000);
|
|
expect(result.pagination.totalPages).toBe(50);
|
|
});
|
|
|
|
test("limits results per page appropriately", async () => {
|
|
const logReader = new LogReaderService();
|
|
|
|
await logReader.getPaginatedEntries({ pageSize: 5 });
|
|
|
|
expect(mockLogReader.getPaginatedEntries).toHaveBeenCalledWith(
|
|
expect.objectContaining({ pageSize: 5 })
|
|
);
|
|
});
|
|
});
|
|
});
|