chore: strengthen backend eslint profile

This commit is contained in:
2026-09-28 16:37:01 +03:00
committed by ksv741
parent 53b2794963
commit a970eaaf7a
179 changed files with 4485 additions and 2716 deletions
@@ -1,4 +1,5 @@
import { ServiceUnavailableException } from '@nestjs/common';
import { NcrRatingsClient } from './ncr-ratings.client';
const isin = 'RU000A10EW44';
const row = (value = 'AAA.ru', code = isin, href = '/ratings/issues/123/') =>
@@ -65,6 +66,18 @@ describe('public NCR issue ratings', () => {
);
},
);
it.each(['AA+.ru', 'B-.ru', 'BBB+.ru', 'CCC-.ru', 'CC.ru', 'D.ru'])(
'accepts the public rating grammar: %s', async (value) => {
jest.spyOn(global, 'fetch').mockResolvedValue(response(table(row(value))));
expect(await new NcrRatingsClient().rating(isin)).toMatchObject({ value });
},
);
it.each(['AAA+.ru', 'CC-.ru', 'BBBB.ru', 'BBB++.ru'])(
'rejects invalid rating grades: %s', async (value) => {
jest.spyOn(global, 'fetch').mockResolvedValue(response(table(row(value))));
await expect(new NcrRatingsClient().rating(isin)).rejects.toBeInstanceOf(ServiceUnavailableException);
},
);
it('bounds outage retries then recovers without retaining a false missing rating', async () => {
const fetchMock = jest
.spyOn(global, 'fetch')
+18 -10
View File
@@ -11,11 +11,11 @@ const DATE_COLUMN = 5;
const registryUrl = 'https://ratings.ru/ratings/issues/';
export type NcrRating = {
agency: 'NCR';
value: string | null;
status: 'active' | 'withdrawn';
date: string;
sourceUrl: string;
observedAt: string;
sourceUrl: string;
status: 'active' | 'withdrawn';
value: null | string;
};
@Injectable()
@@ -33,8 +33,9 @@ export class NcrRatingsClient {
if (!pending) {
pending = this.registry().catch(() => {
// Bound outage traffic too; never present a failed download as no rating.
if (this.cache.get('registry') === pending)
if (this.cache.get('registry') === pending) {
this.cache.set('registry', pending, { ttl: 60_000 });
}
throw new ServiceUnavailableException({ code: 'NCR_UNAVAILABLE' });
});
this.cache.set('registry', pending);
@@ -57,10 +58,13 @@ export class NcrRatingsClient {
table.length !== 1 ||
headings.length !== COLUMN_COUNT ||
headings[2] !== 'Рейтинг' ||
// eslint-disable-next-line security/detect-object-injection -- Column indexes are local numeric constants.
headings[ISIN_COLUMN] !== 'ISIN' ||
// eslint-disable-next-line security/detect-object-injection -- Column indexes are local numeric constants.
headings[DATE_COLUMN] !== 'Дата'
)
) {
throw new Error('NCR registry changed');
}
const observedAt = new Date().toISOString();
const result = new Map<string, NcrRating>();
table.find('tbody tr').each((_, row) => {
@@ -73,23 +77,26 @@ export class NcrRatingsClient {
const withdrawn = raw.toLowerCase() === 'рейтинг отозван';
if (
!withdrawn &&
!/^(AAA|AA[+-]?|A[+-]?|BBB[+-]?|BB[+-]?|B[+-]?|CCC[+-]?|CC|C|D)\.ru$/.test(
!/^(?:AAA|A{1,2}[+-]?|B{1,3}[+-]?|CCC[+-]?|C{1,2}|D)\.ru$/.test(
raw,
)
)
) {
throw new Error('NCR rating changed');
}
const dateParts = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec(
cells.eq(DATE_COLUMN).text().trim(),
);
const href = cells.eq(ISIN_COLUMN).find('a').attr('href');
if (!dateParts || !href || !/^\/ratings\/issues\/\d+\/$/.test(href))
if (!dateParts || !href || !/^\/ratings\/issues\/\d+\/$/.test(href)) {
throw new Error('NCR identity missing');
}
const date = `${dateParts[3]}-${dateParts[2]}-${dateParts[1]}`;
if (
!Number.isFinite(Date.parse(date)) ||
new Date(date).toISOString().slice(0, 10) !== date
)
) {
throw new Error('NCR date invalid');
}
const rating: NcrRating = {
agency: 'NCR',
value: withdrawn ? null : raw,
@@ -103,8 +110,9 @@ export class NcrRatingsClient {
previous &&
previous.date === date &&
(previous.value !== rating.value || previous.status !== rating.status)
)
) {
throw new Error('NCR rating ambiguous');
}
if (!previous || previous.date < date) result.set(isin, rating);
});
if (!result.size) throw new Error('NCR registry empty');
+1
View File
@@ -1,4 +1,5 @@
import { Module } from '@nestjs/common';
import { NcrRatingsClient } from './ncr-ratings.client';
@Module({