import { ConflictException, 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 { TbankAccessService } from '../src/integrations/tbank/tbank-access.service'; import { configureApp } from '../src/main'; describe('All accounts HTTP boundary', () => { let app: INestApplication; const withSupportedAccounts = jest.fn(); const token = new JwtService().sign( { sub: 'account-owner', type: 'access' }, { secret: process.env.JWT_SECRET }, ); const paths = [ 'portfolio', 'operations', 'income-summary', 'income-analytics', 'events', ]; beforeAll(async () => { const module = await Test.createTestingModule({ imports: [AppModule] }) .overrideProvider(TbankAccessService) .useValue({ withSupportedAccounts }) .compile(); app = module.createNestApplication(); configureApp(app); await app.init(); }); beforeEach(() => { withSupportedAccounts.mockReset(); withSupportedAccounts.mockImplementation( (_user: string, handler: (context: unknown) => unknown) => handler({ token: 'unused', generation: 'first', accounts: [] }), ); }); afterAll(() => app.close()); it.each(paths)('requires authentication for /all/%s', async (path) => { await request(app.getHttpServer()) .get(`/api/v1/brokerage-accounts/all/${path}`) .expect(401); expect(withSupportedAccounts).not.toHaveBeenCalled(); }); it.each(paths)( 'routes /all/%s to the aggregate with verified identity', async (path) => { const response = await request(app.getHttpServer()) .get(`/api/v1/brokerage-accounts/all/${path}`) .set('Authorization', `Bearer ${token}`) .expect(200); expect(withSupportedAccounts).toHaveBeenCalledWith( 'account-owner', expect.any(Function), ); expect(response.body).not.toHaveProperty('token'); expect(response.body).not.toHaveProperty('generation'); if (path === 'portfolio') expect(response.body).toMatchObject({ positions: [] }); else expect(response.body).toMatchObject({ accountId: 'all' }); }, ); it.each([ 'operations?limit=0', 'operations?limit=1001', 'operations?categories=unknown', 'operations?from=not-a-date', 'income-analytics?from=2026-08-01', 'income-analytics?from=2026-08-31&to=2026-08-01', ])('validates /all/%s in the configured pipeline', async (path) => { await request(app.getHttpServer()) .get(`/api/v1/brokerage-accounts/all/${path}`) .set('Authorization', `Bearer ${token}`) .expect(400); }); it('returns a recoverable code for an expired or foreign continuation', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/brokerage-accounts/all/operations?cursor=unknown') .set('Authorization', `Bearer ${token}`) .expect(400); expect(response.body).toMatchObject({ code: 'INVALID_OPERATIONS_CURSOR' }); }); it('preserves connection errors without a partial success response', async () => { withSupportedAccounts.mockRejectedValue( new ConflictException({ code: 'TBANK_NOT_CONNECTED' }), ); const response = await request(app.getHttpServer()) .get('/api/v1/brokerage-accounts/all/portfolio') .set('Authorization', `Bearer ${token}`) .expect(409); expect(response.body).toMatchObject({ code: 'TBANK_NOT_CONNECTED' }); }); });