Skip to content

Commit 00d87f3

Browse files
committed
chore: added tests for sso provisioning tab
chore: excluded shadcn ui components from tests chore: removed types from test coverages
1 parent bd4ba9a commit 00d87f3

12 files changed

Lines changed: 996 additions & 1 deletion

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import type { IdpScimTokenBase } from '@auth0/web-ui-components-core';
2+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import { describe, it, expect, vi, beforeEach } from 'vitest';
5+
6+
import { ProvisioningManageToken } from '../provisioning-manage-token';
7+
8+
// Mock hooks
9+
vi.mock('../../../../../../hooks/use-translator', () => ({
10+
useTranslator: () => ({
11+
t: (key: string, params?: any) => {
12+
if (key === 'delete_button_label') return 'Delete';
13+
if (key === 'remove_button_label') return 'Remove';
14+
if (key === 'title' && params?.providerName) return `Delete ${params.providerName}`;
15+
return key;
16+
},
17+
}),
18+
}));
19+
20+
describe('ProvisioningManageToken', () => {
21+
const mockOnListScimTokens = vi.fn();
22+
const mockOnCreateScimToken = vi.fn();
23+
const mockOnDeleteScimToken = vi.fn();
24+
25+
const defaultProps = {
26+
isScimTokensLoading: false,
27+
isScimTokenCreating: false,
28+
isScimTokenDeleting: false,
29+
onListScimTokens: mockOnListScimTokens,
30+
onCreateScimToken: mockOnCreateScimToken,
31+
onDeleteScimToken: mockOnDeleteScimToken,
32+
};
33+
34+
const mockTokens: IdpScimTokenBase[] = [
35+
{
36+
token_id: 'token-1',
37+
valid_until: undefined,
38+
created_at: '',
39+
},
40+
{
41+
token_id: 'token-2',
42+
valid_until: '2025-12-31T23:59:59Z',
43+
created_at: '',
44+
},
45+
];
46+
47+
const expiredToken: IdpScimTokenBase = {
48+
token_id: 'token-expired',
49+
valid_until: '2020-01-01T00:00:00Z',
50+
created_at: '',
51+
};
52+
53+
beforeEach(() => {
54+
vi.clearAllMocks();
55+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: [] });
56+
});
57+
58+
it('should render title and description', async () => {
59+
render(<ProvisioningManageToken {...defaultProps} />);
60+
61+
expect(await screen.findByText('title')).toBeInTheDocument();
62+
expect(await screen.findByText('description')).toBeInTheDocument();
63+
});
64+
65+
it('should render generate button', async () => {
66+
render(<ProvisioningManageToken {...defaultProps} />);
67+
68+
expect(await screen.findByText('generate_button_label')).toBeInTheDocument();
69+
});
70+
71+
it('should load tokens on mount', async () => {
72+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: mockTokens });
73+
render(<ProvisioningManageToken {...defaultProps} />);
74+
75+
await waitFor(() => {
76+
expect(mockOnListScimTokens).toHaveBeenCalled();
77+
});
78+
});
79+
80+
it('should show loading spinner when isScimTokensLoading is true', async () => {
81+
render(<ProvisioningManageToken {...defaultProps} isScimTokensLoading={true} />);
82+
expect(await screen.findByText('Loading...')).toBeInTheDocument();
83+
});
84+
85+
it('should display tokens when loaded', async () => {
86+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: mockTokens });
87+
render(<ProvisioningManageToken {...defaultProps} />);
88+
89+
expect(await screen.findByText(/token-1/)).toBeInTheDocument();
90+
expect(await screen.findByText(/token-2/)).toBeInTheDocument();
91+
});
92+
93+
it('should show active badge for active tokens', async () => {
94+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: mockTokens });
95+
render(<ProvisioningManageToken {...defaultProps} />);
96+
97+
const badges = await screen.findAllByText('token_item.status_active');
98+
expect(badges).toHaveLength(2);
99+
});
100+
101+
it('should show expired badge for expired tokens', async () => {
102+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: [expiredToken] });
103+
render(<ProvisioningManageToken {...defaultProps} />);
104+
105+
expect(await screen.findByText('token_item.status_expired')).toBeInTheDocument();
106+
});
107+
108+
it('should show "never expire" text for tokens without valid_until', async () => {
109+
mockOnListScimTokens.mockResolvedValue({
110+
scim_tokens: [{ token_id: 'token-1', valid_until: undefined }],
111+
});
112+
render(<ProvisioningManageToken {...defaultProps} />);
113+
114+
expect(await screen.findByText('token_item.never_expire')).toBeInTheDocument();
115+
});
116+
117+
it('should call onCreateScimToken when generate button is clicked', async () => {
118+
const user = userEvent.setup();
119+
const mockCreatedToken = {
120+
scim_token: 'new-token-value',
121+
token_id: 'new-token-id',
122+
};
123+
mockOnCreateScimToken.mockResolvedValue(mockCreatedToken);
124+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: [] });
125+
126+
render(<ProvisioningManageToken {...defaultProps} />);
127+
128+
const generateButton = await screen.findByText('generate_button_label');
129+
await user.click(generateButton);
130+
131+
await waitFor(() => {
132+
expect(mockOnCreateScimToken).toHaveBeenCalledWith({ token_lifetime: 3600 });
133+
});
134+
});
135+
136+
it('should disable generate button when max tokens reached', async () => {
137+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: mockTokens });
138+
render(<ProvisioningManageToken {...defaultProps} />);
139+
140+
const generateButton = await screen.findByText('generate_button_label');
141+
expect(generateButton).toBeDisabled();
142+
});
143+
144+
it('should disable generate button when isScimTokenCreating is true', () => {
145+
render(<ProvisioningManageToken {...defaultProps} isScimTokenCreating={true} />);
146+
147+
const generateButton = screen.getByText('generate_button_label');
148+
expect(generateButton).toBeDisabled();
149+
});
150+
151+
it('should show spinner on generate button when creating token', async () => {
152+
render(<ProvisioningManageToken {...defaultProps} isScimTokenCreating={true} />);
153+
154+
const spinners = await screen.findAllByText('Loading...');
155+
expect(spinners.length).toBeGreaterThan(0);
156+
});
157+
158+
it('should open delete modal when delete button is clicked', async () => {
159+
mockOnListScimTokens.mockResolvedValue({
160+
scim_tokens: [{ token_id: 'token-1', valid_until: null }],
161+
});
162+
render(<ProvisioningManageToken {...defaultProps} />);
163+
164+
await screen.findByText(/token-1/);
165+
166+
const deleteButton = screen.findByRole('button', { name: /delete/i });
167+
fireEvent.click(await deleteButton);
168+
expect(await screen.findByRole('dialog')).toBeInTheDocument();
169+
});
170+
171+
it('should call onDeleteScimToken when delete is confirmed', async () => {
172+
const user = userEvent.setup();
173+
mockOnListScimTokens.mockResolvedValue({
174+
scim_tokens: [{ token_id: 'token-1', valid_until: null }],
175+
});
176+
render(<ProvisioningManageToken {...defaultProps} />);
177+
178+
await screen.findByText(/token-1/);
179+
180+
const deleteButton = screen.findByRole('button', { name: /delete/i });
181+
fireEvent.click(await deleteButton);
182+
await screen.findByRole('dialog');
183+
184+
const confirmButton = screen.findByText('delete_modal.delete_button_label');
185+
user.click(await confirmButton);
186+
await waitFor(() => {
187+
expect(mockOnDeleteScimToken).toHaveBeenCalledWith('token-1');
188+
});
189+
});
190+
191+
it('should disable delete buttons when isScimTokenDeleting is true', async () => {
192+
mockOnListScimTokens.mockResolvedValue({
193+
scim_tokens: [{ token_id: 'token-1', valid_until: null }],
194+
});
195+
render(<ProvisioningManageToken {...defaultProps} isScimTokenDeleting={true} />);
196+
197+
await screen.findByText(/token-1/);
198+
199+
const deleteButtons = screen.getAllByRole('button', { name: /delete/i });
200+
deleteButtons.forEach((btn) => {
201+
expect(btn).toBeDisabled();
202+
});
203+
});
204+
205+
it('should reload tokens after successful creation', async () => {
206+
const user = userEvent.setup();
207+
const mockCreatedToken = {
208+
scim_token: 'new-token-value',
209+
token_id: 'new-token-id',
210+
};
211+
mockOnCreateScimToken.mockResolvedValue(mockCreatedToken);
212+
mockOnListScimTokens.mockResolvedValueOnce({ scim_tokens: [] }).mockResolvedValueOnce({
213+
scim_tokens: [{ token_id: 'new-token-id', valid_until: undefined }],
214+
});
215+
216+
render(<ProvisioningManageToken {...defaultProps} />);
217+
218+
const generateButton = await screen.findByText('generate_button_label');
219+
await user.click(generateButton);
220+
221+
await waitFor(() => {
222+
expect(mockOnListScimTokens).toHaveBeenCalledTimes(2);
223+
});
224+
});
225+
226+
it('should apply custom styling classes', async () => {
227+
const styling = {
228+
variables: { common: {}, light: {}, dark: {} },
229+
classes: {
230+
'ProvisioningManageToken-root': 'custom-root-class',
231+
'ProvisioningManageToken-card': 'custom-card-class',
232+
},
233+
};
234+
235+
const { container } = render(<ProvisioningManageToken {...defaultProps} styling={styling} />);
236+
237+
await waitFor(() => {
238+
expect(container.querySelector('.custom-root-class')).toBeInTheDocument();
239+
});
240+
});
241+
242+
it('should not render card content when no tokens exist', async () => {
243+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: [] });
244+
render(<ProvisioningManageToken {...defaultProps} />);
245+
246+
await waitFor(() => {
247+
expect(screen.queryByText('token_item.token_prefix')).not.toBeInTheDocument();
248+
});
249+
});
250+
251+
it('should show create modal after generating token', async () => {
252+
const user = userEvent.setup();
253+
const mockCreatedToken = {
254+
scim_token: 'new-token-value',
255+
token_id: 'new-token-id',
256+
};
257+
mockOnCreateScimToken.mockResolvedValue(mockCreatedToken);
258+
mockOnListScimTokens.mockResolvedValue({ scim_tokens: [] });
259+
260+
render(<ProvisioningManageToken {...defaultProps} />);
261+
262+
const generateButton = await screen.findByText('generate_button_label');
263+
await user.click(generateButton);
264+
265+
expect(await screen.findByRole('dialog')).toBeInTheDocument();
266+
});
267+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { render, screen, waitFor } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { describe, it, expect, vi, beforeEach } from 'vitest';
4+
5+
import { SsoProvisioningDeleteModal } from '../sso-provisioning-delete-modal';
6+
7+
describe('SsoProvisioningDeleteModal', () => {
8+
const mockOnOpenChange = vi.fn();
9+
const mockOnConfirm = vi.fn();
10+
11+
const defaultProps = {
12+
open: true,
13+
onOpenChange: mockOnOpenChange,
14+
onConfirm: mockOnConfirm,
15+
isLoading: false,
16+
};
17+
18+
// Mock hooks
19+
vi.mock('../../../../../../hooks/use-translator', () => ({
20+
useTranslator: () => ({
21+
t: (key: string, params?: any) => {
22+
if (key === 'delete_button_label') return 'Delete';
23+
if (key === 'remove_button_label') return 'Remove';
24+
if (key === 'title' && params?.providerName) return `Delete ${params.providerName}`;
25+
return key;
26+
},
27+
}),
28+
}));
29+
30+
beforeEach(() => {
31+
vi.clearAllMocks();
32+
});
33+
34+
it('should render modal with correct title and description', () => {
35+
render(<SsoProvisioningDeleteModal {...defaultProps} />);
36+
37+
expect(screen.getByText('modal.title')).toBeInTheDocument();
38+
expect(screen.getByText('modal.content.description')).toBeInTheDocument();
39+
});
40+
41+
it('should render cancel and delete buttons', () => {
42+
render(<SsoProvisioningDeleteModal {...defaultProps} />);
43+
44+
expect(screen.getByText('modal.actions.cancel_button_label')).toBeInTheDocument();
45+
expect(screen.getByText('modal.actions.delete_button_label')).toBeInTheDocument();
46+
});
47+
48+
it('should call onOpenChange with false when cancel button is clicked', async () => {
49+
const user = userEvent.setup();
50+
render(<SsoProvisioningDeleteModal {...defaultProps} />);
51+
52+
const cancelButton = screen.getByText('modal.actions.cancel_button_label');
53+
54+
await user.click(cancelButton);
55+
56+
await waitFor(() => {
57+
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
58+
});
59+
});
60+
61+
it('should call onConfirm when delete button is clicked', async () => {
62+
const user = userEvent.setup();
63+
render(<SsoProvisioningDeleteModal {...defaultProps} />);
64+
65+
const deleteButton = screen.getByText('modal.actions.delete_button_label');
66+
67+
await user.click(deleteButton);
68+
69+
await waitFor(() => {
70+
expect(mockOnConfirm).toHaveBeenCalled();
71+
});
72+
});
73+
74+
it('should disable buttons when isLoading is true', () => {
75+
render(<SsoProvisioningDeleteModal {...defaultProps} isLoading={true} />);
76+
77+
const cancelButton = screen.getByText('modal.actions.cancel_button_label');
78+
expect(cancelButton).toBeDisabled();
79+
});
80+
81+
it('should not render modal when open is false', () => {
82+
render(<SsoProvisioningDeleteModal {...defaultProps} open={false} />);
83+
expect(screen.queryByText('modal.title')).not.toBeInTheDocument();
84+
});
85+
});

0 commit comments

Comments
 (0)