import { describe, it, expect, beforeEach, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import { createPinia, setActivePinia } from 'pinia'; import { createVuetify } from 'vuetify'; import { createRouter, createMemoryHistory } from 'vue-router'; vi.mock('../../resources/js/api/auth', () => ({ login: vi.fn(), register: vi.fn(), me: vi.fn(), logout: vi.fn(), verifyTwoFactor: vi.fn(), forgotPassword: vi.fn(), resetPassword: vi.fn(), })); vi.mock('../../resources/js/api/client', () => ({ extractRateLimitRetry: vi.fn(() => null), extractValidationErrors: vi.fn(() => null), extractErrorMessage: vi.fn(() => 'Ошибка'), apiClient: {}, ensureCsrfCookie: vi.fn(), })); import * as authApi from '../../resources/js/api/auth'; import { useAuthStore } from '../../resources/js/stores/auth'; import ResetPasswordView from '../../resources/js/views/auth/ResetPasswordView.vue'; const mountReset = async (path = '/reset/abc-token-xyz?email=user@example.ru') => { const pinia = createPinia(); setActivePinia(pinia); const router = createRouter({ history: createMemoryHistory(), routes: [ { path: '/reset/:token', name: 'reset-password', component: ResetPasswordView }, { path: '/login', name: 'login', component: { template: '
stub
' } }, ], }); await router.push(path); await router.isReady(); return mount(ResetPasswordView, { global: { plugins: [pinia, createVuetify(), router] }, }); }; describe('ResetPasswordView.vue', () => { beforeEach(() => { vi.clearAllMocks(); vi.useFakeTimers(); }); it('монтируется и содержит заголовок «Новый пароль»', async () => { const wrapper = await mountReset(); expect(wrapper.text()).toContain('Новый пароль'); }); it('предзаполняет email из query-параметра', async () => { const wrapper = await mountReset('/reset/some-token?email=preset@example.ru'); const emailInput = wrapper.find('input[type="email"]'); expect((emailInput.element as HTMLInputElement).value).toBe('preset@example.ru'); }); it('содержит поля пароля + подтверждения с autocomplete=new-password', async () => { const wrapper = await mountReset(); const passwordInputs = wrapper.findAll('input[type="password"]'); expect(passwordInputs.length).toBe(2); passwordInputs.forEach((input) => { expect(input.attributes('autocomplete')).toBe('new-password'); }); }); it('после успешного submit показывает success-state и скрывает форму', async () => { vi.mocked(authApi.resetPassword).mockResolvedValue({ message: 'Пароль успешно изменён.' }); const wrapper = await mountReset('/reset/valid-token?email=user@example.ru'); await wrapper.findAll('input[type="password"]')[0].setValue('new-strong-pass-1234'); await wrapper.findAll('input[type="password"]')[1].setValue('new-strong-pass-1234'); await wrapper.find('form').trigger('submit.prevent'); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); expect(wrapper.find('[data-testid="reset-success"]').exists()).toBe(true); expect(wrapper.find('form').exists()).toBe(false); expect(authApi.resetPassword).toHaveBeenCalledWith( expect.objectContaining({ token: 'valid-token', email: 'user@example.ru', password: 'new-strong-pass-1234', password_confirmation: 'new-strong-pass-1234', }), ); }); it('при 429 показывает lockout-alert через auth.lockoutSeconds', async () => { const wrapper = await mountReset(); const auth = useAuthStore(); auth.lockoutSeconds = 600; await wrapper.vm.$nextTick(); const alert = wrapper.find('[data-testid="lockout-alert"]'); expect(alert.exists()).toBe(true); expect(alert.text()).toContain('10 мин'); }); });