Files
portal/app/tests/Frontend/deals-api.spec.ts
T
Дмитрий f94552d452 WIP чекпойнт: lead-region/supplier бэкенд + фронт-редизайн + Pint + тесты
92 файла одной пачкой. Исключены чужие зоны: CLAUDE.md, .claude/settings.json, docs/observer/.pii-counters.json.
gitleaks staged: no leaks found. Не верифицировано тестами - сохранение труда в историю.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 05:17:12 +03:00

194 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: [...] } (JsonResource collection)', async () => {
// ProjectController::index() отдаёт response()->json(['data' => ProjectResource::collection(...)]).
vi.mocked(apiClient.get).mockResolvedValue({
data: {
data: [
{ id: 1, name: 'B1_Окна СПб' },
{ id: 2, name: 'B2_Двери' },
],
},
});
const r = await listProjects(1);
expect(apiClient.get).toHaveBeenCalledWith('/api/projects', { params: { tenant_id: 1 } });
expect(Array.isArray(r)).toBe(true);
expect(r).toHaveLength(2);
expect(r[0].name).toBe('B1_Окна СПб');
});
it('listProjects() возвращает [] при ответе без массива (защита от undefined.map)', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: {} });
const r = await listProjects(1);
expect(r).toEqual([]);
});
});