From 851fed8692d9ea56a7db13e6b444f75c21705552 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Wed, 15 Apr 2026 20:54:25 -0300 Subject: [PATCH] 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). --- .../features/users/pages/UserDetailPage.tsx | 5 ++ .../features/users/UserDetailPage.test.tsx | 78 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 src/web/src/tests/features/users/UserDetailPage.test.tsx diff --git a/src/web/src/features/users/pages/UserDetailPage.tsx b/src/web/src/features/users/pages/UserDetailPage.tsx index 2e2cbff..3c48b2f 100644 --- a/src/web/src/features/users/pages/UserDetailPage.tsx +++ b/src/web/src/features/users/pages/UserDetailPage.tsx @@ -4,11 +4,14 @@ import { Badge } from '@/components/ui/badge' import { useUser } from '../hooks/useUser' import { useDeactivateUser } from '../hooks/useDeactivateUser' import { useReactivateUser } from '../hooks/useReactivateUser' +import { ResetPasswordModal } from '../components/ResetPasswordModal' +import { useAuthStore } from '@/stores/authStore' export function UserDetailPage() { const { id } = useParams<{ id: string }>() const userId = Number(id) const navigate = useNavigate() + const loggedUserId = useAuthStore((s) => s.user?.id) const { data: user, isLoading } = useUser(userId) const { mutate: deactivate, isPending: deactivating } = useDeactivateUser() @@ -90,6 +93,8 @@ export function UserDetailPage() { {reactivating ? 'Reactivando...' : 'Reactivar'} )} + + {loggedUserId !== userId && } ) diff --git a/src/web/src/tests/features/users/UserDetailPage.test.tsx b/src/web/src/tests/features/users/UserDetailPage.test.tsx new file mode 100644 index 0000000..4936b8c --- /dev/null +++ b/src/web/src/tests/features/users/UserDetailPage.test.tsx @@ -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( + + + + } /> + + + , + ) +} + +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() + }) +})