chore: strengthen backend eslint profile
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user