Skip to content

Commit 7143553

Browse files
feat: removing role functionality on audit user page
* feat: roles table for audit user page * feat: integrating api for permissions assignments on audit user page table * test: adding unit test for audit user page components * feat: roles table for audit user page * feat: roles table for audit user page * feat: delete role functionality added to user table * refactor: for toast manager context and some messages ids and descriptions * test: for role deletion on roles table audit user page * fix: minor issues after conflict solving * test: addint ut to improve coverage * fix: addressing pr comments
1 parent b143234 commit 7143553

30 files changed

Lines changed: 688 additions & 201 deletions

src/authz-module/audit-user/index.test.tsx

Lines changed: 209 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import { render, screen, waitFor } from '@testing-library/react';
2+
import { AppContext } from '@edx/frontend-platform/react';
23
import userEvent from '@testing-library/user-event';
34
import { MemoryRouter, Route, Routes } from 'react-router-dom';
45
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
56
import { IntlProvider } from '@edx/frontend-platform/i18n';
67
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
8+
import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext';
79
import AuditUserPage from './index';
810

911
jest.mock('@edx/frontend-platform/auth', () => ({
1012
getAuthenticatedHttpClient: jest.fn(),
1113
configure: jest.fn(),
1214
}));
1315

16+
jest.mock('@edx/frontend-platform/logging', () => ({
17+
logError: jest.fn(),
18+
}));
19+
1420
const mockUser = {
1521
username: 'johndoe',
1622
@@ -40,17 +46,32 @@ const renderWithRouter = (route = '/audit/johndoe') => {
4046
},
4147
});
4248

49+
const mockAppContext = {
50+
authenticatedUser: {
51+
username: 'testuser',
52+
53+
},
54+
config: {
55+
// @ts-ignore
56+
...process.env,
57+
},
58+
};
59+
4360
return render(
44-
<QueryClientProvider client={queryClient}>
45-
<IntlProvider locale="en">
46-
<MemoryRouter initialEntries={[route]}>
47-
<Routes>
48-
<Route path="/audit/:username" element={<AuditUserPage />} />
49-
<Route path="/authz" element={<div>Home Page</div>} />
50-
</Routes>
51-
</MemoryRouter>
52-
</IntlProvider>
53-
</QueryClientProvider>,
61+
<AppContext.Provider value={mockAppContext}>
62+
<QueryClientProvider client={queryClient}>
63+
<IntlProvider locale="en">
64+
<ToastManagerProvider>
65+
<MemoryRouter initialEntries={[route]}>
66+
<Routes>
67+
<Route path="/audit/:username" element={<AuditUserPage />} />
68+
<Route path="/authz" element={<div>Home Page</div>} />
69+
</Routes>
70+
</MemoryRouter>
71+
</ToastManagerProvider>
72+
</IntlProvider>
73+
</QueryClientProvider>
74+
</AppContext.Provider>,
5475
);
5576
};
5677

@@ -59,6 +80,11 @@ describe('AuditUserPage', () => {
5980
jest.clearAllMocks();
6081
});
6182

