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
+33 -19
View File
@@ -1,10 +1,12 @@
import type { Server } from 'node:http';
import { INestApplication } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Test } from '@nestjs/testing';
import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';
import type { Server } from 'node:http';
import request from 'supertest';
import { AppModule } from '../src/app.module';
import { configureApp } from '../src/main';
import {
@@ -28,12 +30,12 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
);
const money = (value: string) => ({ currency: 'RUB', value });
const auth = (
method: 'get' | 'post' | 'patch' | 'delete',
method: 'delete' | 'get' | 'patch' | 'post',
path: string,
user = owner,
) =>
request(app.getHttpServer())
[method]('/api/v1/bank-cards' + path)
// eslint-disable-next-line security/detect-object-injection -- Keys come from local test fixtures or numeric iteration, not external input.
request(app.getHttpServer())[method]('/api/v1/bank-cards' + path)
.set('Authorization', `Bearer ${token(user)}`);
const createCard = async (value = '1000', user = owner, extra = {}) =>
(
@@ -102,10 +104,11 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
if (app) await app.close();
});
it('requires auth and reads never seed data or need T-Bank', async () => {
for (const path of ['', '/categories', '/summary?month=2026-08'])
for (const path of ['', '/categories', '/summary?month=2026-08']) {
await request(app.getHttpServer())
.get('/api/v1/bank-cards' + path)
.expect(401);
}
expect((await auth('get', '').expect(200)).body).toEqual([]);
expect(await categories()).toEqual([]);
expect(await db.expenseCategory.count({ where: { userId: owner } })).toBe(
@@ -241,10 +244,11 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
money('92233720368547758.08'),
{ currency: 'USD', value: '1' },
money('NaN'),
])
]) {
await auth('post', '/operations')
.send({ ...op(card.id), amount })
.expect(400);
}
for (const extra of [
{ date: '2026-02-30' },
{ date: '2099-01-01' },
@@ -252,10 +256,11 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
{ description: 'x'.repeat(501) },
{ kind: 'topup', categoryId: (await categories())[0].id },
{ kind: 'transfer', targetCardId: card.id },
])
]) {
await auth('post', '/operations')
.send(op(card.id, 'expense', '1', extra))
.expect(400);
}
expect(await db.bankCardOperation.count()).toBe(before);
for (const extra of [
{ name: ' ' },
@@ -264,7 +269,7 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
{ lastFour: '123' },
{ openingDate: '2026-02-30' },
{ openingBalance: money('-92233720368547758.09') },
])
]) {
await auth('post', '')
.send({
id: randomUUID(),
@@ -274,6 +279,7 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
...extra,
})
.expect(400);
}
});
it('returns nested money field errors without exposing input or writing a card', async () => {
const before = await db.bankCard.count();
@@ -310,8 +316,8 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
});
it('scopes every card/category/expense link and edit to owner', async () => {
const own = await createCard(),
foreign = await createCard('0', stranger);
const foreign = await createCard('0', stranger),
own = await createCard();
const foreignCategories = (
await auth('get', '/categories', stranger).expect(200)
).body as ExpenseCategoryDto[];
@@ -326,15 +332,17 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
'/' + foreign.id + '/expenses',
'/operations/' + foreignOp.id,
'/summary?month=2026-08&cardId=' + foreign.id,
])
]) {
await auth('get', path).expect(404);
}
for (const body of [
op(foreign.id),
op(own.id, 'expense', '1', { categoryId: foreignCategories[0].id }),
op(own.id, 'transfer', '1', { targetCardId: foreign.id }),
op(own.id, 'refund', '1', { originalExpenseId: foreignOp.id }),
])
]) {
await auth('post', '/operations').send(body).expect(404);
}
await auth('patch', '/' + foreign.id)
.send({ version: 1, archived: true })
.expect(404);
@@ -457,7 +465,7 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
}),
),
]);
expect(responses.map((r) => r.status).sort()).toEqual([201, 409]);
expect(responses.map((r) => r.status).sort((a, b) => a - b)).toEqual([201, 409]);
const body = op(card.id, 'refund', '400', {
date: '2026-09-01',
originalExpenseId: expense.id,
@@ -484,10 +492,11 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
{ amount: money('999') },
{ date: '2026-09-02' },
{ cardId: (await createCard()).id },
])
]) {
await auth('patch', '/operations/' + expense.id)
.send({ version: current.version, ...patch })
.expect(409);
}
await auth(
'delete',
`/operations/${expense.id}?version=${current.version}`,
@@ -501,6 +510,7 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
category.id,
);
});
// eslint-disable-next-line sonarjs/assertions-in-tests -- Every rejection is asserted through awaited Supertest .expect(400).
it('rejects invalid refund references, dates, immutable kind and purchase link', async () => {
const card = await createCard(),
other = await createCard();
@@ -513,8 +523,9 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
originalExpenseId: purchase.id,
date: '2026-07-01',
}),
])
]) {
await auth('post', '/operations').send(body).expect(400);
}
const refund = await createOp(
op(card.id, 'refund', '10', { originalExpenseId: purchase.id }),
);
@@ -532,7 +543,7 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
await createOp(op(a.id, 'expense', '20'));
await createOp(op(a.id, 'expense', '30'));
const pages: BankOperationsPageDto[] = [];
for (let page = 1; page <= 3; page++)
for (let page = 1; page <= 3; page++) {
pages.push(
(
await auth(
@@ -541,6 +552,7 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
).expect(200)
).body as BankOperationsPageDto,
);
}
expect(new Set(pages.flatMap((p) => p.items.map((i) => i.id))).size).toBe(
3,
);
@@ -564,8 +576,9 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
for (const body of [
op(card.id),
op(card.id, 'transfer', '1', { targetCardId: (await createCard()).id }),
])
]) {
await auth('post', '/operations').send(body).expect(409);
}
await auth('patch', '/operations/' + expense.id)
.send({ version: 1, description: 'no' })
.expect(409);
@@ -625,7 +638,7 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
});
expect(
refunds.reduce((sum, row) => sum + row.amountKopecks, 0n) <=
source.amountKopecks,
source.amountKopecks,
).toBe(true);
});
it('serializes opening-date changes and validates archived previous transfer sides', async () => {
@@ -755,8 +768,9 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
it('filters journal and summary inclusively across months and rejects invalid periods', async () => {
const card = await createCard();
const dates = ['2026-07-30', '2026-07-31', '2026-08-01', '2026-08-02'];
for (const date of dates)
for (const date of dates) {
await createOp(op(card.id, 'expense', '10', { date }));
}
const range = 'from=2026-07-31&to=2026-08-01';
const history = (
await auth('get', `/${card.id}/operations?${range}&limit=1`).expect(200)