From 255d88cdb7c8828496561d301273eb5af86e91c2 Mon Sep 17 00:00:00 2001 From: Artyom Gorushkin Date: Sat, 14 Mar 2026 08:57:28 +0300 Subject: [PATCH] refactor: update transaction controller tests and schemas for improved validation and structure --- .../transaction.controller.test.ts | 426 +++++++++--------- .../shared/src/validation/transactions.ts | 12 - 2 files changed, 213 insertions(+), 225 deletions(-) diff --git a/apps/backend/src/interfaces/transactions/transaction.controller.test.ts b/apps/backend/src/interfaces/transactions/transaction.controller.test.ts index 9969567..c6104e7 100644 --- a/apps/backend/src/interfaces/transactions/transaction.controller.test.ts +++ b/apps/backend/src/interfaces/transactions/transaction.controller.test.ts @@ -1,217 +1,217 @@ -import { describe, it, expect } from 'vitest'; +import { TransactionCreateInput } from '@ledgerly/shared/validation'; +import { + CreateOperationRequestDTO, + UpdateTransactionRequestDTO, +} from 'src/application'; +import { CreateTransactionUseCase } from 'src/application/usecases/transaction/CreateTransaction'; +import { GetAllTransactionsUseCase } from 'src/application/usecases/transaction/GetAllTransactions'; +import { GetTransactionByIdUseCase } from 'src/application/usecases/transaction/GetTransactionById'; +import { UpdateTransactionUseCase } from 'src/application/usecases/transaction/UpdateTransaction'; +import { User } from 'src/domain'; +import { Amount, Currency, DateValue, Id } from 'src/domain/domain-core'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { ZodError } from 'zod'; + +import { createUser } from '../helpers'; + +import { TransactionController } from './transaction.controller'; describe('TransactionController', () => { - it('should have tests implemented', () => { - // Placeholder test to ensure the test suite runs - expect(true).toBe(true); + let user: User; + + const mockTransaction = { data: 'mockTransaction' }; + const mockTransactions = [{ data: 'mockTransaction' }]; + + const mockCreateTransactionUseCase = { + execute: vi.fn().mockResolvedValue(mockTransaction), + }; + + const mockGetTransactionByIdUseCase = { + execute: vi.fn().mockResolvedValue(mockTransaction), + }; + + const mockGetAllTransactionsUseCase = { + execute: vi.fn().mockResolvedValue(mockTransactions), + }; + + const mockUpdateTransactionUseCase = { + execute: vi.fn().mockResolvedValue(mockTransaction), + }; + + const operation1: CreateOperationRequestDTO = { + accountId: Id.create().valueOf(), + amount: Amount.create('-100').valueOf(), + description: 'Test Operation From', + value: Amount.create('-100').valueOf(), + }; + + const operation2: CreateOperationRequestDTO = { + accountId: Id.create().valueOf(), + amount: Amount.create('100').valueOf(), + description: 'Test Operation To', + value: Amount.create('100').valueOf(), + }; + + const operations: CreateOperationRequestDTO[] = [operation1, operation2]; + + const transactionController = new TransactionController( + mockCreateTransactionUseCase as unknown as CreateTransactionUseCase, + mockGetTransactionByIdUseCase as unknown as GetTransactionByIdUseCase, + mockGetAllTransactionsUseCase as unknown as GetAllTransactionsUseCase, + mockUpdateTransactionUseCase as unknown as UpdateTransactionUseCase, + ); + + beforeAll(async () => { + user = await createUser(); + }); + + beforeEach(() => { + vi.clearAllMocks(); }); -}); -// import { -// EntryCreateInput, -// TransactionCreateInput, -// } from '@ledgerly/shared/validation'; -// import { UpdateTransactionRequestDTO } from 'src/application'; -// import { CreateTransactionUseCase } from 'src/application/usecases/transaction/CreateTransaction'; -// import { GetAllTransactionsUseCase } from 'src/application/usecases/transaction/GetAllTransactions'; -// import { GetTransactionByIdUseCase } from 'src/application/usecases/transaction/GetTransactionById'; -// import { UpdateTransactionUseCase } from 'src/application/usecases/transaction/UpdateTransaction'; -// import { User } from 'src/domain'; -// import { Amount, DateValue, Id } from 'src/domain/domain-core'; -// import { beforeEach, describe, expect, it, vi } from 'vitest'; -// import { ZodError } from 'zod'; - -// import { createUser } from '../helpers'; - -// import { TransactionController } from './transaction.controller'; - -// describe('TransactionController', () => { -// let user: User; - -// const mockTransaction = { data: 'mockTransaction' }; -// const mockTransactions = [{ data: 'mockTransaction' }]; - -// const mockCreateTransactionUseCase = { -// execute: vi.fn().mockResolvedValue(mockTransaction), -// }; - -// const mockGetTransactionByIdUseCase = { -// execute: vi.fn().mockResolvedValue(mockTransaction), -// }; - -// const mockGetAllTransactionsUseCase = { -// execute: vi.fn().mockResolvedValue(mockTransactions), -// }; - -// const mockUpdateTransactionUseCase = { -// execute: vi.fn().mockResolvedValue(mockTransaction), -// }; - -// const operationFrom = { -// accountId: Id.create().valueOf(), -// amount: Amount.create('-100').valueOf(), -// description: 'Test Operation From', -// }; - -// const operationTo = { -// accountId: Id.create().valueOf(), -// amount: Amount.create('100').valueOf(), -// description: 'Test Operation To', -// }; - -// const entries: EntryCreateInput[] = [ -// { description: 'Test Entry', operations: [operationFrom, operationTo] }, -// ]; - -// const transactionController = new TransactionController( -// mockCreateTransactionUseCase as unknown as CreateTransactionUseCase, -// mockGetTransactionByIdUseCase as unknown as GetTransactionByIdUseCase, -// mockGetAllTransactionsUseCase as unknown as GetAllTransactionsUseCase, -// mockUpdateTransactionUseCase as unknown as UpdateTransactionUseCase, -// ); - -// beforeEach(async () => { -// user = await createUser(); -// }); - -// describe('create', () => { -// it('should call CreateTransactionUseCase with correct parameters', async () => { -// const requestBody: TransactionCreateInput = { -// description: 'Test Transaction', -// entries, -// postingDate: DateValue.restore('2024-01-01').valueOf(), -// transactionDate: DateValue.restore('2024-01-02').valueOf(), -// }; - -// const result = await transactionController.create(user, requestBody); - -// expect(mockCreateTransactionUseCase.execute).toHaveBeenCalledWith( -// user, -// expect.objectContaining({ -// description: 'Test Transaction', -// entries, -// postingDate: '2024-01-01', -// transactionDate: '2024-01-02', -// }), -// ); - -// expect(mockCreateTransactionUseCase.execute).toHaveBeenCalledTimes(1); - -// expect(result).toEqual(mockTransaction); -// }); -// }); - -// describe('getById', () => { -// it('should call GetTransactionByIdUseCase with correct parameters', async () => { -// const transactionId = Id.create().valueOf(); - -// const result = await transactionController.getById(user, transactionId); - -// expect(mockGetTransactionByIdUseCase.execute).toHaveBeenCalledWith( -// user.id, -// transactionId, -// ); - -// expect(mockGetTransactionByIdUseCase.execute).toHaveBeenCalledTimes(1); - -// expect(result).equals(mockTransaction); -// }); -// }); - -// describe('getAll', () => { -// it('should call GetAllTransactionsUseCase with correct parameters', async () => { -// const accountId = Id.create().valueOf(); - -// mockGetAllTransactionsUseCase.execute.mockResolvedValue([ -// mockTransaction, -// ]); - -// const result = await transactionController.getAll(user, { -// accountId, -// }); - -// expect(mockGetAllTransactionsUseCase.execute).toHaveBeenCalledWith( -// user.id, -// { accountId }, -// ); - -// expect(mockGetAllTransactionsUseCase.execute).toHaveBeenCalledTimes(1); - -// expect(result).toEqual(mockTransactions); -// }); -// }); - -// describe('update', () => { -// it.skip('should call UpdateTransactionUseCase with correct parameters', async () => { -// const transactionId = Id.create().valueOf(); - -// const requestBody: UpdateTransactionRequestDTO = { -// description: 'Updated Transaction', -// entries: { -// create: [], -// delete: [], -// update: [ -// { -// description: 'Test Entry', -// id: Id.create().valueOf(), -// operations: [ -// { -// accountId: operationFrom.accountId, -// amount: operationFrom.amount, -// description: operationFrom.description, -// }, -// { -// accountId: operationFrom.accountId, -// amount: operationFrom.amount, -// description: operationFrom.description, -// }, -// ], -// }, -// ], -// }, -// postingDate: DateValue.restore('2024-01-01').valueOf(), -// transactionDate: DateValue.restore('2024-01-02').valueOf(), -// }; - -// const result = await transactionController.update( -// user, -// transactionId, -// requestBody as unknown as UpdateTransactionRequestDTO, -// ); - -// expect(mockUpdateTransactionUseCase.execute).toHaveBeenCalledWith( -// user, -// transactionId, -// expect.objectContaining({ -// description: requestBody.description, -// entries: requestBody.entries, -// postingDate: requestBody.postingDate, -// transactionDate: requestBody.transactionDate, -// }), -// ); - -// expect(mockUpdateTransactionUseCase.execute).toHaveBeenCalledTimes(1); - -// expect(result).toEqual(mockTransaction); -// }); - -// it('should throw validation error for invalid data', async () => { -// const transactionId = Id.create().valueOf(); - -// const invalidRequestBody = { -// description: 'Updated Transaction', -// entries: [], -// postingDate: 'invalid-date', -// transactionDate: '2024-01-02', -// }; - -// await expect( -// transactionController.update( -// user, -// transactionId, -// invalidRequestBody as unknown as UpdateTransactionRequestDTO, -// ), -// ).rejects.toThrow(ZodError); -// }); -// }); -// }); + describe('create', () => { + it('should call CreateTransactionUseCase with correct parameters', async () => { + const requestBody: TransactionCreateInput = { + currencyCode: Currency.create('USD').valueOf(), + description: 'Test Transaction', + operations, + postingDate: DateValue.restore('2024-01-01').valueOf(), + transactionDate: DateValue.restore('2024-01-02').valueOf(), + }; + + const result = await transactionController.create(user, requestBody); + + expect(mockCreateTransactionUseCase.execute).toHaveBeenCalledWith( + user, + expect.objectContaining({ + currencyCode: requestBody.currencyCode, + description: requestBody.description, + operations: requestBody.operations, + postingDate: requestBody.postingDate, + transactionDate: requestBody.transactionDate, + }), + ); + + expect(mockCreateTransactionUseCase.execute).toHaveBeenCalledTimes(1); + + expect(result).toEqual(mockTransaction); + }); + + it('should throw ZodError for invalid request body', async () => { + const invalidRequestBody = { + currencyCode: 123, + description: 'Test Transaction', + operations: [], + postingDate: 'invalid-date', + transactionDate: '2024-01-02', + }; + + await expect( + transactionController.create( + user, + invalidRequestBody as unknown as TransactionCreateInput, + ), + ).rejects.toThrow(ZodError); + }); + }); + + describe('getById', () => { + it('should call GetTransactionByIdUseCase with correct parameters', async () => { + const transactionId = Id.create().valueOf(); + const result = await transactionController.getById(user, transactionId); + expect(mockGetTransactionByIdUseCase.execute).toHaveBeenCalledWith( + user.id, + transactionId, + ); + expect(mockGetTransactionByIdUseCase.execute).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockTransaction); + }); + }); + + describe('getAll', () => { + it('should call GetAllTransactionsUseCase with correct parameters', async () => { + const accountId = Id.create().valueOf(); + mockGetAllTransactionsUseCase.execute.mockResolvedValue([ + mockTransaction, + ]); + + const result = await transactionController.getAll(user, { + accountId, + }); + + expect(mockGetAllTransactionsUseCase.execute).toHaveBeenCalledWith( + user.id, + { accountId }, + ); + + expect(mockGetAllTransactionsUseCase.execute).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockTransactions); + }); + }); + + describe('update', () => { + it('should call UpdateTransactionUseCase with correct parameters', async () => { + const transactionId = Id.create().valueOf(); + const requestBody: UpdateTransactionRequestDTO = { + description: 'Updated Transaction', + operations: { + create: [ + { + accountId: operation1.accountId, + amount: operation1.amount, + description: operation1.description, + value: operation1.value, + }, + { + accountId: operation2.accountId, + amount: operation2.amount, + description: operation2.description, + value: operation2.value, + }, + ], + delete: [], + update: [], + }, + postingDate: DateValue.restore('2024-01-01').valueOf(), + transactionDate: DateValue.restore('2024-01-02').valueOf(), + }; + + const result = await transactionController.update( + user, + transactionId, + requestBody as unknown as UpdateTransactionRequestDTO, + ); + + expect(mockUpdateTransactionUseCase.execute).toHaveBeenCalledWith( + user, + transactionId, + expect.objectContaining({ + description: requestBody.description, + postingDate: requestBody.postingDate, + transactionDate: requestBody.transactionDate, + }), + ); + expect(mockUpdateTransactionUseCase.execute).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockTransaction); + }); + + it('should throw validation error for invalid data', async () => { + const transactionId = Id.create().valueOf(); + + const invalidRequestBody = { + description: 'Updated Transaction', + entries: [], + postingDate: 'invalid-date', + transactionDate: '2024-01-02', + }; + + await expect( + transactionController.update( + user, + transactionId, + invalidRequestBody as unknown as UpdateTransactionRequestDTO, + ), + ).rejects.toThrow(ZodError); + }); + }); +}); diff --git a/packages/shared/src/validation/transactions.ts b/packages/shared/src/validation/transactions.ts index cfed91b..6f5644c 100644 --- a/packages/shared/src/validation/transactions.ts +++ b/packages/shared/src/validation/transactions.ts @@ -29,17 +29,6 @@ export const operationUpdateSchema = z.object({ value: moneyAmountString, }); -export const entryCreateSchema = z.object({ - description: requiredText, - operations: z.tuple([operationCreateSchema, operationCreateSchema]), -}); - -export const entryUpdateSchema = z.object({ - description: requiredText, - id: uuid, - operations: z.tuple([operationUpdateSchema, operationUpdateSchema]), -}); - export const transactionCreateSchema = z.object({ currencyCode: currencyCode, description: requiredText, @@ -73,4 +62,3 @@ export const transactionResponseSchema = z.object({ export type TransactionCreateInput = z.infer; export type TransactionUpdateInput = z.infer; export type TransactionResponse = z.infer; -export type EntryCreateInput = z.infer;