import { INestApplication } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Test } from '@nestjs/testing'; import type { Server } from 'node:http'; import request from 'supertest'; import { AppModule } from '../src/app.module'; import { configureApp } from '../src/main'; import { TbankAccessService } from '../src/tbank/tbank-access.service'; import { TbankClient } from '../src/tbank/tbank.client'; import { MoexClient } from '../src/tbank/moex.client'; describe('Upcoming events date filter HTTP boundary', () => { let app: INestApplication; const getPortfolio = jest.fn(); const token = new JwtService().sign( { sub: 'account-owner', type: 'access' }, { secret: process.env.JWT_SECRET }, ); beforeAll(async () => { const module = await Test.createTestingModule({ imports: [AppModule] }) .overrideProvider(TbankAccessService) .useValue({ withAccount: ( _user: string, _id: string, handler: (ctx: unknown) => unknown, ) => handler({ token: 'unused' }), withSupportedAccounts: ( _user: string, handler: (ctx: unknown) => unknown, ) => handler({ token: 'unused', accounts: [ { id: 'account', name: 'Main' }, { id: 'second', name: 'Second' }, ], }), }) .overrideProvider(TbankClient) .useValue({ getPortfolio, getInstrumentByFigi: () => Promise.resolve(null), }) .overrideProvider(MoexClient) .useValue({ bondEvents: () => Promise.resolve([ { type: 'coupon', date: '2099-02-01', value: '2', currency: 'RUB' }, { type: 'offer', date: '2099-02-28' }, { type: 'offer', date: '2099-03-01' }, ]), }) .compile(); app = module.createNestApplication(); configureApp(app); await app.init(); }); beforeEach(() => { getPortfolio.mockReset().mockResolvedValue({ positions: [ { figi: 'B', ticker: 'BOND', instrumentType: 'bond', quantity: { units: 3 }, }, ], }); }); afterAll(() => app.close()); describe.each(['account', 'all'])('%s account scope', (scope) => { it('returns only the requested inclusive period and matching totals', async () => { const response = await request(app.getHttpServer()) .get( `/api/v1/brokerage-accounts/${scope}/events?from=2099-02-01&to=2099-02-28`, ) .set('Authorization', `Bearer ${token}`) .expect(200); expect(response.body).toMatchObject({ accountId: scope, events: scope === 'all' ? [ { date: '2099-02-01', account: { id: 'account' } }, { date: '2099-02-01', account: { id: 'second' } }, { date: '2099-02-28', account: { id: 'account' } }, { date: '2099-02-28', account: { id: 'second' } }, ] : [{ date: '2099-02-01' }, { date: '2099-02-28' }], incomeTotalsByCurrency: [ { currency: 'RUB', value: scope === 'all' ? '12' : '6' }, ], }); }); it.each([ 'from=not-a-date', 'from=2099-02-30', 'to=2099-13-01', 'from=2099-02-01T00:00:00Z', 'from=2099-02-28&to=2099-02-01', ])('rejects invalid dates before upstream access: %s', async (query) => { await request(app.getHttpServer()) .get(`/api/v1/brokerage-accounts/${scope}/events?${query}`) .set('Authorization', `Bearer ${token}`) .expect(400); expect(getPortfolio).not.toHaveBeenCalled(); }); }); });