e280edd431
4 files reformatted (import list expansion, line-length wrapping). Vitest 88/683+3sk green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
127 lines
4.7 KiB
TypeScript
127 lines
4.7 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { mount, flushPromises } from '@vue/test-utils';
|
|
import { createPinia, setActivePinia } from 'pinia';
|
|
import { createVuetify } from 'vuetify';
|
|
|
|
vi.mock('../../resources/js/api/auth', () => ({
|
|
twoFactorRegenerateRecoveryCodes: vi.fn(),
|
|
}));
|
|
|
|
import RecoveryCodesCard from '../../resources/js/components/settings/security/RecoveryCodesCard.vue';
|
|
import * as authApi from '../../resources/js/api/auth';
|
|
import { useAuthStore } from '../../resources/js/stores/auth';
|
|
|
|
const vuetify = createVuetify();
|
|
|
|
describe('RecoveryCodesCard (Q.DEFER.003 sub-B)', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia());
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
const factory = (totpEnabled: boolean) => {
|
|
// Set auth state BEFORE mount — иначе computed has2fa зафиксируется false
|
|
// на первом render и кнопка не появится reactive'но (Pinia store reactivity quirk).
|
|
const auth = useAuthStore();
|
|
auth.user = {
|
|
id: 1,
|
|
email: 'test@demo.local',
|
|
first_name: 'Test',
|
|
last_name: 'User',
|
|
tenant_id: 1,
|
|
totp_enabled: totpEnabled,
|
|
last_login_at: null,
|
|
} as unknown as ReturnType<typeof useAuthStore>['user'];
|
|
const wrapper = mount(RecoveryCodesCard, {
|
|
global: { plugins: [vuetify], stubs: { VDialog: true } },
|
|
});
|
|
return wrapper;
|
|
};
|
|
|
|
it('does NOT render button when 2FA disabled (auth.user.totp_enabled=false)', () => {
|
|
const wrapper = factory(false);
|
|
expect(wrapper.find('[data-testid="regen-codes-btn"]').exists()).toBe(false);
|
|
});
|
|
|
|
it('renders button when 2FA enabled', async () => {
|
|
const wrapper = factory(true);
|
|
await flushPromises();
|
|
const btn = wrapper.find('[data-testid="regen-codes-btn"]');
|
|
expect(btn.exists()).toBe(true);
|
|
expect(btn.text()).toContain('Перегенерировать резервные коды');
|
|
});
|
|
|
|
it('clicking regen-codes-btn opens dialog (sets regenOpen)', async () => {
|
|
const wrapper = factory(true);
|
|
await flushPromises();
|
|
await wrapper.find('[data-testid="regen-codes-btn"]').trigger('click');
|
|
// VDialog is stubbed — check internal state via component instance
|
|
expect((wrapper.vm as unknown as { regenOpen: boolean }).regenOpen).toBe(true);
|
|
});
|
|
|
|
it('confirmRegen() with valid password fetches 8 codes and clears password', async () => {
|
|
const newCodes = [
|
|
'AAAA-1111',
|
|
'BBBB-2222',
|
|
'CCCC-3333',
|
|
'DDDD-4444',
|
|
'EEEE-5555',
|
|
'FFFF-6666',
|
|
'GGGG-7777',
|
|
'HHHH-8888',
|
|
];
|
|
(authApi.twoFactorRegenerateRecoveryCodes as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
recovery_codes: newCodes,
|
|
});
|
|
const wrapper = factory(true);
|
|
await flushPromises();
|
|
const vm = wrapper.vm as unknown as {
|
|
regenOpen: boolean;
|
|
regenPassword: string;
|
|
regenCodes: string[];
|
|
regenError: string;
|
|
confirmRegen: () => Promise<void>;
|
|
};
|
|
vm.regenOpen = true;
|
|
vm.regenPassword = 'correctPass';
|
|
await vm.confirmRegen();
|
|
await flushPromises();
|
|
expect(vm.regenCodes).toEqual(newCodes);
|
|
expect(vm.regenPassword).toBe('');
|
|
expect(vm.regenError).toBe('');
|
|
expect(authApi.twoFactorRegenerateRecoveryCodes).toHaveBeenCalledWith('correctPass');
|
|
});
|
|
|
|
it('confirmRegen() with invalid password sets error and keeps regenCodes empty', async () => {
|
|
(authApi.twoFactorRegenerateRecoveryCodes as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('401'));
|
|
const wrapper = factory(true);
|
|
await flushPromises();
|
|
const vm = wrapper.vm as unknown as {
|
|
regenPassword: string;
|
|
regenCodes: string[];
|
|
regenError: string;
|
|
confirmRegen: () => Promise<void>;
|
|
};
|
|
vm.regenPassword = 'wrongPass';
|
|
await vm.confirmRegen();
|
|
await flushPromises();
|
|
expect(vm.regenError).toBe('Неверный пароль.');
|
|
expect(vm.regenCodes).toEqual([]);
|
|
});
|
|
|
|
it('closeRegen() clears codes and closes dialog', async () => {
|
|
const wrapper = factory(true);
|
|
await flushPromises();
|
|
const vm = wrapper.vm as unknown as {
|
|
regenOpen: boolean;
|
|
regenCodes: string[];
|
|
closeRegen: () => void;
|
|
};
|
|
vm.regenOpen = true;
|
|
vm.regenCodes = ['AAAA-1111'];
|
|
vm.closeRegen();
|
|
expect(vm.regenOpen).toBe(false);
|
|
expect(vm.regenCodes).toEqual([]);
|
|
});
|
|
});
|