chore: strengthen backend eslint profile

This commit is contained in:
2026-09-28 11:04:18 +03:00
parent ca5dbcb382
commit 9b3558f96c
179 changed files with 4485 additions and 2716 deletions
+54 -33
View File
@@ -1,23 +1,29 @@
import type { NextFunction, Request, Response } from 'express';
import type { Server } from 'node:http';
import { INestApplication } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Test } from '@nestjs/testing';
import type { Server } from 'node:http';
import { cpus, totalmem } from 'node:os';
import { writeFileSync, statSync, readFileSync } from 'node:fs';
import { setTimeout as delay } from 'node:timers/promises';
import type { Request, Response, NextFunction } from 'express';
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { cpus, tmpdir, totalmem } from 'node:os';
import { join } from 'node:path';
import { performance } from 'node:perf_hooks';
import { setTimeout as delay } from 'node:timers/promises';
import request from 'supertest';
import { AppModule } from '../src/app.module';
import { MoexClient } from '../src/integrations/moex/moex.api';
import { NcrRatingsClient } from '../src/integrations/ncr/ncr.api';
import { TbankTokenService } from '../src/integrations/tbank/tbank-token.service';
import {
TbankClient,
TbankOperationsByCursorRequest,
} from '../src/integrations/tbank/tbank.client';
import { TbankTokenService } from '../src/integrations/tbank/tbank-token.service';
import { configureApp } from '../src/main';
import { PrismaService } from '../src/prisma/prisma.service';
import { MoexClient } from '../src/integrations/moex/moex.api';
import { NcrRatingsClient } from '../src/integrations/ncr/ncr.api';
const evidenceDirectory = mkdtempSync(join(tmpdir(), 'wealthledger-read-evidence-'));
// Same navigation and synthetic sources as the saved baseline; isolated SQLite only.
describe('T-Bank persisted read budget', () => {
@@ -52,9 +58,9 @@ describe('T-Bank persisted read budget', () => {
const operations = Array.from({ length: 100_000 }, (_, index) => {
// 1,000 measured-month rows, 200 income receipts, remaining rows over 10 years.
const time =
index < 1000
? Date.UTC(2026, 7, 1) + index * 2_500_000
: Date.UTC(2016, 8, 1) + (index - 1000) * 3_160_000;
index < 1000 ?
Date.UTC(2026, 7, 1) + index * 2_500_000 :
Date.UTC(2016, 8, 1) + (index - 1000) * 3_160_000;
const instrument = instruments[index % instruments.length];
return {
id: `operation-${index}`,
@@ -69,15 +75,17 @@ describe('T-Bank persisted read budget', () => {
};
}).sort((a, b) => Number(b.date.seconds) - Number(a.date.seconds));
const rpc: Record<string, number> = {};
let upstreamMode: 'ready' | 'delayed' | 'failed' = 'ready';
let upstreamMode: 'delayed' | 'failed' | 'ready' = 'ready';
let sourceGate: Promise<void> | undefined;
let releaseSource = () => {};
const serverTimes: number[] = [];
async function counted<T>(name: string, value: T): Promise<T> {
// eslint-disable-next-line security/detect-object-injection -- Keys come from local test fixtures or numeric iteration, not external input.
rpc[name] = (rpc[name] ?? 0) + 1;
if (upstreamMode === 'delayed') await sourceGate;
if (upstreamMode === 'failed')
if (upstreamMode === 'failed') {
return Promise.reject(new Error('Synthetic outage'));
}
return Promise.resolve(value);
}
const upstream = {
@@ -261,7 +269,7 @@ describe('T-Bank persisted read budget', () => {
historyBoundary: 'openedOn',
},
});
for (let offset = 0; offset < operations.length; offset += 1000)
for (let offset = 0; offset < operations.length; offset += 1000) {
await prisma.tbankOperation.createMany({
data: operations.slice(offset, offset + 1000).map((row) => ({
...scope,
@@ -272,6 +280,7 @@ describe('T-Bank persisted read budget', () => {
payload: row,
})),
});
}
await prisma.tbankOperationCoverage.create({
data: {
...scope,
@@ -289,9 +298,9 @@ describe('T-Bank persisted read budget', () => {
uid: instrument.uid,
ticker: instrument.ticker,
instrumentType:
instrument.instrumentKind === 'INSTRUMENT_TYPE_SHARE'
? 'share'
: 'bond',
instrument.instrumentKind === 'INSTRUMENT_TYPE_SHARE' ?
'share' :
'bond',
metadata: instrument,
fetchedAt: now,
},
@@ -329,8 +338,8 @@ describe('T-Bank persisted read budget', () => {
],
});
}
expect(await prisma.tbankOperation.count()).toBe(300_000);
expect(await prisma.tbankCandle.count()).toBe(1_000_000);
assert.equal(await prisma.tbankOperation.count(), 300_000);
assert.equal(await prisma.tbankCandle.count(), 1_000_000);
}, 300_000);
afterAll(async () => app?.close());
@@ -368,8 +377,9 @@ describe('T-Bank persisted read budget', () => {
!(await app
.get(PrismaService)
.tbankSyncWork.count({ where: { state: 'running' } }))
)
) {
return;
}
}
throw new Error('Import did not settle');
}
@@ -397,6 +407,7 @@ describe('T-Bank persisted read budget', () => {
await fresh();
}
const warmupRpc = { ...rpc };
// eslint-disable-next-line security/detect-object-injection -- Keys come from local test fixtures or numeric iteration, not external input.
for (const key of Object.keys(rpc)) rpc[key] = 0;
await app.close();
await start();
@@ -411,21 +422,26 @@ describe('T-Bank persisted read budget', () => {
.set('Authorization', `Bearer ${token}`)
.expect(200);
const body = response.body as {
meta: { availability: string };
data: unknown;
meta: { availability: string };
};
if (route.includes('candles?period=all'))
if (route.includes('candles?period=all')) {
// eslint-disable-next-line jest/no-conditional-expect -- The parameterized fixture chooses the scenario; each branch asserts its outcome and the test also asserts common invariants.
expect((body.data as { candles: unknown[] }).candles).toHaveLength(
5000,
);
}
expect({ route, availability: body.meta.availability }).toEqual({
route,
availability: 'complete',
});
// eslint-disable-next-line security/detect-object-injection -- Keys come from local test fixtures or numeric iteration, not external input.
if (!responses[route]) {
// eslint-disable-next-line security/detect-object-injection -- Keys come from local test fixtures or numeric iteration, not external input.
responses[route] = body;
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths are under an exclusive mkdtemp directory or the local migrations directory.
writeFileSync(
'/tmp/wealthledger-browser-responses.json',
join(evidenceDirectory, 'browser-responses.json'),
JSON.stringify(responses),
);
}
@@ -439,8 +455,9 @@ describe('T-Bank persisted read budget', () => {
serverTimes.length = 0;
const before = { ...rpc };
const samples = [];
for (let batch = 0; batch < 10; batch += 1)
for (let batch = 0; batch < 10; batch += 1) {
samples.push(...(await Promise.all(Array.from({ length: 10 }, read))));
}
const clientTimes = samples
.map((sample) => sample.ms)
.sort((a, b) => a - b);
@@ -459,6 +476,7 @@ describe('T-Bank persisted read budget', () => {
rpc: Object.fromEntries(
Object.entries(rpc).map(([key, count]) => [
key,
// eslint-disable-next-line security/detect-object-injection -- Keys come from local test fixtures or numeric iteration, not external input.
count - (before[key] ?? 0),
]),
),
@@ -469,13 +487,13 @@ describe('T-Bank persisted read budget', () => {
runtime: process.version,
hardware: {
cpuQuota:
process.platform === 'linux'
? readFileSync('/sys/fs/cgroup/cpu.max', 'utf8').trim()
: null,
process.platform === 'linux' ?
readFileSync('/sys/fs/cgroup/cpu.max', 'utf8').trim() :
null,
memoryLimit:
process.platform === 'linux'
? readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim()
: null,
process.platform === 'linux' ?
readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim() :
null,
cpu: cpus()[0]?.model,
cpus: cpus().length,
memoryBytes: totalmem(),
@@ -495,6 +513,7 @@ describe('T-Bank persisted read budget', () => {
evidence,
totalRpc: { ...rpc },
warmupRpc,
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths stay in runner-created temporary directories or explicitly selected local test builds.
databaseBytes: statSync(
String(process.env.DATABASE_URL).replace('file:', ''),
).size,
@@ -513,11 +532,12 @@ describe('T-Bank persisted read budget', () => {
newRpc,
reduction: 1 - newRpc / oldRpc,
};
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths are under an exclusive mkdtemp directory or the local migrations directory.
writeFileSync(
'/tmp/wealthledger-persistent-budget.json',
join(evidenceDirectory, 'persistent-budget.json'),
JSON.stringify(output, null, 2),
);
console.log(JSON.stringify(output));
console.log(JSON.stringify({ ...output, evidenceDirectory }));
// Hold the source until HTTP reads finish: proves independence without a
// wall-clock race against the runner's throughput. The p95 gate stays below.
await fresh();
@@ -572,8 +592,9 @@ describe('T-Bank persisted read budget', () => {
staleLogicalPass: { portfolio: 1, positions: 1, retries: 0 },
coldImportMs,
});
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths are under an exclusive mkdtemp directory or the local migrations directory.
writeFileSync(
'/tmp/wealthledger-persistent-budget.json',
join(evidenceDirectory, 'persistent-budget.json'),
JSON.stringify(output, null, 2),
);
expect(newRpc).toBe(0);