fix(web): cablear ResetPasswordModal en UserDetailPage [UDT-008]
El componente ResetPasswordModal estaba implementado pero nunca montado en una pagina. Ahora se renderiza en UserDetailPage, oculto cuando el target es el usuario logueado (evita hit de cannot-self-reset en backend).
This commit is contained in:
@@ -4,11 +4,14 @@ import { Badge } from '@/components/ui/badge'
|
|||||||
import { useUser } from '../hooks/useUser'
|
import { useUser } from '../hooks/useUser'
|
||||||
import { useDeactivateUser } from '../hooks/useDeactivateUser'
|
import { useDeactivateUser } from '../hooks/useDeactivateUser'
|
||||||
import { useReactivateUser } from '../hooks/useReactivateUser'
|
import { useReactivateUser } from '../hooks/useReactivateUser'
|
||||||
|
import { ResetPasswordModal } from '../components/ResetPasswordModal'
|
||||||
|
import { useAuthStore } from '@/stores/authStore'
|
||||||
|
|
||||||
export function UserDetailPage() {
|
export function UserDetailPage() {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
const userId = Number(id)
|
const userId = Number(id)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const loggedUserId = useAuthStore((s) => s.user?.id)
|
||||||
|
|
||||||
const { data: user, isLoading } = useUser(userId)
|
const { data: user, isLoading } = useUser(userId)
|
||||||
const { mutate: deactivate, isPending: deactivating } = useDeactivateUser()
|
const { mutate: deactivate, isPending: deactivating } = useDeactivateUser()
|
||||||
@@ -90,6 +93,8 @@ export function UserDetailPage() {
|
|||||||
{reactivating ? 'Reactivando...' : 'Reactivar'}
|
{reactivating ? 'Reactivando...' : 'Reactivar'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{loggedUserId !== userId && <ResetPasswordModal userId={userId} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
78
src/web/src/tests/features/users/UserDetailPage.test.tsx
Normal file
78
src/web/src/tests/features/users/UserDetailPage.test.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react'
|
||||||
|
import { http, HttpResponse } from 'msw'
|
||||||
|
import { setupServer } from 'msw/node'
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
import { MemoryRouter, Routes, Route } from 'react-router-dom'
|
||||||
|
import { UserDetailPage } from '../../../features/users/pages/UserDetailPage'
|
||||||
|
import { useAuthStore } from '../../../stores/authStore'
|
||||||
|
|
||||||
|
const API_URL = 'http://localhost:5000'
|
||||||
|
|
||||||
|
const adminUser = {
|
||||||
|
id: 1, username: 'admin', nombre: 'Admin', rol: 'admin',
|
||||||
|
permisos: ['administracion:usuarios:gestionar'],
|
||||||
|
mustChangePassword: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = {
|
||||||
|
id: 5,
|
||||||
|
username: 'cajero1',
|
||||||
|
nombre: 'Juan',
|
||||||
|
apellido: 'Perez',
|
||||||
|
email: 'juan@test.com',
|
||||||
|
rol: 'cajero',
|
||||||
|
activo: true,
|
||||||
|
permisosJson: '[]',
|
||||||
|
fechaModificacion: null,
|
||||||
|
ultimoLogin: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = setupServer()
|
||||||
|
|
||||||
|
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
|
||||||
|
afterEach(() => {
|
||||||
|
server.resetHandlers()
|
||||||
|
useAuthStore.getState().clearAuth()
|
||||||
|
})
|
||||||
|
afterAll(() => server.close())
|
||||||
|
|
||||||
|
function renderDetail(userId: number) {
|
||||||
|
useAuthStore.setState({ user: adminUser })
|
||||||
|
const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
|
||||||
|
return render(
|
||||||
|
<QueryClientProvider client={qc}>
|
||||||
|
<MemoryRouter initialEntries={[`/usuarios/${userId}`]}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/usuarios/:id" element={<UserDetailPage />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
</QueryClientProvider>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('UserDetailPage — reset password wiring', () => {
|
||||||
|
it('shows "Resetear contraseña" button when viewing another user', async () => {
|
||||||
|
server.use(
|
||||||
|
http.get(`${API_URL}/api/v1/users/5`, () => HttpResponse.json(target)),
|
||||||
|
)
|
||||||
|
|
||||||
|
renderDetail(5)
|
||||||
|
|
||||||
|
await waitFor(() => expect(screen.getByText('Juan Perez')).toBeInTheDocument())
|
||||||
|
expect(screen.getByRole('button', { name: /resetear contraseña/i })).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides "Resetear contraseña" button when viewing own profile (prevent cannot-self-reset)', async () => {
|
||||||
|
server.use(
|
||||||
|
http.get(`${API_URL}/api/v1/users/1`, () =>
|
||||||
|
HttpResponse.json({ ...target, id: 1, username: 'admin', nombre: 'Admin', apellido: 'Root', rol: 'admin' }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
renderDetail(1)
|
||||||
|
|
||||||
|
await waitFor(() => expect(screen.getByText('Admin Root')).toBeInTheDocument())
|
||||||
|
expect(screen.queryByRole('button', { name: /resetear contraseña/i })).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user