Skip to content

Commit 8de204b

Browse files
committed
updates and add testas
1 parent 5a384d0 commit 8de204b

File tree

3 files changed

+293
-1
lines changed

3 files changed

+293
-1
lines changed

src/common/modules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export enum Modules {
1414
export const ModulesToHumanName: Record<Modules, string> = {
1515
[Modules.IAM]: "IAM",
1616
[Modules.EVENTS]: "Events",
17-
[Modules.STRIPE]: "Stripe",
17+
[Modules.STRIPE]: "Stripe Integration",
1818
[Modules.TICKETS]: "Ticketing/Merch",
1919
[Modules.EMAIL_NOTIFICATION]: "Email Notifications",
2020
[Modules.PROVISION_NEW_MEMBER]: "Member Provisioning",
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import React from 'react';
2+
import { render, screen, act } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import { vi } from 'vitest';
5+
import { MantineProvider } from '@mantine/core';
6+
import { notifications } from '@mantine/notifications';
7+
import { LogRenderer } from './LogRenderer';
8+
import { Modules, ModulesToHumanName } from '@common/modules';
9+
import { MemoryRouter } from 'react-router-dom';
10+
11+
describe('LogRenderer Tests', () => {
12+
const getLogsMock = vi.fn();
13+
console.log('matchMedia in test:', window.matchMedia?.toString());
14+
15+
// Mock date for consistent testing
16+
const mockCurrentDate = new Date('2023-01-15T12:00:00Z');
17+
const mockPastDate = new Date('2023-01-14T12:00:00Z');
18+
19+
// Sample log data for testing
20+
const sampleLogs = [
21+
{
22+
actor: 'admin',
23+
createdAt: Math.floor(mockCurrentDate.getTime() / 1000) - 3600,
24+
expireAt: Math.floor(mockCurrentDate.getTime() / 1000) + 86400,
25+
message: 'User created',
26+
module: Modules.IAM,
27+
requestId: 'req-123',
28+
target: '[email protected]',
29+
},
30+
{
31+
actor: 'system',
32+
createdAt: Math.floor(mockCurrentDate.getTime() / 1000) - 7200,
33+
expireAt: Math.floor(mockCurrentDate.getTime() / 1000) + 86400,
34+
message: 'Config updated',
35+
module: Modules.AUDIT_LOG,
36+
requestId: 'req-456',
37+
target: Modules.STRIPE,
38+
},
39+
];
40+
41+
const renderComponent = async () => {
42+
await act(async () => {
43+
render(
44+
<MemoryRouter>
45+
<MantineProvider withGlobalClasses withCssVariables forceColorScheme="light">
46+
<LogRenderer getLogs={getLogsMock} />
47+
</MantineProvider>
48+
</MemoryRouter>
49+
);
50+
});
51+
};
52+
53+
beforeEach(() => {
54+
vi.clearAllMocks();
55+
// Mock Date.now to return a fixed timestamp
56+
vi.spyOn(Date, 'now').mockImplementation(() => mockCurrentDate.getTime());
57+
// Reset notification spy
58+
vi.spyOn(notifications, 'show');
59+
});
60+
61+
it('renders the filter controls correctly', async () => {
62+
await renderComponent();
63+
64+
expect(screen.getByText('Filter Logs')).toBeInTheDocument();
65+
expect(screen.getByText('Module')).toBeInTheDocument();
66+
expect(screen.getByText('Start Time')).toBeInTheDocument();
67+
expect(screen.getByText('End Time')).toBeInTheDocument();
68+
expect(screen.getByRole('button', { name: /Fetch Logs/i })).toBeInTheDocument();
69+
});
70+
71+
it('shows error notification when fetch logs without selecting a module', async () => {
72+
const user = userEvent.setup();
73+
await renderComponent();
74+
75+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
76+
77+
expect(notifications.show).toHaveBeenCalledWith(
78+
expect.objectContaining({
79+
title: 'Missing parameters',
80+
message: 'Please select a module and time range',
81+
color: 'red',
82+
})
83+
);
84+
expect(getLogsMock).not.toHaveBeenCalled();
85+
});
86+
87+
it('fetches logs successfully when parameters are valid', async () => {
88+
getLogsMock.mockResolvedValue(sampleLogs);
89+
const user = userEvent.setup();
90+
await renderComponent();
91+
92+
// Select a module
93+
await user.click(screen.getByPlaceholderText('Select service module'));
94+
// Find and click on the IAM option
95+
await user.click(screen.getByText(ModulesToHumanName[Modules.IAM]));
96+
97+
// Click fetch logs
98+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
99+
100+
// Verify the getLogs was called with correct parameters
101+
expect(getLogsMock).toHaveBeenCalledWith(
102+
Modules.IAM,
103+
expect.any(Number), // Start timestamp
104+
expect.any(Number) // End timestamp
105+
);
106+
107+
// Verify logs are displayed
108+
await screen.findByText('User created');
109+
expect(screen.getByText('admin')).toBeInTheDocument();
110+
expect(screen.getByText('[email protected]')).toBeInTheDocument();
111+
expect(screen.getByText('req-123')).toBeInTheDocument();
112+
});
113+
114+
it('handles API errors gracefully', async () => {
115+
getLogsMock.mockRejectedValue(new Error('API Error'));
116+
const user = userEvent.setup();
117+
await renderComponent();
118+
119+
// Select a module
120+
await user.click(screen.getByPlaceholderText('Select service module'));
121+
await user.click(screen.getByText(ModulesToHumanName[Modules.EVENTS]));
122+
123+
// Click fetch logs
124+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
125+
126+
expect(notifications.show).toHaveBeenCalledWith(
127+
expect.objectContaining({
128+
title: 'Error fetching logs',
129+
message: 'Failed to load logs. Please try again later.',
130+
color: 'red',
131+
})
132+
);
133+
});
134+
135+
it('filters logs based on search query', async () => {
136+
getLogsMock.mockResolvedValue(sampleLogs);
137+
const user = userEvent.setup();
138+
await renderComponent();
139+
140+
// Select a module and fetch logs
141+
await user.click(screen.getByPlaceholderText('Select service module'));
142+
await user.click(screen.getByText(ModulesToHumanName[Modules.AUDIT_LOG]));
143+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
144+
145+
// Wait for logs to display
146+
await screen.findByText('User created');
147+
148+
// Search for 'config'
149+
await user.type(screen.getByPlaceholderText('Search in logs...'), 'config');
150+
151+
// "User created" should no longer be visible, but "Config updated" should be
152+
expect(screen.queryByText('User created')).not.toBeInTheDocument();
153+
expect(screen.getByText('Config updated')).toBeInTheDocument();
154+
});
155+
156+
it('toggles between UTC and local time display', async () => {
157+
getLogsMock.mockResolvedValue(sampleLogs);
158+
const user = userEvent.setup();
159+
await renderComponent();
160+
161+
// Select a module and fetch logs
162+
await user.click(screen.getByPlaceholderText('Select service module'));
163+
await user.click(screen.getByText(ModulesToHumanName[Modules.IAM]));
164+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
165+
166+
// Wait for logs to display
167+
await screen.findByText('User created');
168+
169+
// Check default is local time
170+
expect(screen.getByText(/Show times in local timezone/)).toBeInTheDocument();
171+
172+
// Toggle to UTC
173+
await user.click(screen.getByRole('switch'));
174+
expect(screen.getByText(/Show times in UTC/)).toBeInTheDocument();
175+
});
176+
177+
it('paginates logs correctly', async () => {
178+
// Create 15 sample logs
179+
const manyLogs = Array(15)
180+
.fill(null)
181+
.map((_, index) => ({
182+
actor: `actor-${index}`,
183+
createdAt: Math.floor(mockCurrentDate.getTime() / 1000) - index * 100,
184+
expireAt: Math.floor(mockCurrentDate.getTime() / 1000) + 86400,
185+
message: `Message ${index}`,
186+
module: Modules.IAM,
187+
requestId: `req-${index}`,
188+
target: `target-${index}`,
189+
}));
190+
191+
getLogsMock.mockResolvedValue(manyLogs);
192+
const user = userEvent.setup();
193+
await renderComponent();
194+
195+
// Select a module and fetch logs
196+
await user.click(screen.getByPlaceholderText('Select service module'));
197+
await user.click(screen.getByText(ModulesToHumanName[Modules.IAM]));
198+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
199+
200+
// Wait for logs to display - first page should show entries 0-9
201+
await screen.findByText('Message 0');
202+
expect(screen.getByText('Message 9')).toBeInTheDocument();
203+
expect(screen.queryByText('Message 10')).not.toBeInTheDocument();
204+
205+
// Go to page 2
206+
await user.click(screen.getByRole('button', { name: '2' }));
207+
208+
// Second page should show entries 10-14
209+
expect(screen.queryByText('Message 9')).not.toBeInTheDocument();
210+
expect(screen.getByText('Message 10')).toBeInTheDocument();
211+
expect(screen.getByText('Message 14')).toBeInTheDocument();
212+
213+
// Change page size
214+
await user.click(screen.getByText('10'));
215+
await user.click(screen.getByText('25'));
216+
217+
// Should now show all logs on one page
218+
expect(screen.getByText('Message 0')).toBeInTheDocument();
219+
expect(screen.getByText('Message 14')).toBeInTheDocument();
220+
});
221+
222+
it('shows empty state when no logs are returned', async () => {
223+
getLogsMock.mockResolvedValue([]);
224+
const user = userEvent.setup();
225+
await renderComponent();
226+
227+
// Select a module and fetch logs
228+
await user.click(screen.getByPlaceholderText('Select service module'));
229+
await user.click(screen.getByText(ModulesToHumanName[Modules.MOBILE_WALLET]));
230+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
231+
232+
// Should show empty state
233+
expect(screen.getByText('No logs to display')).toBeInTheDocument();
234+
});
235+
236+
it('displays translated module names when viewing audit logs', async () => {
237+
const auditLogs = [
238+
{
239+
actor: 'admin',
240+
createdAt: Math.floor(mockCurrentDate.getTime() / 1000) - 3600,
241+
expireAt: Math.floor(mockCurrentDate.getTime() / 1000) + 86400,
242+
message: 'Module accessed',
243+
module: Modules.AUDIT_LOG,
244+
requestId: 'req-789',
245+
target: Modules.STRIPE, // This should be translated to "Stripe" in the UI
246+
},
247+
];
248+
249+
getLogsMock.mockResolvedValue(auditLogs);
250+
const user = userEvent.setup();
251+
await renderComponent();
252+
253+
// Select the AUDIT_LOG module
254+
await user.click(screen.getByPlaceholderText('Select service module'));
255+
await user.click(screen.getByText(ModulesToHumanName[Modules.AUDIT_LOG]));
256+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
257+
258+
// Wait for logs to display
259+
await screen.findByText('Module accessed');
260+
261+
// The target column should show "Stripe" (the human-readable name) instead of "stripe"
262+
expect(screen.getAllByText('Stripe Integration')).toHaveLength(2);
263+
});
264+
265+
it('respects date range selection when fetching logs', async () => {
266+
getLogsMock.mockResolvedValue(sampleLogs);
267+
const user = userEvent.setup();
268+
await renderComponent();
269+
270+
// Select a module
271+
await user.click(screen.getByPlaceholderText('Select service module'));
272+
await user.click(screen.getByText(ModulesToHumanName[Modules.LINKRY]));
273+
274+
// Open and set Start Time
275+
await user.click(screen.getByRole('button', { name: /Start Time/i }));
276+
const [startInput] = await screen.findAllByRole('textbox');
277+
await user.type(startInput, '01/10/2023 12:00 AM');
278+
279+
// Open and set End Time
280+
await user.click(screen.getByRole('button', { name: /End Time/i }));
281+
const [endInput] = await screen.findAllByRole('textbox');
282+
await user.type(endInput, '01/11/2023 11:59 PM');
283+
284+
// Click Fetch Logs
285+
await user.click(screen.getByRole('button', { name: /Fetch Logs/i }));
286+
287+
// Assert that getLogsMock was called with correct arguments
288+
expect(getLogsMock).toHaveBeenCalledWith('linkry', expect.any(Number), expect.any(Number));
289+
});
290+
});

src/ui/pages/logs/LogRenderer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ export const LogRenderer: React.FC<LogRendererProps> = ({ getLogs }) => {
203203
valueFormat={
204204
showUtcTime ? 'MM-DD-YYYY h:mm A [UTC]' : `MM-DD-YYYY h:mm A [Local Time]`
205205
}
206+
data-testid="start-time-input"
206207
required
207208
/>
208209

@@ -215,6 +216,7 @@ export const LogRenderer: React.FC<LogRendererProps> = ({ getLogs }) => {
215216
valueFormat={
216217
showUtcTime ? 'MM-DD-YYYY h:mm A [UTC]' : `MM-DD-YYYY h:mm A [Local Time]`
217218
}
219+
data-testid="end-time-input"
218220
required
219221
/>
220222

0 commit comments

Comments
 (0)