95f5f94a6b
User chose (A) api/* unit tests first (highest ROI per blocked.md). 5 new
spec files covering auth/deals/notifications/reminders/reports api modules.
- auth-api.spec.ts (13 tests): login/register/me/logout/verifyTwoFactor/
useRecoveryCode/twoFactorInit/Confirm/Disable/RegenerateRecoveryCodes/
forgotPassword/resetPassword/updateNotificationPreferences
- deals-api.spec.ts (12 tests): createDeal/bulkDelete/bulkRestore/update/
transition/exportCSV/exportXLSX/getDeal/listDeals×2/listManagers/
listProjects
- notifications-api.spec.ts (6 tests): listNotifications×3 (unreadOnly
variants)/markRead/markAllRead/delete
- reminders-api.spec.ts (6 tests): listReminders×2/create/update/complete/
delete
- reports-api.spec.ts (6 tests): listReportJobs×2/create/retry/cancel/delete
Approach: vi.mock('../../resources/js/api/client') replaces apiClient with
{get,post,patch,delete} mocks + ensureCsrfCookie mock. Each test verifies:
(1) correct HTTP method, (2) correct URL, (3) correct params/body
(camelCase→snake_case mapping for query params), (4) data unwrap from
wrapper objects ({user}/{deal}/{job}/{reminder}/{managers}/{projects}),
(5) ensureCsrfCookie called for mutating endpoints.
Vitest delta: 614 → 657 passed (+43 / 0 failed); 79 → 84 files (+5).
3 skipped unchanged. Q.DEFER.003 sub-B (security cards) + sub-C (router
guards) remain deferred — sub-A api/* was highest ROI per blocked.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
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 {
|
||
listReminders,
|
||
createReminder,
|
||
updateReminder,
|
||
completeReminder,
|
||
deleteReminder,
|
||
} from '../../resources/js/api/reminders';
|
||
import { apiClient, ensureCsrfCookie } from '../../resources/js/api/client';
|
||
|
||
const FAKE_REMINDER = {
|
||
id: 1,
|
||
deal_id: 10,
|
||
text: 'remind me',
|
||
remind_at: '2026-05-13T09:00:00Z',
|
||
completed_at: null,
|
||
is_sent: false,
|
||
sent_at: null,
|
||
created_at: '2026-05-12T20:00:00Z',
|
||
created_by: 1,
|
||
assignee_id: null,
|
||
creator_name: 'Demo',
|
||
};
|
||
|
||
describe('api/reminders', () => {
|
||
beforeEach(() => vi.clearAllMocks());
|
||
|
||
it('listReminders() без params → все params undefined', async () => {
|
||
vi.mocked(apiClient.get).mockResolvedValue({
|
||
data: { items: [], counts: { active: 0, today: 0, upcoming: 0, overdue: 0 } },
|
||
});
|
||
await listReminders();
|
||
expect(apiClient.get).toHaveBeenCalledWith('/api/reminders', {
|
||
params: { filter: undefined, deal_id: undefined, limit: undefined },
|
||
});
|
||
});
|
||
|
||
it('listReminders({filter:"overdue", dealId, limit}) → snake_case params', async () => {
|
||
vi.mocked(apiClient.get).mockResolvedValue({
|
||
data: { items: [], counts: { active: 0, today: 0, upcoming: 0, overdue: 0 } },
|
||
});
|
||
await listReminders({ filter: 'overdue', dealId: 42, limit: 25 });
|
||
expect(apiClient.get).toHaveBeenCalledWith('/api/reminders', {
|
||
params: { filter: 'overdue', deal_id: 42, limit: 25 },
|
||
});
|
||
});
|
||
|
||
it('createReminder() POSTs /api/reminders + unwraps data.reminder', async () => {
|
||
vi.mocked(apiClient.post).mockResolvedValue({ data: { reminder: FAKE_REMINDER } });
|
||
const r = await createReminder({ deal_id: 10, remind_at: '2026-05-13T09:00:00Z', text: 'hi' });
|
||
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
|
||
expect(apiClient.post).toHaveBeenCalledWith('/api/reminders', {
|
||
deal_id: 10,
|
||
remind_at: '2026-05-13T09:00:00Z',
|
||
text: 'hi',
|
||
});
|
||
expect(r.id).toBe(1);
|
||
});
|
||
|
||
it('updateReminder() PATCH /api/reminders/{id} с partial payload', async () => {
|
||
vi.mocked(apiClient.patch).mockResolvedValue({ data: { reminder: FAKE_REMINDER } });
|
||
await updateReminder(1, { text: 'updated', assignee_id: 5 });
|
||
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
|
||
expect(apiClient.patch).toHaveBeenCalledWith('/api/reminders/1', { text: 'updated', assignee_id: 5 });
|
||
});
|
||
|
||
it('completeReminder() POSTs /api/reminders/{id}/complete', async () => {
|
||
vi.mocked(apiClient.post).mockResolvedValue({ data: { reminder: FAKE_REMINDER } });
|
||
await completeReminder(3);
|
||
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
|
||
expect(apiClient.post).toHaveBeenCalledWith('/api/reminders/3/complete');
|
||
});
|
||
|
||
it('deleteReminder() DELETE /api/reminders/{id}', async () => {
|
||
vi.mocked(apiClient.delete).mockResolvedValue({ data: undefined });
|
||
await deleteReminder(7);
|
||
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
|
||
expect(apiClient.delete).toHaveBeenCalledWith('/api/reminders/7');
|
||
});
|
||
});
|