test: isolate HTTP suites from scheduler and bank-card case order (WealthLedger/backend#65) (#100)
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 12s
Backend verify / verify (push) Failing after 5m59s

This commit was merged in pull request #100.
This commit is contained in:
2026-10-15 11:50:47 +03:00
parent a93f2d55e7
commit 9e4f9d9cab
10 changed files with 44 additions and 2 deletions
+3
View File
@@ -9,6 +9,7 @@ import { AppModule } from '../src/app.module';
import { TbankTokenService } from '../src/integrations/tbank/tbank-token.service'; import { TbankTokenService } from '../src/integrations/tbank/tbank-token.service';
import { TbankClient } from '../src/integrations/tbank/tbank.client'; import { TbankClient } from '../src/integrations/tbank/tbank.client';
import { configureApp } from '../src/main'; import { configureApp } from '../src/main';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service'; import { PrismaService } from '../src/prisma/prisma.service';
describe('All accounts HTTP boundary', () => { describe('All accounts HTTP boundary', () => {
@@ -31,6 +32,8 @@ describe('All accounts HTTP boundary', () => {
const module = await Test.createTestingModule({ imports: [AppModule] }) const module = await Test.createTestingModule({ imports: [AppModule] })
.overrideProvider(TbankClient) .overrideProvider(TbankClient)
.useValue(upstream) .useValue(upstream)
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile(); .compile();
app = module.createNestApplication(); app = module.createNestApplication();
configureApp(app); configureApp(app);
+5 -1
View File
@@ -10,6 +10,7 @@ import { AppModule } from '../src/app.module';
import { loggingOptions } from '../src/common/logging/logging.module'; import { loggingOptions } from '../src/common/logging/logging.module';
import { configureApp } from '../src/main'; import { configureApp } from '../src/main';
import { AuthService } from '../src/modules/auth/application/auth.service'; import { AuthService } from '../src/modules/auth/application/auth.service';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service'; import { PrismaService } from '../src/prisma/prisma.service';
type Authentication = { accessToken: string; user: { email: string } }; type Authentication = { accessToken: string; user: { email: string } };
@@ -33,7 +34,10 @@ describe('Authentication (e2e)', () => {
}, },
assignResponse: true, assignResponse: true,
}), }),
}).compile(); })
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile();
app = moduleFixture.createNestApplication(); app = moduleFixture.createNestApplication();
configureApp(app); configureApp(app);
+15 -1
View File
@@ -16,6 +16,7 @@ import {
BankSummaryDto, BankSummaryDto,
ExpenseCategoryDto, ExpenseCategoryDto,
} from '../src/modules/banking/bank-cards/presentation/bank-card.dto'; } from '../src/modules/banking/bank-cards/presentation/bank-card.dto';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service'; import { PrismaService } from '../src/prisma/prisma.service';
jest.setTimeout(60000); jest.setTimeout(60000);
@@ -77,7 +78,10 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
const start = async () => { const start = async () => {
const module = await Test.createTestingModule({ const module = await Test.createTestingModule({
imports: [AppModule], imports: [AppModule],
}).compile(); })
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile();
app = module.createNestApplication(); app = module.createNestApplication();
configureApp(app); configureApp(app);
await app.listen(0, '127.0.0.1'); await app.listen(0, '127.0.0.1');
@@ -369,6 +373,8 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
expect((await getCard(own.id)).balance.value).toBe('1000'); expect((await getCard(own.id)).balance.value).toBe('1000');
}); });
it('renames default category without role loss; duplicates and stale rename conflict', async () => { it('renames default category without role loss; duplicates and stale rename conflict', async () => {
// Defaults are seeded by the first write of the user; this case must not depend on earlier cases.
await createCard();
const category = (await categories()).find((c) => c.isDefault); const category = (await categories()).find((c) => c.isDefault);
assert.ok(category); assert.ok(category);
await auth('patch', '/categories/' + category.id) await auth('patch', '/categories/' + category.id)
@@ -725,11 +731,19 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
expect(await getCard(card.id)).toEqual(before); expect(await getCard(card.id)).toEqual(before);
expect((await getOp(refund.id)).originalExpenseId).toBe(purchase.id); expect((await getOp(refund.id)).originalExpenseId).toBe(purchase.id);
await auth('post', '/operations').send(deletedBody).expect(410); await auth('post', '/operations').send(deletedBody).expect(410);
// The rename is this case's own prerequisite, not an effect of an earlier case.
const defaultCategory = (await categories()).find((c) => c.isDefault);
assert.ok(defaultCategory);
await auth('patch', '/categories/' + defaultCategory.id)
.send({ version: defaultCategory.version, name: 'Остальное' })
.expect(200);
expect((await categories()).find((c) => c.isDefault)?.name).toBe( expect((await categories()).find((c) => c.isDefault)?.name).toBe(
'Остальное', 'Остальное',
); );
}); });
it('enforces actual FK and unique constraints', async () => { it('enforces actual FK and unique constraints', async () => {
// Seeds the owner's default categories so the unique-name check has a real row to collide with.
await createCard();
await expect( await expect(
db.expenseCategory.create({ db.expenseCategory.create({
data: { data: {
+3
View File
@@ -15,6 +15,7 @@ import { configureApp } from '../src/main';
import { BondCatalogService } from '../src/modules/investments/instruments/bonds/application/bond-catalog.service'; import { BondCatalogService } from '../src/modules/investments/instruments/bonds/application/bond-catalog.service';
import { BondIncomeService } from '../src/modules/investments/instruments/bonds/application/bond-income.service'; import { BondIncomeService } from '../src/modules/investments/instruments/bonds/application/bond-income.service';
import { BondMarketService } from '../src/modules/investments/instruments/bonds/application/bond-market.service'; import { BondMarketService } from '../src/modules/investments/instruments/bonds/application/bond-market.service';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
const uid = 'e6123145-9665-43e0-8413-cd61b8aa9b13'; const uid = 'e6123145-9665-43e0-8413-cd61b8aa9b13';
describe('Bond HTTP contract', () => { describe('Bond HTTP contract', () => {
@@ -38,6 +39,8 @@ describe('Bond HTTP contract', () => {
.useValue(bonds) .useValue(bonds)
.overrideProvider(BondIncomeService) .overrideProvider(BondIncomeService)
.useValue(bonds) .useValue(bonds)
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile(); .compile();
app = module.createNestApplication(); app = module.createNestApplication();
configureApp(app); configureApp(app);
+3
View File
@@ -16,6 +16,7 @@ import { StockCatalogService } from '../src/modules/investments/instruments/stoc
import { StockIncomeService } from '../src/modules/investments/instruments/stocks/application/stock-income.service'; import { StockIncomeService } from '../src/modules/investments/instruments/stocks/application/stock-income.service';
import { StockMarketService } from '../src/modules/investments/instruments/stocks/application/stock-market.service'; import { StockMarketService } from '../src/modules/investments/instruments/stocks/application/stock-market.service';
import { StockScreenerService } from '../src/modules/investments/instruments/stocks/application/stock-screener.service'; import { StockScreenerService } from '../src/modules/investments/instruments/stocks/application/stock-screener.service';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
const uid = 'e6123145-9665-43e0-8413-cd61b8aa9b13'; const uid = 'e6123145-9665-43e0-8413-cd61b8aa9b13';
describe('Stock HTTP contract', () => { describe('Stock HTTP contract', () => {
@@ -41,6 +42,8 @@ describe('Stock HTTP contract', () => {
.useValue(stocks) .useValue(stocks)
.overrideProvider(StockScreenerService) .overrideProvider(StockScreenerService)
.useValue(screener) .useValue(screener)
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile(); .compile();
app = module.createNestApplication(); app = module.createNestApplication();
configureApp(app); configureApp(app);
+3
View File
@@ -10,6 +10,7 @@ import { TbankAccessService } from '../src/integrations/tbank/tbank-access.servi
import { TbankTokenService } from '../src/integrations/tbank/tbank-token.service'; import { TbankTokenService } from '../src/integrations/tbank/tbank-token.service';
import { TbankClient } from '../src/integrations/tbank/tbank.client'; import { TbankClient } from '../src/integrations/tbank/tbank.client';
import { configureApp } from '../src/main'; import { configureApp } from '../src/main';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service'; import { PrismaService } from '../src/prisma/prisma.service';
describe('Persistent T-Bank connection boundary (SQLite/HTTP)', () => { describe('Persistent T-Bank connection boundary (SQLite/HTTP)', () => {
@@ -31,6 +32,8 @@ describe('Persistent T-Bank connection boundary (SQLite/HTTP)', () => {
const module = await Test.createTestingModule({ imports: [AppModule] }) const module = await Test.createTestingModule({ imports: [AppModule] })
.overrideProvider(TbankClient) .overrideProvider(TbankClient)
.useValue(upstream) .useValue(upstream)
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile(); .compile();
app = module.createNestApplication(); app = module.createNestApplication();
configureApp(app); configureApp(app);
+3
View File
@@ -15,6 +15,7 @@ import {
} from '../src/modules/investments/income/income-analytics.dto'; } from '../src/modules/investments/income/income-analytics.dto';
import { moscowDate } from '../src/modules/investments/investment-calendar'; import { moscowDate } from '../src/modules/investments/investment-calendar';
import { BrokerageOperationsRepository } from '../src/modules/investments/operations/infrastructure/brokerage-operations.repository'; import { BrokerageOperationsRepository } from '../src/modules/investments/operations/infrastructure/brokerage-operations.repository';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service'; import { PrismaService } from '../src/prisma/prisma.service';
// Financial proof uses real persisted rows/coverage and the configured HTTP boundary. // Financial proof uses real persisted rows/coverage and the configured HTTP boundary.
@@ -59,6 +60,8 @@ describe('local income completeness and exact money (SQLite)', () => {
const module = await Test.createTestingModule({ imports: [AppModule] }) const module = await Test.createTestingModule({ imports: [AppModule] })
.overrideProvider(TbankClient) .overrideProvider(TbankClient)
.useValue(upstream) .useValue(upstream)
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile(); .compile();
app = module.createNestApplication(); app = module.createNestApplication();
configureApp(app); configureApp(app);
+3
View File
@@ -15,6 +15,7 @@ import { TbankClient } from '../src/integrations/tbank/tbank.client';
import { configureApp } from '../src/main'; import { configureApp } from '../src/main';
import { BrokerageOperationsRepository } from '../src/modules/investments/operations/infrastructure/brokerage-operations.repository'; import { BrokerageOperationsRepository } from '../src/modules/investments/operations/infrastructure/brokerage-operations.repository';
import { BrokerageOperationsResourceDto } from '../src/modules/investments/operations/presentation/brokerage-operation.dto'; import { BrokerageOperationsResourceDto } from '../src/modules/investments/operations/presentation/brokerage-operation.dto';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service'; import { PrismaService } from '../src/prisma/prisma.service';
const uid = '11111111-1111-4111-8111-111111111111'; const uid = '11111111-1111-4111-8111-111111111111';
@@ -45,6 +46,8 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
const module = await Test.createTestingModule({ imports: [AppModule] }) const module = await Test.createTestingModule({ imports: [AppModule] })
.overrideProvider(TbankClient) .overrideProvider(TbankClient)
.useValue(upstream) .useValue(upstream)
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile(); .compile();
app = module.createNestApplication(); app = module.createNestApplication();
configureApp(app); configureApp(app);
+3
View File
@@ -18,6 +18,7 @@ import { BrokerageAccountsResourceDto } from '../src/modules/investments/brokera
import { IncomeAnalyticsResourceDto } from '../src/modules/investments/income/income-analytics.dto'; import { IncomeAnalyticsResourceDto } from '../src/modules/investments/income/income-analytics.dto';
import { PortfolioService } from '../src/modules/investments/portfolio/application/portfolio.service'; import { PortfolioService } from '../src/modules/investments/portfolio/application/portfolio.service';
import { PortfolioResourceDto } from '../src/modules/investments/portfolio/presentation/portfolio.dto'; import { PortfolioResourceDto } from '../src/modules/investments/portfolio/presentation/portfolio.dto';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service'; import { PrismaService } from '../src/prisma/prisma.service';
function deferred<T>() { function deferred<T>() {
@@ -102,6 +103,8 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
const module = await Test.createTestingModule({ imports: [AppModule] }) const module = await Test.createTestingModule({ imports: [AppModule] })
.overrideProvider(TbankClient) .overrideProvider(TbankClient)
.useValue(upstream) .useValue(upstream)
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile(); .compile();
app = module.createNestApplication(); app = module.createNestApplication();
configureApp(app); configureApp(app);
+3
View File
@@ -21,6 +21,7 @@ import {
TbankOperationsByCursorRequest, TbankOperationsByCursorRequest,
} from '../src/integrations/tbank/tbank.client'; } from '../src/integrations/tbank/tbank.client';
import { configureApp } from '../src/main'; import { configureApp } from '../src/main';
import { TbankHourlyScheduler } from '../src/modules/investments/tbank-sync/tbank-hourly.scheduler';
import { PrismaService } from '../src/prisma/prisma.service'; import { PrismaService } from '../src/prisma/prisma.service';
const evidenceDirectory = mkdtempSync(join(tmpdir(), 'wealthledger-read-evidence-')); const evidenceDirectory = mkdtempSync(join(tmpdir(), 'wealthledger-read-evidence-'));
@@ -215,6 +216,8 @@ describe('T-Bank persisted read budget', () => {
}) })
.overrideProvider(NcrRatingsClient) .overrideProvider(NcrRatingsClient)
.useValue({ rating: () => counted('ncrRating', null) }) .useValue({ rating: () => counted('ncrRating', null) })
.overrideProvider(TbankHourlyScheduler)
.useValue({ tick: jest.fn() })
.compile(); .compile();
app = module.createNestApplication(); app = module.createNestApplication();
app.use((_req: Request, res: Response, next: NextFunction) => { app.use((_req: Request, res: Response, next: NextFunction) => {