Some checks failed
95 tests across 22 files covering all frontend modules: - API layer: client, auth - Components: BondDetails, Layout, PriceChart, ProtectedRoute, SearchBar, StockDetails - Context: AuthContext - Hooks: useAuth, useBond, useBondCandles, useSearch, useStock, useStockCandles, useStockDividends - Pages: BondPage, HomePage, LoginPage, ProfilePage, RegisterPage, StockPage Infrastructure: - vitest + @testing-library/react + MSW v2 with 13 API handlers - Co-located test files alongside source files - Test utilities: setup, server, factories, test-utils - BrowserRouter future flags for MemoryRouter test compatibility - Root test:frontend script for workspace-wide execution
191 lines
4.6 KiB
TypeScript
191 lines
4.6 KiB
TypeScript
import { http, HttpResponse } from 'msw';
|
||
import type { ShareResponse, BondResponse, CandleItem, SearchResultItem } from '../api/responses';
|
||
|
||
const API = '/api/v1';
|
||
|
||
const mockShare: ShareResponse = {
|
||
secid: 'SBER',
|
||
isin: 'RU0009029540',
|
||
name: 'Сбер Банк',
|
||
shortName: 'Сбер',
|
||
latName: 'Sberbank',
|
||
listLevel: 1,
|
||
issueSize: 21586900000,
|
||
faceValue: 3,
|
||
faceUnit: 'RUB',
|
||
type: 'common_share',
|
||
marketData: {
|
||
price: 289.5,
|
||
change: 2.5,
|
||
changePercent: 0.87,
|
||
open: 287,
|
||
high: 291,
|
||
low: 286.5,
|
||
volume: 15000000,
|
||
value: 4350000000,
|
||
issueCapitalization: 6250000000000,
|
||
updatedAt: '2024-01-15T10:00:00Z',
|
||
},
|
||
};
|
||
|
||
const mockBond: BondResponse = {
|
||
secid: 'SU26238RMFS5',
|
||
isin: 'RU000A101XU7',
|
||
name: 'ОФЗ 26238',
|
||
shortName: 'ОФЗ 26238',
|
||
latName: null,
|
||
listLevel: 1,
|
||
issueSize: 500000000,
|
||
faceValue: 1000,
|
||
faceUnit: 'RUB',
|
||
matDate: '2027-05-15',
|
||
couponValue: 36.9,
|
||
couponPercent: 7.5,
|
||
couponPeriod: 182,
|
||
nextCoupon: '2024-07-15',
|
||
accruedInt: 8.45,
|
||
bondType: 'ОФЗ',
|
||
bondSubType: 'ОФЗ-ПД',
|
||
offerDate: null,
|
||
buybackDate: null,
|
||
marketData: {
|
||
price: 98.5,
|
||
yieldToMaturity: 8.2,
|
||
duration: 3.5,
|
||
accruedInt: 8.45,
|
||
couponValue: 36.9,
|
||
couponPercent: 7.5,
|
||
nextCouponDate: '2024-07-15',
|
||
open: 98.0,
|
||
high: 99.0,
|
||
low: 97.5,
|
||
volume: 1000000,
|
||
updatedAt: '2024-01-15T10:00:00Z',
|
||
},
|
||
};
|
||
|
||
const mockCandles: CandleItem[] = [
|
||
{
|
||
open: 280,
|
||
high: 290,
|
||
low: 278,
|
||
close: 289.5,
|
||
volume: 1000000,
|
||
value: 280000000,
|
||
begin: '2024-01-15T10:00:00Z',
|
||
end: '2024-01-15T18:00:00Z',
|
||
},
|
||
{
|
||
open: 289,
|
||
high: 292,
|
||
low: 285,
|
||
close: 288,
|
||
volume: 800000,
|
||
value: 231200000,
|
||
begin: '2024-01-16T10:00:00Z',
|
||
end: '2024-01-16T18:00:00Z',
|
||
},
|
||
];
|
||
|
||
const mockDividends = [
|
||
{ registryCloseDate: '2024-07-10', value: 35.0, currency: 'RUB' },
|
||
{ registryCloseDate: '2023-10-05', value: 30.0, currency: 'RUB' },
|
||
];
|
||
|
||
const mockSearchResults: SearchResultItem[] = [
|
||
{
|
||
secid: 'SBER',
|
||
isin: 'RU0009029540',
|
||
shortName: 'Сбер',
|
||
type: 'share',
|
||
listLevel: 1,
|
||
currency: 'RUB',
|
||
price: 289.5,
|
||
},
|
||
{
|
||
secid: 'VTBR',
|
||
isin: 'RU000A0JP5V6',
|
||
shortName: 'ВТБ',
|
||
type: 'share',
|
||
listLevel: 1,
|
||
currency: 'RUB',
|
||
price: 0.0234,
|
||
},
|
||
];
|
||
|
||
const userResponse = {
|
||
id: 1,
|
||
email: 'user@test.com',
|
||
name: 'Test User',
|
||
role: 'user',
|
||
};
|
||
|
||
const authResponse = {
|
||
user: userResponse,
|
||
accessToken: 'mock-access-token',
|
||
};
|
||
|
||
const envelope = (data: unknown) => ({
|
||
data: { data, meta: { fromCache: false, cachedAt: null } },
|
||
});
|
||
|
||
export const handlers = [
|
||
http.get(`${API}/securities/search`, ({ request }) => {
|
||
const url = new URL(request.url);
|
||
const q = url.searchParams.get('q') || '';
|
||
if (q.length < 2) {
|
||
return HttpResponse.json(envelope([]));
|
||
}
|
||
const filtered = mockSearchResults.filter(
|
||
(r) =>
|
||
r.secid.toLowerCase().includes(q.toLowerCase()) ||
|
||
r.shortName.toLowerCase().includes(q.toLowerCase()),
|
||
);
|
||
return HttpResponse.json(envelope(filtered));
|
||
}),
|
||
|
||
http.get(`${API}/securities/shares/:secid`, ({ params }) => {
|
||
const { secid } = params;
|
||
if (secid === 'NOTFOUND') return new HttpResponse(null, { status: 404 });
|
||
return HttpResponse.json(envelope({ ...mockShare, secid } as ShareResponse));
|
||
}),
|
||
|
||
http.get(`${API}/securities/shares/:secid/candles`, () =>
|
||
HttpResponse.json(envelope(mockCandles)),
|
||
),
|
||
|
||
http.get(`${API}/securities/shares/:secid/dividends`, () =>
|
||
HttpResponse.json(envelope(mockDividends)),
|
||
),
|
||
|
||
http.get(`${API}/securities/bonds/:secid`, ({ params }) => {
|
||
const { secid } = params;
|
||
if (secid === 'NOTFOUND') return new HttpResponse(null, { status: 404 });
|
||
return HttpResponse.json(envelope({ ...mockBond, secid } as BondResponse));
|
||
}),
|
||
|
||
http.get(`${API}/securities/bonds/:secid/candles`, () =>
|
||
HttpResponse.json(envelope(mockCandles)),
|
||
),
|
||
|
||
http.get(`${API}/auth/me`, () => HttpResponse.json(envelope(userResponse))),
|
||
|
||
http.post(`${API}/auth/login`, () => HttpResponse.json(envelope(authResponse))),
|
||
|
||
http.post(`${API}/auth/register`, () => HttpResponse.json(envelope(authResponse))),
|
||
|
||
http.post(`${API}/auth/refresh`, () => HttpResponse.json(envelope(authResponse))),
|
||
|
||
http.post(`${API}/auth/logout`, () => HttpResponse.json(envelope({ message: 'Logged out' }))),
|
||
|
||
http.patch(`${API}/auth/me`, () =>
|
||
HttpResponse.json(envelope({ ...userResponse, name: 'Updated' })),
|
||
),
|
||
|
||
http.get(`${API}/health`, () =>
|
||
HttpResponse.json(
|
||
envelope({ status: 'ok', timestamp: new Date().toISOString(), uptime: 12345 }),
|
||
),
|
||
),
|
||
];
|