Skip to content

Commit

Permalink
Add basic tests for admin panel users page (#182)
Browse files Browse the repository at this point in the history
  • Loading branch information
byn9826 authored Oct 14, 2024
1 parent 69ac4e1 commit 74bdf29
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 4 deletions.
211 changes: 211 additions & 0 deletions admin-panel/app/[lang]/users/[authId]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import {
fireEvent, screen,
} from '@testing-library/react'
import {
describe, it, expect, vi, beforeEach, Mock,
} from 'vitest'
import Page from 'app/[lang]/users/[authId]/page'
import { render } from 'vitest.setup'
import {
useDeleteApiV1UsersByAuthIdConsentedAppsAndAppIdMutation,
useDeleteApiV1UsersByAuthIdEmailMfaMutation,
useDeleteApiV1UsersByAuthIdLockedIpsMutation,
useDeleteApiV1UsersByAuthIdMutation,
useDeleteApiV1UsersByAuthIdOtpMfaMutation,
useDeleteApiV1UsersByAuthIdSmsMfaMutation,
useGetApiV1RolesQuery,
useGetApiV1UsersByAuthIdConsentedAppsQuery,
useGetApiV1UsersByAuthIdLockedIpsQuery,
useGetApiV1UsersByAuthIdQuery,
usePostApiV1UsersByAuthIdEmailMfaMutation,
usePostApiV1UsersByAuthIdOtpMfaMutation,
usePostApiV1UsersByAuthIdSmsMfaMutation,
usePostApiV1UsersByAuthIdVerifyEmailMutation,
usePutApiV1UsersByAuthIdMutation,
} from 'services/auth/api'
import { users } from 'tests/userMock'
import { roles } from 'tests/roleMock'

const mockNav = {
authId: '3ed71b1e-fd0c-444b-b653-7e78731d4865',
push: vi.fn(),
}

vi.mock(
'next/navigation',
() => ({
useParams: vi.fn(() => ({ authId: mockNav.authId })),
useRouter: vi.fn(() => ({ push: mockNav.push })),
}),
)

vi.mock(
'services/auth/api',
() => ({
useDeleteApiV1UsersByAuthIdConsentedAppsAndAppIdMutation: vi.fn(),
useDeleteApiV1UsersByAuthIdEmailMfaMutation: vi.fn(),
useDeleteApiV1UsersByAuthIdLockedIpsMutation: vi.fn(),
useDeleteApiV1UsersByAuthIdMutation: vi.fn(),
useDeleteApiV1UsersByAuthIdOtpMfaMutation: vi.fn(),
useDeleteApiV1UsersByAuthIdSmsMfaMutation: vi.fn(),
useGetApiV1RolesQuery: vi.fn(),
useGetApiV1UsersByAuthIdConsentedAppsQuery: vi.fn(),
useGetApiV1UsersByAuthIdLockedIpsQuery: vi.fn(),
useGetApiV1UsersByAuthIdQuery: vi.fn(),
usePostApiV1UsersByAuthIdEmailMfaMutation: vi.fn(),
usePostApiV1UsersByAuthIdOtpMfaMutation: vi.fn(),
usePostApiV1UsersByAuthIdSmsMfaMutation: vi.fn(),
usePostApiV1UsersByAuthIdVerifyEmailMutation: vi.fn(),
usePutApiV1UsersByAuthIdMutation: vi.fn(),
}),
)

vi.mock(
'signals',
() => ({
configSignal: {
value: {
ENABLE_NAMES: true,
SUPPORTED_LOCALES: ['en', 'fr'],
ACCOUNT_LOCKOUT_THRESHOLD: 2,
},
subscribe: () => () => {},
},
userInfoSignal: {
value: { authId: '3ed71b1e-fd0c-444b-b653-7e78731d4865' },
subscribe: () => () => {},
},
errorSignal: {
value: '',
subscribe: () => () => {},
},
}),
)

const mockUpdate = vi.fn()
const mockDelete = vi.fn()
const mockDeleteConsent = vi.fn()
const mockDeleteIps = vi.fn()
const mockResendVerifyEmail = vi.fn()
const mockEnrollEmailMfa = vi.fn()
const mockEnrollOtpMfa = vi.fn()
const mockEnrollSmsMfa = vi.fn()
const mockUnenrollEmailMfa = vi.fn()
const mockUnenrollSmsMfa = vi.fn()
const mockUnenrollOtpMfa = vi.fn()

describe(
'user',
() => {
beforeEach(() => {
(useGetApiV1UsersByAuthIdQuery as Mock).mockReturnValue({ data: { user: users[0] } });
(useGetApiV1RolesQuery as Mock).mockReturnValue({ data: { roles } });
(useGetApiV1UsersByAuthIdConsentedAppsQuery as Mock).mockReturnValue({
data: {
consentedApps: [{
appId: 1, appName: 'test app',
}],
},
});
(useGetApiV1UsersByAuthIdLockedIpsQuery as Mock).mockReturnValue({ data: { lockedIPs: ['1.1.1.1'] } });
(usePutApiV1UsersByAuthIdMutation as Mock).mockReturnValue([mockUpdate, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdMutation as Mock).mockReturnValue([mockDelete, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdConsentedAppsAndAppIdMutation as Mock)
.mockReturnValue([mockDeleteConsent, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdLockedIpsMutation as Mock).mockReturnValue([mockDeleteIps, { isLoading: false }]);
(usePostApiV1UsersByAuthIdVerifyEmailMutation as Mock)
.mockReturnValue([mockResendVerifyEmail, { isLoading: false }]);
(usePostApiV1UsersByAuthIdEmailMfaMutation as Mock).mockReturnValue([mockEnrollEmailMfa, { isLoading: false }]);
(usePostApiV1UsersByAuthIdSmsMfaMutation as Mock).mockReturnValue([mockEnrollSmsMfa, { isLoading: false }]);
(usePostApiV1UsersByAuthIdOtpMfaMutation as Mock).mockReturnValue([mockEnrollOtpMfa, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdEmailMfaMutation as Mock)
.mockReturnValue([mockUnenrollEmailMfa, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdOtpMfaMutation as Mock).mockReturnValue([mockUnenrollOtpMfa, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdSmsMfaMutation as Mock).mockReturnValue([mockUnenrollSmsMfa, { isLoading: false }])
})

it(
'render user',
async () => {
render(<Page />)

const localeOptions = screen.queryAllByTestId('localeOption')
expect(localeOptions.length).toBe(2)

const lockedIpBadges = screen.queryAllByTestId('lockedIpBadge')
expect(lockedIpBadges.length).toBe(1)
expect(lockedIpBadges[0]?.innerHTML).toContain('1.1.1.1')

const roleInputs = screen.queryAllByTestId('roleInput') as HTMLInputElement[]
expect(roleInputs.length).toBe(2)
expect(roleInputs[0]?.checked).toBeFalsy()
expect(roleInputs[1]?.checked).toBeFalsy()

const firstNameInput = screen.queryByTestId('firstNameInput') as HTMLInputElement
expect(firstNameInput?.value).toBe(users[0].firstName)

const lastNameInput = screen.queryByTestId('lastNameInput') as HTMLInputElement
expect(lastNameInput?.value).toBe(users[0].lastName)
},
)

it(
'update user',
async () => {
render(<Page />)

const localeSelect = screen.queryByTestId('localeSelect') as HTMLSelectElement
fireEvent.change(
localeSelect,
{ target: { value: 'fr' } },
)

const roleInputs = screen.queryAllByTestId('roleInput') as HTMLInputElement[]
fireEvent.click(roleInputs[0])

const firstNameInput = screen.queryByTestId('firstNameInput') as HTMLInputElement
fireEvent.change(
firstNameInput,
{ target: { value: 'new firstname' } },
)

const lastNameInput = screen.queryByTestId('lastNameInput') as HTMLInputElement
fireEvent.change(
lastNameInput,
{ target: { value: 'new lastname' } },
)

const saveBtn = screen.queryByTestId('saveButton') as HTMLButtonElement
expect(saveBtn?.disabled).toBeFalsy()
fireEvent.click(saveBtn)

expect(mockUpdate).toHaveBeenLastCalledWith({
authId: '3ed71b1e-fd0c-444b-b653-7e78731d4865',
putUserReq: {
locale: 'fr',
firstName: 'new firstname',
lastName: 'new lastname',
roles: ['super_admin'],
},
})
},
)

it(
'delete user',
async () => {
render(<Page />)

const deleteBtn = screen.queryByTestId('deleteButton') as HTMLButtonElement
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()

fireEvent.click(deleteBtn)
expect(screen.queryByRole('dialog')).toBeInTheDocument()

fireEvent.click(screen.queryByTestId('confirmButton') as HTMLButtonElement)

expect(mockDelete).toHaveBeenLastCalledWith({ authId: '3ed71b1e-fd0c-444b-b653-7e78731d4865' })
},
)
},
)
6 changes: 6 additions & 0 deletions admin-panel/app/[lang]/users/[authId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -389,13 +389,15 @@ const Page = () => {
<Table.Cell>
{configs.SUPPORTED_LOCALES.length > 1 && (
<Select
data-testid='localeSelect'
value={locale}
onChange={(e) => setLocale(e.target.value)}
>
<option disabled></option>
{configs.SUPPORTED_LOCALES.map((locale: string) => (
<option
key={locale}
data-testid='localeOption'
value={locale}>{locale.toUpperCase()}
</option>
))}
Expand Down Expand Up @@ -428,6 +430,7 @@ const Page = () => {
<div className='flex items-center gap-6'>
{lockedIPs?.map((ip) => (
<Badge
data-testid='lockedIpBadge'
color='gray'
key={ip}>{ip || t('users.noIP')}
</Badge>
Expand All @@ -453,6 +456,7 @@ const Page = () => {
className='flex items-center gap-2'>
<Checkbox
id={`role-${role.id}`}
data-testid='roleInput'
onChange={() => handleToggleUserRole(role.name)}
checked={userRoles?.includes(role.name)}
/>
Expand All @@ -472,6 +476,7 @@ const Page = () => {
<Table.Cell>{t('users.firstName')}</Table.Cell>
<Table.Cell>
<TextInput
data-testid='firstNameInput'
onChange={(e) => setFirstName(e.target.value)}
value={firstName ?? ''}
/>
Expand All @@ -481,6 +486,7 @@ const Page = () => {
<Table.Cell>{t('users.lastName')}</Table.Cell>
<Table.Cell>
<TextInput
data-testid='lastNameInput'
onChange={(e) => setLastName(e.target.value)}
value={lastName ?? ''}
/>
Expand Down
65 changes: 65 additions & 0 deletions admin-panel/app/[lang]/users/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
describe, it, expect, vi, beforeEach, Mock,
} from 'vitest'
import { screen } from '@testing-library/react'
import { render } from '../../../vitest.setup'
import Page from 'app/[lang]/users/page'
import { useGetApiV1UsersQuery } from 'services/auth/api'
import { users } from 'tests/userMock'

vi.mock(
'services/auth/api',
() => ({ useGetApiV1UsersQuery: vi.fn() }),
)

vi.mock(
'signals',
() => ({
configSignal: {
value: { ENABLE_NAMES: true },
subscribe: () => () => {},
},
userInfoSignal: {
value: { authId: '3ed71b1e-fd0c-444b-b653-7e78731d4865' },
subscribe: () => () => {},
},
}),
)

describe(
'Page Component',
() => {
beforeEach(() => {
(useGetApiV1UsersQuery as Mock).mockReturnValue({
data: {
users, count: 3,
},
})
})

it(
'renders users',
async () => {
render(<Page />)

const rows = screen.queryAllByTestId('userRow')
expect(rows.length).toBe(3)

rows.forEach((
row, index,
) => {
expect(row.querySelectorAll('td')[0]?.innerHTML).toContain(users[index].authId)
if (index === 0) {
expect(row.querySelectorAll('td')[0]?.innerHTML).toContain('users.you')
}

expect(row.querySelectorAll('td')[1]?.innerHTML).toContain(users[index].email)
expect(row.querySelectorAll('td')[2]?.innerHTML).toContain(users[index].isActive ? 'common.active' : 'common.disabled')
expect(row.querySelectorAll('td')[3]?.innerHTML).toContain(`${users[index].firstName} ${users[index].lastName}`)
const editLink = row.querySelectorAll('td')[4]?.getElementsByTagName('a')
expect(editLink[0].getAttribute('href')).toBe(`/en/users/${users[index].authId}`)
})
},
)
},
)
7 changes: 5 additions & 2 deletions admin-panel/app/[lang]/users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ const Page = () => {
</Table.Head>
<Table.Body className='divide-y md:hidden'>
{users.map((user) => (
<Table.Row key={user.id}>
<Table.Row
key={user.id}>
<Table.Cell>
<section className='flex items-center justify-between'>
<section className='flex flex-col gap-2'>
Expand All @@ -103,7 +104,9 @@ const Page = () => {
</Table.Body>
<Table.Body className='divide-y max-md:hidden'>
{users.map((user) => (
<Table.Row key={user.id}>
<Table.Row
key={user.id}
data-testid='userRow'>
<Table.Cell>
<div className='flex items-center gap-2'>
{user.authId}
Expand Down
3 changes: 1 addition & 2 deletions admin-panel/tests/scopeMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,4 @@ export const scopes = [{
createdAt: '2024-08-07 20:45:27',
updatedAt: '2024-08-07 20:45:27',
deletedAt: null,
},
]
}]
Loading

0 comments on commit 74bdf29

Please sign in to comment.