import { describe, it, expect, beforeEach } from 'vitest'; import { http, HttpResponse } from 'msw'; import { server } from '../test/server'; import { request, setAccessToken, getAccessToken, setOnUnauthorized } from './client'; const API = '/api/v1'; beforeEach(() => { setAccessToken(null); }); describe('request', () => { it('makes GET request and returns data', async () => { const result = await request<{ status: string; timestamp: string; uptime: number }>( '/api/v1/health', ); expect(result.data.status).toBe('ok'); }); it('supports the single API envelope shape documented by Swagger', async () => { server.use( http.get(`${API}/test-single-envelope`, () => HttpResponse.json({ data: { ok: true }, meta: { fromCache: true, cachedAt: '2026-06-17T00:00:00.000Z' }, }), ), ); const result = await request<{ ok: boolean }>('/api/v1/test-single-envelope'); expect(result).toEqual({ data: { ok: true }, meta: { fromCache: true, cachedAt: '2026-06-17T00:00:00.000Z' }, }); }); it('includes Authorization header when token is set', async () => { setAccessToken('test-token'); let capturedAuth: string | null = null; server.use( http.get(`${API}/test-auth`, ({ request }) => { capturedAuth = request.headers.get('Authorization'); return HttpResponse.json({ data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } }, }); }), ); await request('/api/v1/test-auth'); expect(capturedAuth).toBe('Bearer test-token'); }); it('retries on 401 and succeeds after refresh', async () => { setAccessToken('expired-token'); let attempts = 0; server.use( http.get(`${API}/test-retry`, ({ request }) => { attempts++; const auth = request.headers.get('Authorization'); if (auth === 'Bearer expired-token') { return new HttpResponse(null, { status: 401 }); } return HttpResponse.json({ data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } }, }); }), http.post(`${API}/auth/refresh`, () => HttpResponse.json({ data: { data: { user: { id: 1, email: 'user@test.com', name: null, role: 'user' }, accessToken: 'new-token', }, meta: { fromCache: false, cachedAt: null }, }, }), ), ); const result = await request<{ ok: boolean }>('/api/v1/test-retry'); expect(attempts).toBe(2); expect(result.data).toEqual({ ok: true }); expect(getAccessToken()).toBe('new-token'); }); it('throws on persistent 401 and clears token', async () => { setAccessToken('expired-token'); let unauthorizedCalled = false; setOnUnauthorized(() => { unauthorizedCalled = true; }); server.use( http.get(`${API}/test-fail`, () => new HttpResponse(null, { status: 401 })), http.post(`${API}/auth/refresh`, () => new HttpResponse(null, { status: 401 })), ); await expect(request('/api/v1/test-fail')).rejects.toThrow('Сессия истекла'); expect(getAccessToken()).toBeNull(); expect(unauthorizedCalled).toBe(true); }); it('throws on non-ok response with status text', async () => { server.use( http.get( `${API}/test-error`, () => new HttpResponse('Not found', { status: 404, statusText: 'Not Found' }), ), ); await expect(request('/api/v1/test-error')).rejects.toThrow('Ошибка API: 404'); }); it('sends JSON body for POST requests', async () => { let capturedBody: string | null = null; server.use( http.post(`${API}/test-post`, async ({ request }) => { capturedBody = await request.text(); return HttpResponse.json({ data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } }, }); }), ); await request('/api/v1/test-post', undefined, { method: 'POST', body: { foo: 'bar' } }); expect(capturedBody).toBe(JSON.stringify({ foo: 'bar' })); }); it('does not send auth header when skipAuth is true', async () => { setAccessToken('test-token'); let capturedAuth: string | null = null; server.use( http.get(`${API}/test-skip`, ({ request }) => { capturedAuth = request.headers.get('Authorization'); return HttpResponse.json({ data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } }, }); }), ); await request('/api/v1/test-skip', undefined, { skipAuth: true }); expect(capturedAuth).toBeNull(); }); it('sets query params correctly', async () => { let capturedUrl = ''; server.use( http.get(`${API}/test-params`, ({ request }) => { capturedUrl = request.url; return HttpResponse.json({ data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } }, }); }), ); await request('/api/v1/test-params', { q: 'sber', type: 'share' }); expect(capturedUrl).toContain('q=sber'); expect(capturedUrl).toContain('type=share'); }); });