From fbdacf60f78ee93acd41dc88d98b7c1e237bb008 Mon Sep 17 00:00:00 2001 From: joelson brito Date: Fri, 7 Nov 2025 11:07:28 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20adiciona=20valida=C3=A7=C3=B5es=20e=20t?= =?UTF-8?q?estes=20para=20m=C3=A9todos=20do=20data-consult=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adiciona validações para stores(), sellers(), billings(), customers(), getAllProducts(), getAllCarriers() e getRegions() - Valida se dados retornados têm propriedades necessárias - Valida se resultado do repository é array válido - Filtra dados incompletos ou com propriedades inválidas - Refatora testes criando helper para reduzir duplicação - Adiciona testes que expõem problemas e validam correções - Melhora tratamento de erros com logging adequado --- .../data-consult.service.spec.helper.ts | 82 +++ .../__tests__/data-consult.service.spec.ts | 692 ++++++++++-------- src/data-consult/data-consult.service.ts | 129 +++- 3 files changed, 603 insertions(+), 300 deletions(-) create mode 100644 src/data-consult/__tests__/data-consult.service.spec.helper.ts diff --git a/src/data-consult/__tests__/data-consult.service.spec.helper.ts b/src/data-consult/__tests__/data-consult.service.spec.helper.ts new file mode 100644 index 0000000..fb8ac01 --- /dev/null +++ b/src/data-consult/__tests__/data-consult.service.spec.helper.ts @@ -0,0 +1,82 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DataConsultService } from '../data-consult.service'; +import { DataConsultRepository } from '../data-consult.repository'; +import { ILogger } from '../../Log/ILogger'; +import { IRedisClient } from '../../core/configs/cache/IRedisClient'; +import { RedisClientToken } from '../../core/configs/cache/redis-client.adapter.provider'; +import { DataSource } from 'typeorm'; +import { DATA_SOURCE } from '../../core/constants'; + +export const createMockRepository = (methods: Partial = {}) => ({ + findStores: jest.fn(), + findSellers: jest.fn(), + findBillings: jest.fn(), + findCustomers: jest.fn(), + findAllProducts: jest.fn(), + findAllCarriers: jest.fn(), + findRegions: jest.fn(), + ...methods, +} as any); + +export const createMockLogger = () => ({ + log: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + debug: jest.fn(), +} as any); + +export const createMockRedisClient = () => ({ + get: jest.fn().mockResolvedValue(null), + set: jest.fn().mockResolvedValue(undefined), +} as any); + +export interface DataConsultServiceTestContext { + service: DataConsultService; + mockRepository: jest.Mocked; + mockLogger: jest.Mocked; + mockRedisClient: jest.Mocked; + mockDataSource: jest.Mocked; +} + +export async function createDataConsultServiceTestModule( + repositoryMethods: Partial = {}, + redisClientMethods: Partial = {} +): Promise { + const mockRepository = createMockRepository(repositoryMethods); + const mockLogger = createMockLogger(); + const mockRedisClient = { ...createMockRedisClient(), ...redisClientMethods } as any; + const mockDataSource = {} as any; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + DataConsultService, + { + provide: DataConsultRepository, + useValue: mockRepository, + }, + { + provide: RedisClientToken, + useValue: mockRedisClient, + }, + { + provide: 'LoggerService', + useValue: mockLogger, + }, + { + provide: DATA_SOURCE, + useValue: mockDataSource, + }, + ], + }).compile(); + + const service = module.get(DataConsultService); + + return { + service, + mockRepository, + mockLogger, + mockRedisClient, + mockDataSource, + }; +} + diff --git a/src/data-consult/__tests__/data-consult.service.spec.ts b/src/data-consult/__tests__/data-consult.service.spec.ts index 8b44134..ff3d9f0 100644 --- a/src/data-consult/__tests__/data-consult.service.spec.ts +++ b/src/data-consult/__tests__/data-consult.service.spec.ts @@ -1,314 +1,436 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { HttpException, HttpStatus } from '@nestjs/common'; -import { DataConsultService } from '../data-consult.service'; -import { DataConsultRepository } from '../data-consult.repository'; -import { StoreDto } from '../dto/store.dto'; -import { SellerDto } from '../dto/seller.dto'; -import { ILogger } from '../../Log/ILogger'; -import { IRedisClient } from '../../core/configs/cache/IRedisClient'; -import { RedisClientToken } from '../../core/configs/cache/redis-client.adapter.provider'; -import { DataSource } from 'typeorm'; -import { DATA_SOURCE } from '../../core/constants'; +import { HttpException } from '@nestjs/common'; +import { createDataConsultServiceTestModule } from './data-consult.service.spec.helper'; -describe('DataConsultService - stores', () => { - let service: DataConsultService; - let mockRepository: jest.Mocked; - let mockLogger: jest.Mocked; - let mockRedisClient: jest.Mocked; - let mockDataSource: jest.Mocked; +describe('DataConsultService', () => { + describe('stores', () => { + let context: Awaited>; - beforeEach(async () => { - mockRepository = { - findStores: jest.fn(), - findSellers: jest.fn(), - } as any; + beforeEach(async () => { + context = await createDataConsultServiceTestModule(); + }); - mockLogger = { - log: jest.fn(), - error: jest.fn(), - warn: jest.fn(), - debug: jest.fn(), - } as any; + afterEach(() => { + jest.clearAllMocks(); + }); - mockRedisClient = { - get: jest.fn(), - set: jest.fn(), - } as any; + describe('Tests that expose problems', () => { + it('should validate that all stores have required properties (id, name, store)', async () => { + context.mockRepository.findStores.mockResolvedValue([ + { id: '001', name: 'Loja 1' }, + { id: '002', store: '002 - Loja 2' }, + { id: '003', name: 'Loja 3', store: '003 - Loja 3' }, + ] as any); - mockDataSource = {} as any; + const result = await context.service.stores(); - const module: TestingModule = await Test.createTestingModule({ - providers: [ - DataConsultService, - { - provide: DataConsultRepository, - useValue: mockRepository, - }, - { - provide: RedisClientToken, - useValue: mockRedisClient, - }, - { - provide: 'LoggerService', - useValue: mockLogger, - }, - { - provide: DATA_SOURCE, - useValue: mockDataSource, - }, - ], - }).compile(); + result.forEach(store => { + expect(store.id).toBeDefined(); + expect(store.name).toBeDefined(); + expect(store.store).toBeDefined(); + }); + }); - service = module.get(DataConsultService); - }); + it('should handle null or undefined result from repository', async () => { + context.mockRepository.findStores.mockResolvedValue(null as any); + await expect(context.service.stores()).rejects.toThrow(); + }); - afterEach(() => { - jest.clearAllMocks(); - }); + it('should validate that repository result is an array', async () => { + context.mockRepository.findStores.mockResolvedValue({ id: '001', name: 'Loja 1' } as any); + const result = await context.service.stores(); + expect(Array.isArray(result)).toBe(true); + }); - describe('stores - Tests that expose problems', () => { - /** - * NOTA: Estes testes identificam problemas no método stores. - * - * PROBLEMAS IDENTIFICADOS: - * 1. Não valida se os dados retornados têm todas as propriedades necessárias - * 2. Não valida se o resultado do repository é um array válido - * 3. Permite criar StoreDto com propriedades undefined - * 4. Não trata o caso onde o repository retorna dados incompletos - */ + it('should validate that required properties are not empty strings', async () => { + context.mockRepository.findStores.mockResolvedValue([ + { id: '', name: 'Loja 1', store: '001 - Loja 1' }, + { id: '002', name: '', store: '002 - Loja 2' }, + { id: '003', name: 'Loja 3', store: '' }, + ] as any); - it('should validate that all stores have required properties (id, name, store)', async () => { - /** - * Cenário: Repository retorna dados incompletos (faltando propriedades). - * Problema: StoreDto é criado com propriedades undefined. - * Solução esperada: Validar dados antes de criar StoreDto ou lançar exceção. - */ - mockRepository.findStores.mockResolvedValue([ - { id: '001', name: 'Loja 1' }, // falta propriedade 'store' - { id: '002', store: '002 - Loja 2' }, // falta propriedade 'name' - { id: '003', name: 'Loja 3', store: '003 - Loja 3' }, // completo - ] as any); + const result = await context.service.stores(); + result.forEach(store => { + expect(store.id).not.toBe(''); + expect(store.name).not.toBe(''); + expect(store.store).not.toBe(''); + }); + }); - const result = await service.stores(); - - // Este teste falha porque o método atual não valida as propriedades - // e permite criar StoreDto com propriedades undefined - result.forEach(store => { - expect(store.id).toBeDefined(); - expect(store.name).toBeDefined(); - expect(store.store).toBeDefined(); + it('should log error when repository throws exception', async () => { + const repositoryError = new Error('Database connection failed'); + context.mockRepository.findStores.mockRejectedValue(repositoryError); + await expect(context.service.stores()).rejects.toThrow(HttpException); + expect(context.mockLogger.error).toHaveBeenCalledWith('Erro ao buscar lojas', repositoryError); }); }); + }); - it('should handle null or undefined result from repository', async () => { - /** - * Cenário: Repository retorna null ou undefined. - * Problema: Método tenta fazer map em null/undefined causando erro. - * Solução esperada: Validar se o resultado é um array antes de fazer map. - */ - mockRepository.findStores.mockResolvedValue(null as any); + describe('sellers', () => { + let context: Awaited>; - await expect(service.stores()).rejects.toThrow(); + beforeEach(async () => { + context = await createDataConsultServiceTestModule(); }); - it('should validate that repository result is an array', async () => { - /** - * Cenário: Repository retorna um objeto em vez de array. - * Problema: Método tenta fazer map em objeto causando comportamento inesperado. - * Solução esperada: Validar se o resultado é um array. - */ - mockRepository.findStores.mockResolvedValue({ id: '001', name: 'Loja 1' } as any); - - const result = await service.stores(); - - // Este teste falha porque o método não valida se o resultado é um array - expect(Array.isArray(result)).toBe(true); + afterEach(() => { + jest.clearAllMocks(); }); - it('should validate that required properties are not empty strings', async () => { - /** - * Cenário: Repository retorna dados com propriedades vazias. - * Problema: StoreDto é criado com strings vazias, o que pode causar problemas. - * Solução esperada: Validar se as propriedades são strings não vazias. - */ - mockRepository.findStores.mockResolvedValue([ - { id: '', name: 'Loja 1', store: '001 - Loja 1' }, // id vazio - { id: '002', name: '', store: '002 - Loja 2' }, // name vazio - { id: '003', name: 'Loja 3', store: '' }, // store vazio - ] as any); + describe('Tests that expose problems', () => { + it('should validate that all sellers have required properties (id, name)', async () => { + context.mockRepository.findSellers.mockResolvedValue([ + { id: '001' }, + { name: 'Vendedor 2' }, + { id: '003', name: 'Vendedor 3' }, + ] as any); - const result = await service.stores(); + const result = await context.service.sellers(); + result.forEach(seller => { + expect(seller.id).toBeDefined(); + expect(seller.name).toBeDefined(); + }); + }); - // Este teste falha porque o método não valida se as propriedades são não vazias - result.forEach(store => { - expect(store.id).not.toBe(''); - expect(store.name).not.toBe(''); - expect(store.store).not.toBe(''); + it('should handle null or undefined result from repository', async () => { + context.mockRepository.findSellers.mockResolvedValue(null as any); + await expect(context.service.sellers()).rejects.toThrow(); + }); + + it('should validate that repository result is an array', async () => { + context.mockRepository.findSellers.mockResolvedValue({ id: '001', name: 'Vendedor 1' } as any); + const result = await context.service.sellers(); + expect(Array.isArray(result)).toBe(true); + }); + + it('should validate that required properties are not empty strings', async () => { + context.mockRepository.findSellers.mockResolvedValue([ + { id: '', name: 'Vendedor 1' }, + { id: '002', name: '' }, + ] as any); + + const result = await context.service.sellers(); + result.forEach(seller => { + expect(seller.id).not.toBe(''); + expect(seller.name).not.toBe(''); + }); + }); + + it('should log error when repository throws exception', async () => { + const repositoryError = new Error('Database connection failed'); + context.mockRepository.findSellers.mockRejectedValue(repositoryError); + await expect(context.service.sellers()).rejects.toThrow(HttpException); + expect(context.mockLogger.error).toHaveBeenCalledWith('Erro ao buscar vendedores', repositoryError); }); }); + }); - it('should log error when repository throws exception', async () => { - /** - * Cenário: Repository lança uma exceção. - * Problema: Método deve logar o erro antes de lançar HttpException. - * Solução esperada: Logger.error deve ser chamado com a mensagem correta. - */ - const repositoryError = new Error('Database connection failed'); - mockRepository.findStores.mockRejectedValue(repositoryError); + describe('billings', () => { + let context: Awaited>; - await expect(service.stores()).rejects.toThrow(HttpException); + beforeEach(async () => { + context = await createDataConsultServiceTestModule(); + }); - expect(mockLogger.error).toHaveBeenCalledWith('Erro ao buscar lojas', repositoryError); + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Tests that expose problems', () => { + it('should validate that all billings have required properties (id, date, total)', async () => { + context.mockRepository.findBillings.mockResolvedValue([ + { id: '001' }, + { date: new Date(), total: 1000 }, + { id: '003', date: new Date(), total: 2000 }, + ] as any); + + const result = await context.service.billings(); + result.forEach(billing => { + expect(billing.id).toBeDefined(); + expect(billing.date).toBeDefined(); + expect(billing.total).toBeDefined(); + }); + }); + + it('should handle null or undefined result from repository', async () => { + context.mockRepository.findBillings.mockResolvedValue(null as any); + await expect(context.service.billings()).rejects.toThrow(); + }); + + it('should validate that repository result is an array', async () => { + context.mockRepository.findBillings.mockResolvedValue({ id: '001', date: new Date(), total: 1000 } as any); + const result = await context.service.billings(); + expect(Array.isArray(result)).toBe(true); + }); + + it('should validate that required properties are not empty or invalid', async () => { + context.mockRepository.findBillings.mockResolvedValue([ + { id: '', date: new Date(), total: 1000 }, + { id: '002', date: null, total: 1000 }, + { id: '003', date: new Date(), total: null }, + ] as any); + + const result = await context.service.billings(); + result.forEach(billing => { + expect(billing.id).not.toBe(''); + expect(billing.date).toBeDefined(); + expect(billing.total).toBeDefined(); + }); + }); + + it('should log error when repository throws exception', async () => { + const repositoryError = new Error('Database connection failed'); + context.mockRepository.findBillings.mockRejectedValue(repositoryError); + await expect(context.service.billings()).rejects.toThrow(HttpException); + expect(context.mockLogger.error).toHaveBeenCalledWith('Erro ao buscar faturamento', repositoryError); + }); + }); + }); + + describe('customers', () => { + let context: Awaited>; + + beforeEach(async () => { + context = await createDataConsultServiceTestModule(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Tests that expose problems', () => { + it('should validate that all customers have required properties (id, name, document)', async () => { + context.mockRepository.findCustomers.mockResolvedValue([ + { id: '001', name: 'Cliente 1' }, + { id: '002', document: '12345678900' }, + { id: '003', name: 'Cliente 3', document: '12345678901' }, + ] as any); + + const result = await context.service.customers('test'); + result.forEach(customer => { + expect(customer.id).toBeDefined(); + expect(customer.name).toBeDefined(); + expect(customer.document).toBeDefined(); + }); + }); + + it('should handle null or undefined result from repository', async () => { + context.mockRepository.findCustomers.mockResolvedValue(null as any); + await expect(context.service.customers('test')).rejects.toThrow(); + }); + + it('should validate that repository result is an array', async () => { + context.mockRepository.findCustomers.mockResolvedValue({ id: '001', name: 'Cliente 1', document: '12345678900' } as any); + const result = await context.service.customers('test'); + expect(Array.isArray(result)).toBe(true); + }); + + it('should validate that required properties are not empty strings', async () => { + context.mockRepository.findCustomers.mockResolvedValue([ + { id: '', name: 'Cliente 1', document: '12345678900' }, + { id: '002', name: '', document: '12345678901' }, + { id: '003', name: 'Cliente 3', document: '' }, + ] as any); + + const result = await context.service.customers('test'); + result.forEach(customer => { + expect(customer.id).not.toBe(''); + expect(customer.name).not.toBe(''); + expect(customer.document).not.toBe(''); + }); + }); + + it('should log error when repository throws exception', async () => { + const repositoryError = new Error('Database connection failed'); + context.mockRepository.findCustomers.mockRejectedValue(repositoryError); + await expect(context.service.customers('test')).rejects.toThrow(HttpException); + expect(context.mockLogger.error).toHaveBeenCalledWith('Erro ao buscar clientes', repositoryError); + }); + }); + }); + + describe('getAllProducts', () => { + let context: Awaited>; + + beforeEach(async () => { + context = await createDataConsultServiceTestModule(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Tests that expose problems', () => { + it('should validate that all products have required properties (id, name, manufacturerCode)', async () => { + context.mockRepository.findAllProducts.mockResolvedValue([ + { id: '001', name: 'Produto 1' }, + { id: '002', manufacturerCode: 'FAB001' }, + { id: '003', name: 'Produto 3', manufacturerCode: 'FAB003' }, + ] as any); + + const result = await context.service.getAllProducts(); + result.forEach(product => { + expect(product.id).toBeDefined(); + expect(product.name).toBeDefined(); + expect(product.manufacturerCode).toBeDefined(); + }); + }); + + it('should handle null or undefined result from repository', async () => { + context.mockRepository.findAllProducts.mockResolvedValue(null as any); + await expect(context.service.getAllProducts()).rejects.toThrow(); + }); + + it('should validate that repository result is an array', async () => { + context.mockRepository.findAllProducts.mockResolvedValue({ id: '001', name: 'Produto 1', manufacturerCode: 'FAB001' } as any); + const result = await context.service.getAllProducts(); + expect(Array.isArray(result)).toBe(true); + }); + + it('should validate that required properties are not empty strings', async () => { + context.mockRepository.findAllProducts.mockResolvedValue([ + { id: '', name: 'Produto 1', manufacturerCode: 'FAB001' }, + { id: '002', name: '', manufacturerCode: 'FAB002' }, + { id: '003', name: 'Produto 3', manufacturerCode: '' }, + ] as any); + + const result = await context.service.getAllProducts(); + result.forEach(product => { + expect(product.id).not.toBe(''); + expect(product.name).not.toBe(''); + expect(product.manufacturerCode).not.toBe(''); + }); + }); + + it('should log error when repository throws exception', async () => { + const repositoryError = new Error('Database connection failed'); + context.mockRepository.findAllProducts.mockRejectedValue(repositoryError); + await expect(context.service.getAllProducts()).rejects.toThrow(HttpException); + expect(context.mockLogger.error).toHaveBeenCalledWith('Erro ao buscar todos os produtos', repositoryError); + }); + }); + }); + + describe('getAllCarriers', () => { + let context: Awaited>; + + beforeEach(async () => { + context = await createDataConsultServiceTestModule(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Tests that expose problems', () => { + it('should validate that all carriers have required properties (carrierId, carrierName, carrierDescription)', async () => { + context.mockRepository.findAllCarriers.mockResolvedValue([ + { carrierId: '001', carrierName: 'Transportadora 1' }, + { carrierName: 'Transportadora 2', carrierDescription: '002 - Transportadora 2' }, + { carrierId: '003', carrierName: 'Transportadora 3', carrierDescription: '003 - Transportadora 3' }, + ] as any); + + const result = await context.service.getAllCarriers(); + result.forEach(carrier => { + expect(carrier.carrierId).toBeDefined(); + expect(carrier.carrierName).toBeDefined(); + expect(carrier.carrierDescription).toBeDefined(); + }); + }); + + it('should handle null or undefined result from repository', async () => { + context.mockRepository.findAllCarriers.mockResolvedValue(null as any); + await expect(context.service.getAllCarriers()).rejects.toThrow(); + }); + + it('should validate that repository result is an array', async () => { + context.mockRepository.findAllCarriers.mockResolvedValue({ carrierId: '001', carrierName: 'Transportadora 1', carrierDescription: '001 - Transportadora 1' } as any); + const result = await context.service.getAllCarriers(); + expect(Array.isArray(result)).toBe(true); + }); + + it('should validate that required properties are not empty strings', async () => { + context.mockRepository.findAllCarriers.mockResolvedValue([ + { carrierId: '', carrierName: 'Transportadora 1', carrierDescription: '001 - Transportadora 1' }, + { carrierId: '002', carrierName: '', carrierDescription: '002 - Transportadora 2' }, + { carrierId: '003', carrierName: 'Transportadora 3', carrierDescription: '' }, + ] as any); + + const result = await context.service.getAllCarriers(); + result.forEach(carrier => { + expect(carrier.carrierId).not.toBe(''); + expect(carrier.carrierName).not.toBe(''); + expect(carrier.carrierDescription).not.toBe(''); + }); + }); + + it('should log error when repository throws exception', async () => { + const repositoryError = new Error('Database connection failed'); + context.mockRepository.findAllCarriers.mockRejectedValue(repositoryError); + await expect(context.service.getAllCarriers()).rejects.toThrow(HttpException); + expect(context.mockLogger.error).toHaveBeenCalledWith('Erro ao buscar transportadoras', repositoryError); + }); + }); + }); + + describe('getRegions', () => { + let context: Awaited>; + + beforeEach(async () => { + context = await createDataConsultServiceTestModule(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Tests that expose problems', () => { + it('should validate that all regions have required properties (numregiao, regiao)', async () => { + context.mockRepository.findRegions.mockResolvedValue([ + { numregiao: 1 }, + { regiao: 'Região Sul' }, + { numregiao: 3, regiao: 'Região Norte' }, + ] as any); + + const result = await context.service.getRegions(); + result.forEach(region => { + expect(region.numregiao).toBeDefined(); + expect(region.regiao).toBeDefined(); + }); + }); + + it('should handle null or undefined result from repository', async () => { + context.mockRepository.findRegions.mockResolvedValue(null as any); + await expect(context.service.getRegions()).rejects.toThrow(); + }); + + it('should validate that repository result is an array', async () => { + context.mockRepository.findRegions.mockResolvedValue({ numregiao: 1, regiao: 'Região Sul' } as any); + const result = await context.service.getRegions(); + expect(Array.isArray(result)).toBe(true); + }); + + it('should validate that required properties are not empty or invalid', async () => { + context.mockRepository.findRegions.mockResolvedValue([ + { numregiao: null, regiao: 'Região Sul' }, + { numregiao: 2, regiao: '' }, + { numregiao: 3, regiao: 'Região Norte' }, + ] as any); + + const result = await context.service.getRegions(); + result.forEach(region => { + expect(region.numregiao).toBeDefined(); + expect(region.numregiao).not.toBeNull(); + expect(region.regiao).toBeDefined(); + expect(region.regiao).not.toBe(''); + }); + }); + + it('should log error when repository throws exception', async () => { + const repositoryError = new Error('Database connection failed'); + context.mockRepository.findRegions.mockRejectedValue(repositoryError); + await expect(context.service.getRegions()).rejects.toThrow(HttpException); + expect(context.mockLogger.error).toHaveBeenCalledWith('Erro ao buscar regiões', repositoryError); + }); }); }); }); - -describe('DataConsultService - sellers', () => { - let service: DataConsultService; - let mockRepository: jest.Mocked; - let mockLogger: jest.Mocked; - let mockRedisClient: jest.Mocked; - let mockDataSource: jest.Mocked; - - beforeEach(async () => { - mockRepository = { - findSellers: jest.fn(), - } as any; - - mockLogger = { - log: jest.fn(), - error: jest.fn(), - warn: jest.fn(), - debug: jest.fn(), - } as any; - - mockRedisClient = { - get: jest.fn().mockResolvedValue(null), - set: jest.fn().mockResolvedValue(undefined), - } as any; - - mockDataSource = {} as any; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - DataConsultService, - { - provide: DataConsultRepository, - useValue: mockRepository, - }, - { - provide: RedisClientToken, - useValue: mockRedisClient, - }, - { - provide: 'LoggerService', - useValue: mockLogger, - }, - { - provide: DATA_SOURCE, - useValue: mockDataSource, - }, - ], - }).compile(); - - service = module.get(DataConsultService); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('sellers - Tests that expose problems', () => { - /** - * NOTA: Estes testes identificam problemas no método sellers. - * - * PROBLEMAS IDENTIFICADOS: - * 1. Não valida se os dados retornados têm todas as propriedades necessárias - * 2. Não valida se o resultado do repository é um array válido - * 3. Permite criar SellerDto com propriedades undefined - * 4. Não trata o caso onde o repository retorna dados incompletos - */ - - it('should validate that all sellers have required properties (id, name)', async () => { - /** - * Cenário: Repository retorna dados incompletos (faltando propriedades). - * Problema: SellerDto é criado com propriedades undefined. - * Solução esperada: Validar dados antes de criar SellerDto ou lançar exceção. - */ - mockRepository.findSellers.mockResolvedValue([ - { id: '001' }, // falta propriedade 'name' - { name: 'Vendedor 2' }, // falta propriedade 'id' - { id: '003', name: 'Vendedor 3' }, // completo - ] as any); - - const result = await service.sellers(); - - // Este teste falha porque o método atual não valida as propriedades - // e permite criar SellerDto com propriedades undefined - result.forEach(seller => { - expect(seller.id).toBeDefined(); - expect(seller.name).toBeDefined(); - }); - }); - - it('should handle null or undefined result from repository', async () => { - /** - * Cenário: Repository retorna null ou undefined. - * Problema: Método tenta fazer map em null/undefined causando erro. - * Solução esperada: Validar se o resultado é um array antes de fazer map. - */ - mockRepository.findSellers.mockResolvedValue(null as any); - - await expect(service.sellers()).rejects.toThrow(); - }); - - it('should validate that repository result is an array', async () => { - /** - * Cenário: Repository retorna um objeto em vez de array. - * Problema: Método tenta fazer map em objeto causando comportamento inesperado. - * Solução esperada: Validar se o resultado é um array. - */ - mockRepository.findSellers.mockResolvedValue({ id: '001', name: 'Vendedor 1' } as any); - - const result = await service.sellers(); - - // Este teste falha porque o método não valida se o resultado é um array - expect(Array.isArray(result)).toBe(true); - }); - - it('should validate that required properties are not empty strings', async () => { - /** - * Cenário: Repository retorna dados com propriedades vazias. - * Problema: SellerDto é criado com strings vazias, o que pode causar problemas. - * Solução esperada: Validar se as propriedades são strings não vazias. - */ - mockRepository.findSellers.mockResolvedValue([ - { id: '', name: 'Vendedor 1' }, // id vazio - { id: '002', name: '' }, // name vazio - ] as any); - - const result = await service.sellers(); - - // Este teste falha porque o método não valida se as propriedades são não vazias - result.forEach(seller => { - expect(seller.id).not.toBe(''); - expect(seller.name).not.toBe(''); - }); - }); - - it('should log error when repository throws exception', async () => { - /** - * Cenário: Repository lança uma exceção. - * Problema: Método deve logar o erro antes de lançar HttpException. - * Solução esperada: Logger.error deve ser chamado com a mensagem correta. - */ - const repositoryError = new Error('Database connection failed'); - mockRepository.findSellers.mockRejectedValue(repositoryError); - - await expect(service.sellers()).rejects.toThrow(HttpException); - - expect(mockLogger.error).toHaveBeenCalledWith('Erro ao buscar vendedores', repositoryError); - }); - }); -}); - diff --git a/src/data-consult/data-consult.service.ts b/src/data-consult/data-consult.service.ts index 4d698ce..9c24dea 100644 --- a/src/data-consult/data-consult.service.ts +++ b/src/data-consult/data-consult.service.ts @@ -106,7 +106,24 @@ export class DataConsultService { this.logger.log('Buscando informações de faturamento'); try { const billings = await this.repository.findBillings(); - return billings.map(billing => new BillingDto(billing)); + + if (billings === null || billings === undefined) { + throw new HttpException('Resultado inválido do repositório', HttpStatus.INTERNAL_SERVER_ERROR); + } + + const billingsArray = Array.isArray(billings) ? billings : [billings]; + + return billingsArray + .filter(billing => { + if (!billing || typeof billing !== 'object') { + return false; + } + const hasId = billing.id !== undefined && billing.id !== null && billing.id !== ''; + const hasDate = billing.date !== undefined && billing.date !== null; + const hasTotal = billing.total !== undefined && billing.total !== null; + return hasId && hasDate && hasTotal; + }) + .map(billing => new BillingDto(billing)); } catch (error) { this.logger.error('Erro ao buscar faturamento', error); throw new HttpException('Erro ao buscar faturamento', HttpStatus.INTERNAL_SERVER_ERROR); @@ -120,7 +137,24 @@ export class DataConsultService { throw new HttpException('Filtro inválido', HttpStatus.BAD_REQUEST); } const customers = await this.repository.findCustomers(filter); - return customers.map(customer => new CustomerDto(customer)); + + if (customers === null || customers === undefined) { + throw new HttpException('Resultado inválido do repositório', HttpStatus.INTERNAL_SERVER_ERROR); + } + + const customersArray = Array.isArray(customers) ? customers : [customers]; + + return customersArray + .filter(customer => { + if (!customer || typeof customer !== 'object') { + return false; + } + const hasId = customer.id !== undefined && customer.id !== null && customer.id !== ''; + const hasName = customer.name !== undefined && customer.name !== null && customer.name !== ''; + const hasDocument = customer.document !== undefined && customer.document !== null && customer.document !== ''; + return hasId && hasName && hasDocument; + }) + .map(customer => new CustomerDto(customer)); } catch (error) { this.logger.error('Erro ao buscar clientes', error); throw new HttpException('Erro ao buscar clientes', HttpStatus.INTERNAL_SERVER_ERROR); @@ -144,13 +178,35 @@ export class DataConsultService { async getAllProducts(): Promise { this.logger.log('Buscando todos os produtos'); try { - return getOrSetCache( + return await getOrSetCache( this.redisClient, this.ALL_PRODUCTS_CACHE_KEY, this.ALL_PRODUCTS_TTL, async () => { - const products = await this.repository.findAllProducts(); - return products.map(product => new ProductDto(product)); + try { + const products = await this.repository.findAllProducts(); + + if (products === null || products === undefined) { + throw new HttpException('Resultado inválido do repositório', HttpStatus.INTERNAL_SERVER_ERROR); + } + + const productsArray = Array.isArray(products) ? products : [products]; + + return productsArray + .filter(product => { + if (!product || typeof product !== 'object') { + return false; + } + const hasId = product.id !== undefined && product.id !== null && product.id !== ''; + const hasName = product.name !== undefined && product.name !== null && product.name !== ''; + const hasManufacturerCode = product.manufacturerCode !== undefined && product.manufacturerCode !== null && product.manufacturerCode !== ''; + return hasId && hasName && hasManufacturerCode; + }) + .map(product => new ProductDto(product)); + } catch (error) { + this.logger.error('Erro ao buscar todos os produtos', error); + throw error; + } } ); } catch (error) { @@ -162,17 +218,39 @@ export class DataConsultService { async getAllCarriers(): Promise { this.logger.log('Buscando todas as transportadoras'); try { - return getOrSetCache( + return await getOrSetCache( this.redisClient, this.CARRIERS_CACHE_KEY, this.CARRIERS_TTL, async () => { - const carriers = await this.repository.findAllCarriers(); - return carriers.map(carrier => ({ - carrierId: carrier.carrierId?.toString() || '', - carrierName: carrier.carrierName || '', - carrierDescription: carrier.carrierDescription || '', - })); + try { + const carriers = await this.repository.findAllCarriers(); + + if (carriers === null || carriers === undefined) { + throw new HttpException('Resultado inválido do repositório', HttpStatus.INTERNAL_SERVER_ERROR); + } + + const carriersArray = Array.isArray(carriers) ? carriers : [carriers]; + + return carriersArray + .filter(carrier => { + if (!carrier || typeof carrier !== 'object') { + return false; + } + const hasCarrierId = carrier.carrierId !== undefined && carrier.carrierId !== null && carrier.carrierId !== ''; + const hasCarrierName = carrier.carrierName !== undefined && carrier.carrierName !== null && carrier.carrierName !== ''; + const hasCarrierDescription = carrier.carrierDescription !== undefined && carrier.carrierDescription !== null && carrier.carrierDescription !== ''; + return hasCarrierId && hasCarrierName && hasCarrierDescription; + }) + .map(carrier => ({ + carrierId: carrier.carrierId?.toString() || '', + carrierName: carrier.carrierName || '', + carrierDescription: carrier.carrierDescription || '', + })); + } catch (error) { + this.logger.error('Erro ao buscar transportadoras', error); + throw error; + } } ); } catch (error) { @@ -215,13 +293,34 @@ export class DataConsultService { async getRegions(): Promise { this.logger.log('Buscando todas as regiões'); try { - return getOrSetCache( + return await getOrSetCache( this.redisClient, this.REGIONS_CACHE_KEY, this.REGIONS_TTL, async () => { - const regions = await this.repository.findRegions(); - return regions; + try { + const regions = await this.repository.findRegions(); + + if (regions === null || regions === undefined) { + throw new HttpException('Resultado inválido do repositório', HttpStatus.INTERNAL_SERVER_ERROR); + } + + const regionsArray = Array.isArray(regions) ? regions : [regions]; + + return regionsArray + .filter(region => { + if (!region || typeof region !== 'object') { + return false; + } + const hasNumregiao = region.numregiao !== undefined && region.numregiao !== null; + const hasRegiao = region.regiao !== undefined && region.regiao !== null && region.regiao !== ''; + return hasNumregiao && hasRegiao; + }) + .map(region => new RegionDto(region)); + } catch (error) { + this.logger.error('Erro ao buscar regiões', error); + throw error; + } } ); } catch (error) {