import { describe, it, expect, beforeEach, vi } from 'vitest'; vi.mock('../../resources/js/api/client', () => ({ apiClient: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn(), }, ensureCsrfCookie: vi.fn(), })); import { createDeal, bulkDeleteDeals, bulkRestoreDeals, updateDeal, transitionDeals, exportDeals, exportDealsXlsx, getDeal, listDeals, listManagers, listProjects, } from '../../resources/js/api/deals'; import { apiClient, ensureCsrfCookie } from '../../resources/js/api/client'; const FAKE_DEAL = { id: 1, tenant_id: 1, project_id: 1, project_name: 'Test', phone: '+79161234567', contact_name: 'X', status: 'new', manager_id: null, manager_name: null, manager_initials: null, received_at: null, comment: null, assigned_at: null, }; describe('api/deals', () => { beforeEach(() => vi.clearAllMocks()); it('createDeal() POSTs /api/deals + ensureCsrfCookie + unwraps data.deal', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: { deal: FAKE_DEAL, message: 'ok' } }); const r = await createDeal({ tenant_id: 1, project_name: 'P', phone: '+79161234567' }); expect(ensureCsrfCookie).toHaveBeenCalledOnce(); expect(apiClient.post).toHaveBeenCalledWith('/api/deals', { tenant_id: 1, project_name: 'P', phone: '+79161234567', }); expect(r.id).toBe(1); }); it('bulkDeleteDeals() DELETE /api/deals с data payload', async () => { vi.mocked(apiClient.delete).mockResolvedValue({ data: { deleted: 3, requested: 4 } }); const r = await bulkDeleteDeals({ tenant_id: 1, ids: [1, 2, 3, 4] }); expect(ensureCsrfCookie).toHaveBeenCalledOnce(); expect(apiClient.delete).toHaveBeenCalledWith('/api/deals', { data: { tenant_id: 1, ids: [1, 2, 3, 4] } }); expect(r.deleted).toBe(3); }); it('bulkRestoreDeals() POSTs /api/deals/restore', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: { restored: 2, requested: 2 } }); await bulkRestoreDeals({ tenant_id: 1, ids: [1, 2] }); expect(ensureCsrfCookie).toHaveBeenCalledOnce(); expect(apiClient.post).toHaveBeenCalledWith('/api/deals/restore', { tenant_id: 1, ids: [1, 2] }); }); it('updateDeal() PATCH /api/deals/{id} c partial payload', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: { deal: FAKE_DEAL } }); await updateDeal(42, { tenant_id: 1, comment: 'new' }); expect(ensureCsrfCookie).toHaveBeenCalledOnce(); expect(apiClient.patch).toHaveBeenCalledWith('/api/deals/42', { tenant_id: 1, comment: 'new' }); }); it('transitionDeals() POSTs /api/deals/transition с status', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: { updated: 5, requested: 5, status: 'won' } }); const r = await transitionDeals({ tenant_id: 1, ids: [1, 2, 3, 4, 5], status: 'won' }); expect(apiClient.post).toHaveBeenCalledWith('/api/deals/transition', { tenant_id: 1, ids: [1, 2, 3, 4, 5], status: 'won', }); expect(r.status).toBe('won'); }); it('exportDeals() POSTs /api/deals/export с format=csv + responseType=text', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: 'id,phone\n1,+79..' }); const r = await exportDeals({ tenant_id: 1, ids: [1] }); expect(ensureCsrfCookie).toHaveBeenCalledOnce(); expect(apiClient.post).toHaveBeenCalledWith( '/api/deals/export', { tenant_id: 1, ids: [1], format: 'csv' }, { responseType: 'text' }, ); expect(typeof r).toBe('string'); }); it('exportDealsXlsx() POSTs /api/deals/export с format=xlsx + responseType=blob', async () => { const blob = new Blob(['fake'], { type: 'application/octet-stream' }); vi.mocked(apiClient.post).mockResolvedValue({ data: blob }); const r = await exportDealsXlsx({ tenant_id: 1, ids: [1, 2] }); expect(apiClient.post).toHaveBeenCalledWith( '/api/deals/export', { tenant_id: 1, ids: [1, 2], format: 'xlsx' }, { responseType: 'blob' }, ); expect(r).toBeInstanceOf(Blob); }); it('getDeal() GET /api/deals/{id} с tenant_id param', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: { deal: FAKE_DEAL, events: [] } }); const r = await getDeal(7, 1); expect(apiClient.get).toHaveBeenCalledWith('/api/deals/7', { params: { tenant_id: 1 } }); expect(r.events).toEqual([]); }); it('listDeals() GET /api/deals с маппингом camelCase→snake_case + onlyDeleted flag', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: { deals: [], total: 0, limit: 50, offset: 0 }, }); await listDeals({ tenantId: 1, statusIn: ['new', 'in_progress'], projectId: 2, managerId: 3, search: 'q', limit: 50, offset: 0, onlyDeleted: true, }); expect(apiClient.get).toHaveBeenCalledWith('/api/deals', { params: { tenant_id: 1, status_in: ['new', 'in_progress'], project_id: 2, manager_id: 3, search: 'q', limit: 50, offset: 0, only_deleted: 'true', }, }); }); it('listDeals() без onlyDeleted → only_deleted=undefined', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: { deals: [], total: 0, limit: 50, offset: 0 } }); await listDeals({ tenantId: 1 }); expect(apiClient.get).toHaveBeenCalledWith( '/api/deals', expect.objectContaining({ params: expect.objectContaining({ only_deleted: undefined }) }), ); }); it('listManagers() GET /api/managers + unwraps data.managers', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: { managers: [{ id: 1, email: 'm@x.ru', first_name: 'M', last_name: 'X', name: 'M X', initials: 'MX' }], }, }); const r = await listManagers(1); expect(apiClient.get).toHaveBeenCalledWith('/api/managers', { params: { tenant_id: 1 } }); expect(r).toHaveLength(1); }); it('listProjects() GET /api/projects + unwraps data.projects', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: { projects: [{ id: 1, name: 'P', tag: 'site', type: 'webhook' }] }, }); const r = await listProjects(1); expect(apiClient.get).toHaveBeenCalledWith('/api/projects', { params: { tenant_id: 1 } }); expect(r[0].name).toBe('P'); }); });