154 lines
5.1 KiB
TypeScript
154 lines
5.1 KiB
TypeScript
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');
|
|
});
|
|
});
|