import { describe, it, expect, beforeEach, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; vi.mock('../../resources/js/api/apiKeys', () => ({ listApiKeys: vi.fn(), regenerateApiKey: vi.fn(), })); vi.mock('../../resources/js/api/webhooks', () => ({ getWebhookSettings: vi.fn(), saveWebhookSettings: vi.fn(), testWebhook: vi.fn(), })); vi.mock('../../resources/js/api/client', () => ({ apiClient: {}, ensureCsrfCookie: vi.fn(), extractValidationErrors: vi.fn(() => null), extractErrorMessage: vi.fn((_e, fallback) => fallback ?? 'Произошла ошибка.'), extractRateLimitRetry: vi.fn(() => null), })); import * as apiKeysApi from '../../resources/js/api/apiKeys'; import * as webhooksApi from '../../resources/js/api/webhooks'; import ApiTab from '../../resources/js/views/settings/ApiTab.vue'; const vuetify = createVuetify(); const flush = () => new Promise((r) => setTimeout(r, 30)); const mountTab = () => mount(ApiTab, { global: { plugins: [vuetify] } }); describe('ApiTab.vue', () => { beforeEach(() => { vi.clearAllMocks(); (apiKeysApi.listApiKeys as ReturnType).mockResolvedValue([]); (webhooksApi.getWebhookSettings as ReturnType).mockResolvedValue(null); Object.assign(navigator, { clipboard: { writeText: vi.fn().mockResolvedValue(undefined) } }); }); it('загружает и показывает префикс API-ключа', async () => { (apiKeysApi.listApiKeys as ReturnType).mockResolvedValue([ { id: 1, name: 'API-ключ', key_prefix: 'lpkapi_abc', last_used_at: null, expires_at: null, created_at: null, }, ]); const wrapper = mountTab(); await flush(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; expect(vm.apiKeyExists).toBe(true); expect(vm.apiTokenDisplay).toBe('lpkapi_abc'); }); it('copyToken() пишет в буфер и открывает toast', async () => { (apiKeysApi.listApiKeys as ReturnType).mockResolvedValue([ { id: 1, name: 'API-ключ', key_prefix: 'lpkapi_abc', last_used_at: null, expires_at: null, created_at: null, }, ]); const wrapper = mountTab(); await flush(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; await vm.copyToken(); expect(navigator.clipboard.writeText).toHaveBeenCalledWith('lpkapi_abc'); expect(vm.toastOpen).toBe(true); }); it('confirmRegenerate() показывает полный новый ключ один раз', async () => { (apiKeysApi.regenerateApiKey as ReturnType).mockResolvedValue({ id: 2, name: 'API-ключ', key: 'lpkapi_FULLNEWKEYVALUE0000000000', key_prefix: 'lpkapi_FUL', }); const wrapper = mountTab(); await flush(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; await vm.confirmRegenerate(); expect(apiKeysApi.regenerateApiKey).toHaveBeenCalled(); expect(vm.apiTokenDisplay).toBe('lpkapi_FULLNEWKEYVALUE0000000000'); expect(vm.fullKeyShown).toBe(true); }); it('загружает настройки webhook', async () => { (webhooksApi.getWebhookSettings as ReturnType).mockResolvedValue({ target_url: 'https://crm.example.ru/hook', secret_prefix: 'whsec_abc', events: ['deal.created'], is_active: true, }); const wrapper = mountTab(); await flush(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; expect(vm.webhookUrl).toBe('https://crm.example.ru/hook'); expect(vm.secretDisplay).toBe('whsec_abc'); }); it('saveWebhook() вызывает saveWebhookSettings и показывает secret один раз', async () => { (webhooksApi.saveWebhookSettings as ReturnType).mockResolvedValue({ target_url: 'https://crm.example.ru/hook', secret_prefix: 'whsec_new', events: ['deal.created'], is_active: true, secret: 'whsec_FULLSECRETVALUE0000', }); const wrapper = mountTab(); await flush(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; vm.webhookUrl = 'https://crm.example.ru/hook'; await vm.saveWebhook(); expect(webhooksApi.saveWebhookSettings).toHaveBeenCalledWith({ target_url: 'https://crm.example.ru/hook' }); expect(vm.secretDisplay).toBe('whsec_FULLSECRETVALUE0000'); expect(vm.fullSecretShown).toBe(true); expect(vm.webhookSuccess).toBeTruthy(); }); it('runWebhookTest() вызывает testWebhook и открывает toast с результатом', async () => { (webhooksApi.testWebhook as ReturnType).mockResolvedValue({ ok: true, status: 200, message: 'Тестовый запрос доставлен (HTTP 200).', }); const wrapper = mountTab(); await flush(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; await vm.runWebhookTest(); expect(webhooksApi.testWebhook).toHaveBeenCalled(); expect(vm.toastOpen).toBe(true); expect(vm.toastText).toContain('HTTP 200'); }); it('loadApiKey() выставляет apiKeyError при reject', async () => { (apiKeysApi.listApiKeys as ReturnType).mockRejectedValue(new Error('boom')); const wrapper = mountTab(); await flush(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; expect(vm.apiKeyError).toBeTruthy(); }); it('loadWebhook() выставляет webhookError при reject', async () => { (webhooksApi.getWebhookSettings as ReturnType).mockRejectedValue(new Error('boom')); const wrapper = mountTab(); await flush(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const vm = wrapper.vm as any; expect(vm.webhookError).toBeTruthy(); }); });