feat: add hourly portfolio sync history
Backend verify / commits (pull_request) Successful in 33s
Backend verify / security (pull_request) Failing after 12s
Backend verify / verify (pull_request) Skipped

This commit is contained in:
2026-10-09 09:04:41 +03:00
parent 03e75f8d2b
commit 0057925888
49 changed files with 1674 additions and 1870 deletions
+34 -16
View File
@@ -3,16 +3,21 @@ import type { Server } from 'node:http';
import { INestApplication } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Test } from '@nestjs/testing';
import { setTimeout as delay } from 'node:timers/promises';
import request from 'supertest';
import { AppModule } from '../src/app.module';
import { TbankMembershipService } from '../src/integrations/tbank/tbank-membership.service';
import { TbankClient } from '../src/integrations/tbank/tbank.client';
import { configureApp } from '../src/main';
import { PortfolioService } from '../src/modules/investments/portfolio/application/portfolio.service';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service';
describe('Portfolio structure HTTP contract', () => {
let app: INestApplication<Server>;
let prisma: PrismaService;
let membership: TbankMembershipService;
let portfolios: PortfolioService;
const tbank = {
validateToken: jest.fn().mockResolvedValue([
{ id: 'a', name: 'A', type: 'ACCOUNT_TYPE_TINKOFF' },
@@ -22,6 +27,10 @@ describe('Portfolio structure HTTP contract', () => {
getPositions: jest.fn(),
getInstrumentByFigi: jest.fn().mockResolvedValue(null),
getInstrumentByUid: jest.fn().mockResolvedValue(null),
getOpenAccounts: jest.fn().mockResolvedValue([
{ id: 'a', name: 'A', type: 'ACCOUNT_TYPE_TINKOFF' },
{ id: 'b', name: 'B', type: 'ACCOUNT_TYPE_TINKOFF' },
]),
};
const token = new JwtService().sign(
{ sub: 'structure-user', type: 'access' },
@@ -32,11 +41,16 @@ describe('Portfolio structure HTTP contract', () => {
const module = await Test.createTestingModule({ imports: [AppModule] })
.overrideProvider(TbankClient)
.useValue(tbank)
.overrideProvider(TbankHourlyScheduler)
.useValue({ connected: jest.fn().mockResolvedValue(undefined) })
.compile();
app = module.createNestApplication();
configureApp(app);
await app.init();
await app.get(PrismaService).user.create({
prisma = app.get(PrismaService);
membership = app.get(TbankMembershipService);
portfolios = app.get(PortfolioService);
await prisma.user.create({
data: {
id: 'structure-user',
email: 'structure@example.invalid',
@@ -65,21 +79,25 @@ describe('Portfolio structure HTTP contract', () => {
});
afterAll(() => app.close());
async function load(id: string) {
await request(app.getHttpServer())
.get(`/api/v1/brokerage-accounts/${id}/portfolio`)
.set('Authorization', `Bearer ${token}`)
.expect(200);
for (let index = 0; index < 200; index += 1) {
if (
!(await app
.get(PrismaService)
.tbankSyncWork.count({ where: { state: 'running' } }))
) {
return;
}
await delay(5);
const connection = await prisma.tbankConnection.findUniqueOrThrow({
where: { userId: 'structure-user' },
});
const member = await membership.request(
'structure-user',
connection.revision,
true,
);
if (member) await member.done;
const accountIds = id === 'all' ? ['a', 'b'] : [id];
for (const accountId of accountIds) {
const work = await portfolios.request(
'structure-user',
connection.revision,
accountId,
true,
);
if (work) await work.done;
}
throw new Error('Portfolio did not settle');
}
it.each(['a', 'all'])(