import { INestApplication } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Test } from '@nestjs/testing'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; 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 { BankCardDto, BankOperationDto, BankOperationsPageDto, BankSummaryDto, ExpenseCategoryDto, } from '../src/modules/banking/bank-cards/presentation/bank-card.dto'; import { PrismaService } from '../src/prisma/prisma.service'; jest.setTimeout(60000); describe('Manual bank cards configured HTTP and real SQLite', () => { let app: INestApplication, db: PrismaService; const owner = randomUUID(), stranger = randomUUID(); const token = (user = owner) => new JwtService().sign( { sub: user, type: 'access' }, { secret: process.env.JWT_SECRET }, ); const money = (value: string) => ({ currency: 'RUB', value }); const auth = ( method: 'get' | 'post' | 'patch' | 'delete', path: string, user = owner, ) => request(app.getHttpServer()) [method]('/api/v1/bank-cards' + path) .set('Authorization', `Bearer ${token(user)}`); const createCard = async (value = '1000', user = owner, extra = {}) => ( await auth('post', '', user) .send({ id: randomUUID(), name: 'Вымышленная карта', openingDate: '2026-01-01', openingBalance: money(value), ...extra, }) .expect(201) ).body as BankCardDto; const getCard = async (id: string) => (await auth('get', '/' + id).expect(200)).body as BankCardDto; const getOp = async (id: string) => (await auth('get', '/operations/' + id).expect(200)) .body as BankOperationDto; const op = (cardId: string, kind = 'expense', value = '100', extra = {}) => ({ id: randomUUID(), cardId, kind, amount: money(value), date: '2026-08-01', ...extra, }); const createOp = async (body: object) => (await auth('post', '/operations').send(body).expect(201)) .body as BankOperationDto; const categories = async () => (await auth('get', '/categories').expect(200)).body as ExpenseCategoryDto[]; const summary = async (month: string, cardId?: string) => ( await auth( 'get', `/summary?month=${month}${cardId ? '&cardId=' + cardId : ''}`, ).expect(200) ).body as BankSummaryDto; const start = async () => { const module = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = module.createNestApplication(); configureApp(app); await app.listen(0, '127.0.0.1'); db = app.get(PrismaService); }; beforeAll(async () => { const url = process.env.DATABASE_URL; if ( !url || !/^file:\/(?:private\/)?tmp\/bank-cards-[^/]+\/.+\.db$/.test(url) ) throw new Error( 'Pass a unique /tmp/bank-cards-*/test.db DATABASE_URL. No shared/user database is allowed.', ); execFileSync( process.execPath, ['node_modules/prisma/build/index.js', 'migrate', 'deploy'], { cwd: process.cwd(), env: { ...process.env, RUST_LOG: 'info' }, stdio: 'pipe', }, ); await start(); await db.user.createMany({ data: [ { id: owner, email: owner + '@example.test', passwordHash: 'fictional', }, { id: stranger, email: stranger + '@example.test', passwordHash: 'fictional', }, ], }); }); afterAll(async () => { 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']) 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( 0, ); }); it('seeds once concurrently, replays card creation and preserves current archived state', async () => { const body = { id: randomUUID(), name: ' Основная ', openingDate: '2026-01-01', openingBalance: money('1000'), }; const responses = await Promise.all([ auth('post', '').send(body), auth('post', '').send(body), auth('post', '').send({ ...body, id: randomUUID() }), ]); expect(responses.map((r) => r.status)).toEqual([201, 201, 201]); expect(await db.bankCard.count({ where: { userId: owner } })).toBe(2); expect(await categories()).toHaveLength(9); await auth('post', '') .send({ ...body, name: 'Другое имя' }) .expect(409); const archived = ( await auth('patch', '/' + body.id) .send({ version: 1, archived: true }) .expect(200) ).body as BankCardDto; const replay = (await auth('post', '').send(body).expect(201)) .body as BankCardDto; expect(replay).toEqual(archived); expect(replay.name).toBe('Основная'); }); it('roundtrips >2^53 and signed64 boundaries, and calculates beyond a stored value', async () => { for (const value of [ '90071992547409.93', '92233720368547758.07', '-92233720368547758.08', ]) { const card = await createCard(value); expect(card.balance.value).toBe(value); expect((await getCard(card.id)).openingBalance.value).toBe(value); } const card = await createCard('92233720368547758.07'); await createOp(op(card.id, 'topup', '0.01')); expect((await getCard(card.id)).balance.value).toBe('92233720368547758.08'); const exact = await createCard('1'); await createOp(op(exact.id, 'expense', '0.10')); await createOp(op(exact.id, 'expense', '0.20')); expect((await getCard(exact.id)).balance.value).toBe('0.7'); await createOp(op(exact.id, 'expense', '1')); expect((await getCard(exact.id)).balance.value).toBe('-0.3'); }); it('clears optional card metadata and changes opening balance without synthetic operations', async () => { const card = await createCard('0', owner, { bankName: 'Fake Bank', lastFour: '1234', }); const changed = ( await auth('patch', '/' + card.id) .send({ version: 1, bankName: null, lastFour: null, openingBalance: money('-25'), }) .expect(200) ).body as BankCardDto; expect(changed).toMatchObject({ bankName: null, lastFour: null, balance: money('-25'), }); expect( await db.bankCardOperation.count({ where: { cardId: card.id } }), ).toBe(0); expect((await summary('2026-08', card.id)).net.value).toBe('0'); }); it('rejects explicit null on nonnullable write fields without changing values or versions', async () => { const card = await createCard(); const expense = await createOp(op(card.id)); const archived = ( await auth('patch', '/' + card.id) .send({ version: card.version, archived: true }) .expect(200) ).body as BankCardDto; for (const field of ['archived', 'openingBalance', 'openingDate', 'name']) { await auth('patch', '/' + card.id) .send({ version: archived.version, [field]: null }) .expect(400); expect(await getCard(card.id)).toEqual(archived); } await auth('patch', '/' + card.id) .send({ version: archived.version, archived: false }) .expect(200); for (const field of [ 'amount', 'kind', 'cardId', 'date', 'description', 'targetCardId', 'categoryId', 'originalExpenseId', ]) { await auth('patch', '/operations/' + expense.id) .send({ version: expense.version, [field]: null }) .expect(400); expect(await getOp(expense.id)).toEqual(expense); } const before = await db.bankCardOperation.count(); for (const field of [ 'amount', 'description', 'targetCardId', 'categoryId', 'originalExpenseId', ]) { await auth('post', '/operations') .send({ ...op(card.id), [field]: null }) .expect(400); } expect(await db.bankCardOperation.count()).toBe(before); }); it('validates actual money, calendar, field and operation-shape inputs without writes', async () => { const card = await createCard(); const before = await db.bankCardOperation.count(); for (const amount of [ money('0'), money('-1'), money('1.001'), 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' }, { date: '2025-12-31' }, { 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: ' ' }, { name: 'x'.repeat(101) }, { bankName: 'x'.repeat(101) }, { lastFour: '123' }, { openingDate: '2026-02-30' }, { openingBalance: money('-92233720368547758.09') }, ]) await auth('post', '') .send({ id: randomUUID(), name: 'Card', openingDate: '2026-01-01', openingBalance: money('0'), ...extra, }) .expect(400); }); it('scopes every card/category/expense link and edit to owner', async () => { const own = await createCard(), foreign = await createCard('0', stranger); const foreignCategories = ( await auth('get', '/categories', stranger).expect(200) ).body as ExpenseCategoryDto[]; const foreignOp = ( await auth('post', '/operations', stranger) .send(op(foreign.id)) .expect(201) ).body as BankOperationDto; for (const path of [ '/' + foreign.id, '/' + foreign.id + '/operations?month=2026-08', '/' + 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); await auth('patch', '/categories/' + foreignCategories[0].id) .send({ version: 1, name: 'No' }) .expect(404); await auth('patch', '/operations/' + foreignOp.id) .send({ version: 1, amount: money('2') }) .expect(404); await auth('delete', '/operations/' + foreignOp.id + '?version=1').expect( 404, ); expect((await getCard(own.id)).balance.value).toBe('1000'); }); it('renames default category without role loss; duplicates and stale rename conflict', async () => { const category = (await categories()).find((c) => c.isDefault); assert.ok(category); await auth('patch', '/categories/' + category.id) .send({ version: category.version, name: 'Остальное' }) .expect(200); await auth('patch', '/categories/' + category.id) .send({ version: category.version, name: 'Stale' }) .expect(409); await auth('post', '/categories').send({ name: ' кафе ' }).expect(409); const custom = ( await auth('post', '/categories').send({ name: 'Питомец' }).expect(201) ).body as ExpenseCategoryDto; expect( (await categories()).find((c) => c.id === category.id), ).toMatchObject({ name: 'Остальное', isDefault: true }); const card = await createCard(); expect((await createOp(op(card.id))).categoryId).toBe(category.id); expect(custom.name).toBe('Питомец'); }); it('keeps both independent expenses, exactly one same-intent operation and tombstone after editing/deleting', async () => { const card = await createCard(); const body = op(card.id); const responses = await Promise.all([ auth('post', '/operations').send(body), auth('post', '/operations').send(body), auth('post', '/operations').send(op(card.id, 'expense', '200')), ]); expect(responses.map((r) => r.status)).toEqual([201, 201, 201]); expect((await getCard(card.id)).balance.value).toBe('700'); await auth('post', '/operations') .send({ ...body, amount: money('200') }) .expect(409); const edited = ( await auth('patch', '/operations/' + body.id) .send({ version: 1, amount: money('150') }) .expect(200) ).body as BankOperationDto; expect( ( (await auth('post', '/operations').send(body).expect(201)) .body as BankOperationDto ).amount.value, ).toBe('150'); await auth('delete', '/operations/' + body.id + '?version=1').expect(409); await auth( 'delete', `/operations/${body.id}?version=${edited.version}`, ).expect(204); await auth('delete', '/operations/' + body.id + '?version=1').expect(204); await auth('post', '/operations').send(body).expect(410); expect((await getCard(card.id)).balance.value).toBe('800'); }); it('transfers atomically, edits both sides, validates old and new cards and opening dates', async () => { const a = await createCard('100'), b = await createCard('0'), late = await createCard('0', owner, { openingDate: '2026-09-01' }); await auth('post', '/operations') .send(op(a.id, 'transfer', '150', { targetCardId: late.id })) .expect(400); const transfer = await createOp( op(a.id, 'transfer', '150', { targetCardId: b.id }), ); expect((await getCard(a.id)).balance.value).toBe('-50'); expect((await getCard(b.id)).balance.value).toBe('150'); const history = ( await auth('get', `/${b.id}/operations?month=2026-08`).expect(200) ).body as BankOperationsPageDto; expect(history.items[0]).toMatchObject({ id: transfer.id, cardId: a.id, targetCardId: b.id, }); expect((await summary('2026-08', b.id)).expenses.value).toBe('0'); await auth('patch', '/' + b.id) .send({ version: 1, openingDate: '2026-08-02' }) .expect(400); const edited = ( await auth('patch', '/operations/' + transfer.id) .send({ version: 1, amount: money('50') }) .expect(200) ).body as BankOperationDto; expect((await getCard(a.id)).balance.value).toBe('50'); expect((await getCard(b.id)).balance.value).toBe('50'); await auth( 'delete', `/operations/${transfer.id}?version=${edited.version}`, ).expect(204); expect((await getCard(a.id)).balance.value).toBe('100'); expect((await getCard(b.id)).balance.value).toBe('0'); }); it('serializes refund limits, replays full refund before limit checks, excludes own value during edit', async () => { const card = await createCard(); const expense = await createOp(op(card.id, 'expense', '1000')); const responses = await Promise.all([ auth('post', '/operations').send( op(card.id, 'refund', '600', { date: '2026-09-01', originalExpenseId: expense.id, }), ), auth('post', '/operations').send( op(card.id, 'refund', '600', { date: '2026-09-01', originalExpenseId: expense.id, }), ), ]); expect(responses.map((r) => r.status).sort()).toEqual([201, 409]); const body = op(card.id, 'refund', '400', { date: '2026-09-01', originalExpenseId: expense.id, }); const refund = await createOp(body); expect(refund.availableRefund?.value).toBe('400'); await auth('post', '/operations').send(body).expect(201); await auth('patch', '/operations/' + refund.id) .send({ version: refund.version, description: 'Тот же полный возврат' }) .expect(200); const sept = await summary('2026-09', card.id); expect(sept).toMatchObject({ expenses: money('0'), refunds: money('1000'), net: money('-1000'), }); const august = await summary('2026-08', card.id); expect(august.net.value).toBe('1000'); const choices = (await auth('get', '/' + card.id + '/expenses').expect(200)) .body as BankOperationsPageDto; expect(choices.items[0].availableRefund?.value).toBe('0'); const current = await getOp(expense.id); for (const patch of [ { 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}`, ).expect(409); const category = (await categories()).find((c) => !c.isDefault); assert.ok(category); await auth('patch', '/operations/' + expense.id) .send({ version: current.version, categoryId: category.id }) .expect(200); expect((await summary('2026-09', card.id)).categories[0].categoryId).toBe( category.id, ); }); it('rejects invalid refund references, dates, immutable kind and purchase link', async () => { const card = await createCard(), other = await createCard(); const purchase = await createOp(op(card.id)), topup = await createOp(op(card.id, 'topup')); for (const body of [ op(card.id, 'refund', '1', { originalExpenseId: topup.id }), op(other.id, 'refund', '1', { originalExpenseId: purchase.id }), op(card.id, 'refund', '1', { 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 }), ); await auth('patch', '/operations/' + refund.id) .send({ version: refund.version, originalExpenseId: topup.id }) .expect(400); await auth('patch', '/operations/' + topup.id) .send({ version: topup.version, kind: 'expense' }) .expect(400); }); it('moves plain operations and month totals, preserves full balance with stable pagination', async () => { const a = await createCard(), b = await createCard(); const first = await createOp(op(a.id, 'expense', '10')); await createOp(op(a.id, 'expense', '20')); await createOp(op(a.id, 'expense', '30')); const pages: BankOperationsPageDto[] = []; for (let page = 1; page <= 3; page++) pages.push( ( await auth( 'get', `/${a.id}/operations?month=2026-08&page=${page}&limit=1`, ).expect(200) ).body as BankOperationsPageDto, ); expect(new Set(pages.flatMap((p) => p.items.map((i) => i.id))).size).toBe( 3, ); expect(pages.map((p) => p.hasNext)).toEqual([true, true, false]); expect((await getCard(a.id)).balance.value).toBe('940'); expect((await summary('2026-08', a.id)).expenses.value).toBe('60'); await auth('patch', '/operations/' + first.id) .send({ version: first.version, cardId: b.id, date: '2026-09-01' }) .expect(200); expect((await getCard(a.id)).balance.value).toBe('950'); expect((await getCard(b.id)).balance.value).toBe('990'); expect((await summary('2026-09', b.id)).net.value).toBe('10'); }); it('serializes archive with writes, blocks financial changes, keeps metadata editable and all-card history', async () => { const card = await createCard(); const expense = await createOp(op(card.id)); const total = (await summary('2026-08')).net.value; await auth('patch', '/' + card.id) .send({ version: 1, archived: true }) .expect(200); 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); await auth('delete', `/operations/${expense.id}?version=1`).expect(409); await auth('patch', '/' + card.id) .send({ version: 2, openingBalance: money('200') }) .expect(409); await auth('patch', '/' + card.id) .send({ version: 2, name: 'Архивная' }) .expect(200); expect((await summary('2026-08')).net.value).toBe(total); await auth('patch', '/' + card.id) .send({ version: 1, archived: false }) .expect(409); await auth('patch', '/' + card.id) .send({ version: 3, archived: false }) .expect(200); await createOp(op(card.id)); const racing = await createCard(); const results = await Promise.all([ auth('patch', '/' + racing.id).send({ version: 1, archived: true }), auth('post', '/operations').send(op(racing.id)), ]); expect(results[0].status).toBe(200); expect([201, 409]).toContain(results[1].status); expect((await getCard(racing.id)).balance.value).toBe( results[1].status === 201 ? '900' : '1000', ); }); it('serializes a refund against reducing its purchase, and rolls back rejected source locks', async () => { const card = await createCard(); const expense = await createOp(op(card.id, 'expense', '1000')); const results = await Promise.all([ auth('post', '/operations').send( op(card.id, 'refund', '600', { originalExpenseId: expense.id }), ), auth('patch', '/operations/' + expense.id).send({ version: expense.version, amount: money('500'), }), ]); expect([201, 409]).toContain(results[0].status); expect([200, 409]).toContain(results[1].status); expect(results.filter((r) => r.status < 300)).toHaveLength(1); const current = await getOp(expense.id); const invalid = op(card.id, 'refund', '1', { originalExpenseId: expense.id, date: '2026-07-01', }); await auth('post', '/operations').send(invalid).expect(400); expect((await getOp(expense.id)).version).toBe(current.version); const refunds = await db.bankCardOperation.findMany({ where: { originalExpenseId: expense.id, deletedAt: null }, }); const source = await db.bankCardOperation.findUniqueOrThrow({ where: { id: expense.id }, }); expect( refunds.reduce((sum, row) => sum + row.amountKopecks, 0n) <= source.amountKopecks, ).toBe(true); }); it('serializes opening-date changes and validates archived previous transfer sides', async () => { const card = await createCard(); const results = await Promise.all([ auth('patch', '/' + card.id).send({ version: 1, openingDate: '2026-09-01', }), auth('post', '/operations').send(op(card.id)), ]); expect(results.filter((r) => r.status < 300)).toHaveLength(1); const current = await getCard(card.id); const rows = await db.bankCardOperation.findMany({ where: { cardId: card.id, deletedAt: null }, }); expect(rows.every((row) => row.date >= current.openingDate)).toBe(true); const a = await createCard(), b = await createCard(), c = await createCard(); const transfer = await createOp( op(a.id, 'transfer', '10', { targetCardId: b.id }), ); await auth('patch', '/' + b.id) .send({ version: 1, archived: true }) .expect(200); await auth('patch', '/operations/' + transfer.id) .send({ version: 1, targetCardId: c.id }) .expect(409); expect((await getCard(b.id)).balance.value).toBe('1010'); expect((await getCard(c.id)).balance.value).toBe('1000'); }); it('deleting refunds releases the purchase and a later replay never restores either record', async () => { const card = await createCard(); const purchaseBody = op(card.id), purchase = await createOp(purchaseBody); const refundBody = op(card.id, 'refund', '50', { originalExpenseId: purchase.id, }), refund = await createOp(refundBody); await auth( 'delete', `/operations/${refund.id}?version=${refund.version}`, ).expect(204); const current = await getOp(purchase.id); expect(current.availableRefund?.value).toBe('100'); await auth( 'delete', `/operations/${purchase.id}?version=${current.version}`, ).expect(204); await auth('post', '/operations').send(refundBody).expect(410); await auth('post', '/operations').send(purchaseBody).expect(410); await auth('post', '/operations') .send(op(card.id, 'refund', '1', { originalExpenseId: purchase.id })) .expect(400); expect((await getCard(card.id)).balance.value).toBe('1000'); }); it('preserves history, fingerprints, archive and deleted replay across app restart', async () => { const card = await createCard(); const purchase = await createOp(op(card.id)); const refund = await createOp( op(card.id, 'refund', '10', { originalExpenseId: purchase.id }), ); const deletedBody = op(card.id, 'topup', '20'); const deleted = await createOp(deletedBody); await auth('delete', `/operations/${deleted.id}?version=1`).expect(204); await auth('patch', '/' + card.id) .send({ version: 1, archived: true }) .expect(200); const before = await getCard(card.id); await app.close(); await start(); expect(await getCard(card.id)).toEqual(before); expect((await getOp(refund.id)).originalExpenseId).toBe(purchase.id); await auth('post', '/operations').send(deletedBody).expect(410); expect((await categories()).find((c) => c.isDefault)?.name).toBe( 'Остальное', ); }); it('enforces actual FK and unique constraints', async () => { await expect( db.expenseCategory.create({ data: { id: randomUUID(), userId: randomUUID(), name: 'Invalid', nameKey: 'invalid', }, }), ).rejects.toMatchObject({ code: 'P2003' }); const category = (await categories())[0]; await expect( db.expenseCategory.create({ data: { id: randomUUID(), userId: owner, name: category.name, nameKey: category.name.toLocaleLowerCase('ru-RU'), }, }), ).rejects.toMatchObject({ code: 'P2002' }); }); it('persists payment systems, preserves omitted metadata and allows clearing', async () => { const card = await createCard('1000', owner, { paymentSystem: 'mir' }); expect((await getCard(card.id)).paymentSystem).toBe('mir'); await auth('patch', '/' + card.id) .send({ version: 1, name: 'Renamed' }) .expect(200); expect((await getCard(card.id)).paymentSystem).toBe('mir'); await auth('patch', '/' + card.id) .send({ version: 2, paymentSystem: 'visa' }) .expect(200); expect((await getCard(card.id)).paymentSystem).toBe('visa'); await auth('patch', '/' + card.id) .send({ version: 3, paymentSystem: 'unknown' }) .expect(400); await auth('patch', '/' + card.id) .send({ version: 3, paymentSystem: null }) .expect(200); expect((await getCard(card.id)).paymentSystem).toBeNull(); expect((await createCard()).paymentSystem).toBeNull(); expect( (await createCard('1000', owner, { paymentSystem: 'mastercard' })) .paymentSystem, ).toBe('mastercard'); }); 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) 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) ).body as BankOperationsPageDto; expect(history.total).toBe(2); expect(history.items.map((item) => item.date)).toEqual(['2026-08-01']); expect(history.hasNext).toBe(true); const result = ( await auth('get', `/summary?cardId=${card.id}&${range}`).expect(200) ).body as BankSummaryDto; expect(result.net.value).toBe('20'); const single = ( await auth( 'get', `/${card.id}/operations?from=2026-07-31&to=2026-07-31`, ).expect(200) ).body as BankOperationsPageDto; expect(single.total).toBe(1); for (const invalid of [ '', 'from=2026-07-31', 'to=2026-08-01', 'from=2026-08-02&to=2026-08-01', 'from=2026-02-30&to=2026-08-01', `${range}&month=2026-08`, ]) { await auth('get', `/${card.id}/operations?${invalid}`).expect(400); await auth('get', `/summary?${invalid}`).expect(400); } }); });