83+
beforeAll(() => {
84+
// @ts-ignore
85+
global.logError = jest.fn();
86+
});
87+
6288
it('renders user info and table when data is loaded', async () => {
6389
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
6490
get: jest
@@ -185,4 +211,177 @@ describe('AuditUserPage', () => {
185211
expect(screen.getByText(mockUser.username, { selector: 'li[aria-current="page"]' })).toBeInTheDocument();
186212
});
187213
});
214+
215+
it('opens and closes the ConfirmDeletionModal when delete is clicked and cancel is pressed', async () => {
216+
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
217+
get: jest
218+
.fn()
219+
.mockResolvedValueOnce({ data: mockUser })
220+
.mockResolvedValueOnce({ data: mockAssignments }),
221+
});
222+
223+
renderWithRouter();
224+
225+
await waitFor(() => {
226+
expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument();
227+
});
228+
229+
const user = userEvent.setup();
230+
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
231+
await user.click(deleteButton);
232+
233+
await waitFor(() => {
234+
expect(screen.getByRole('dialog')).toBeInTheDocument();
235+
expect(screen.getByText(/remove role\?/i)).toBeInTheDocument();
236+
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
237+
});
238+
239+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
240+
await user.click(cancelButton);
241+
242+
await waitFor(() => {
243+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
244+
});
245+
});
246+
247+
it('calls onSave when confirming deletion in ConfirmDeletionModal', async () => {
248+
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
249+
get: jest
250+
.fn()
251+
.mockResolvedValueOnce({ data: mockUser })
252+
.mockResolvedValueOnce({ data: mockAssignments }),
253+
delete: jest.fn().mockResolvedValue({ data: { errors: [] } }),
254+
});
255+
256+
renderWithRouter();
257+
258+
await waitFor(() => {
259+
expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument();
260+
});
261+
262+
const user = userEvent.setup();
263+
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
264+
await user.click(deleteButton);
265+
266+
await waitFor(() => {
267+
expect(screen.getByRole('dialog')).toBeInTheDocument();
268+
expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument();
269+
});
270+
271+
const removeButton = screen.getByRole('button', { name: /remove/i });
272+
await user.click(removeButton);
273+
274+
await waitFor(() => {
275+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
276+
expect(screen.getByText(/role has been successfully removed/i)).toBeInTheDocument();
277+
});
278+
});
279+
280+
it('shows error toast when role revocation succeeds but returns errors', async () => {
281+
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
282+
get: jest
283+
.fn()
284+
.mockResolvedValueOnce({ data: mockUser })
285+
.mockResolvedValueOnce({ data: mockAssignments }),
286+
delete: jest.fn().mockResolvedValue({
287+
data: {
288+
errors: ['Failed to revoke user role'],
289+
completed: [],
290+
},
291+
}),
292+
});
293+
294+
renderWithRouter();
295+
296+
await waitFor(() => {
297+
expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument();
298+
});
299+
300+
const user = userEvent.setup();
301+
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
302+
await user.click(deleteButton);
303+
304+
await waitFor(() => {
305+
expect(screen.getByRole('dialog')).toBeInTheDocument();
306+
expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument();
307+
});
308+
309+
const removeButton = screen.getByRole('button', { name: /remove/i });
310+
await user.click(removeButton);
311+
312+
await waitFor(() => {
313+
expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
314+
});
315+
});
316+
317+
it('shows error toast with retry when role revocation fails', async () => {
318+
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
319+
get: jest
320+
.fn()
321+
.mockResolvedValueOnce({ data: mockUser })
322+
.mockResolvedValueOnce({ data: mockAssignments }),
323+
delete: jest.fn().mockRejectedValue(new Error('Network error')),
324+
});
325+
326+
renderWithRouter();
327+
328+
await waitFor(() => {
329+
expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument();
330+
});
331+
332+
const user = userEvent.setup();
333+
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
334+
await user.click(deleteButton);
335+
336+
await waitFor(() => {
337+
expect(screen.getByRole('dialog')).toBeInTheDocument();
338+
expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument();
339+
});
340+
341+
const removeButton = screen.getByRole('button', { name: /remove/i });
342+
await user.click(removeButton);
343+
344+
await waitFor(() => {
345+
expect(screen.getByText(/something went wrong on our end/i)).toBeInTheDocument();
346+
expect(screen.getByText(/try again later/i)).toBeInTheDocument();
347+
});
348+
});
349+
350+
it('shows the extra warning when rolesCount is 1', async () => {
351+
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
352+
get: jest
353+
.fn()
354+
.mockResolvedValueOnce({ data: mockUser })
355+
.mockResolvedValueOnce({
356+
data: {
357+
count: 1,
358+
results: [
359+
{
360+
id: '1',
361+
role: 'library_admin',
362+
org: 'Test Org',
363+
scope: 'lib:test',
364+
permissionCount: 5,
365+
},
366+
],
367+
next: null,
368+
previous: null,
369+
},
370+
}),
371+
});
372+
373+
renderWithRouter();
374+
375+
await waitFor(() => {
376+
expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument();
377+
});
378+
379+
const user = userEvent.setup();
380+
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
381+
await user.click(deleteButton);
382+
383+
await waitFor(() => {
384+
expect(screen.getByText(/this is the user's only role/i)).toBeInTheDocument();
385+
});
386+
});
188387
});

0 commit comments

Comments
 (0)