Adiciona busca por codauxiliar em findProducts e cria API de busca unificada

- Modifica findProducts para buscar por CODPROD e CODAUXILIAR
- Adiciona testes para o método products
- Cria endpoint unified-search para busca unificada por nome, código de barras ou codprod
- Adiciona @IsOptional aos campos opcionais do ProductDetailQueryDto
- Adiciona testes para products.service
This commit is contained in:
joelson brito
2025-11-10 15:04:07 -03:00
parent 6afba4f3b4
commit e3acf34510
9 changed files with 505 additions and 3 deletions

View File

@@ -1,4 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Logger } from '@nestjs/common';
import { DataConsultService } from '../data-consult.service';
import { DataConsultRepository } from '../data-consult.repository';
import { IRedisClient } from '../../core/configs/cache/IRedisClient';
@@ -14,6 +15,8 @@ export const createMockRepository = (
findSellers: jest.fn(),
findBillings: jest.fn(),
findCustomers: jest.fn(),
findProducts: jest.fn(),
findProductsByCodauxiliar: jest.fn(),
findAllProducts: jest.fn(),
findAllCarriers: jest.fn(),
findRegions: jest.fn(),
@@ -31,6 +34,9 @@ export interface DataConsultServiceTestContext {
mockRepository: jest.Mocked<DataConsultRepository>;
mockRedisClient: jest.Mocked<IRedisClient>;
mockDataSource: jest.Mocked<DataSource>;
mockLogger: {
error: jest.Mock;
};
}
export async function createDataConsultServiceTestModule(
@@ -64,10 +70,21 @@ export async function createDataConsultServiceTestModule(
const service = module.get<DataConsultService>(DataConsultService);
const mockLogger = {
error: jest.fn(),
};
jest.spyOn(Logger.prototype, 'error').mockImplementation(
(message: any, ...optionalParams: any[]) => {
mockLogger.error(message, ...optionalParams);
},
);
return {
service,
mockRepository,
mockRedisClient,
mockDataSource,
mockLogger,
};
}

View File

@@ -124,6 +124,23 @@ describe('DataConsultService', () => {
});
});
it('should filter out sellers with null id', async () => {
context.mockRepository.findSellers.mockResolvedValue([
{ id: null, name: 'Vendedor 1' },
{ id: '002', name: 'Vendedor 2' },
{ id: null, name: 'Vendedor 3' },
] as any);
const result = await context.service.sellers();
expect(result).toHaveLength(1);
expect(result[0].id).toBe('002');
expect(result[0].name).toBe('Vendedor 2');
result.forEach((seller) => {
expect(seller.id).not.toBeNull();
expect(seller.id).toBeDefined();
});
});
it('should log error when repository throws exception', async () => {
const repositoryError = new Error('Database connection failed');
context.mockRepository.findSellers.mockRejectedValue(repositoryError);
@@ -510,4 +527,124 @@ describe('DataConsultService', () => {
});
});
});
describe('products', () => {
let context: Awaited<ReturnType<typeof createDataConsultServiceTestModule>>;
beforeEach(async () => {
context = await createDataConsultServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Tests that expose problems', () => {
it('should search products by CODPROD', async () => {
context.mockRepository.findProducts.mockResolvedValue([
{
id: '12345',
name: 'PRODUTO EXEMPLO',
manufacturerCode: 'FAB001',
},
] as any);
const result = await context.service.products('12345');
expect(result).toHaveLength(1);
expect(result[0].id).toBe('12345');
expect(result[0].name).toBe('PRODUTO EXEMPLO');
expect(context.mockRepository.findProducts).toHaveBeenCalledWith(
'12345',
);
});
it('should search products by CODAUXILIAR', async () => {
context.mockRepository.findProducts.mockResolvedValue([
{
id: '12345',
name: 'PRODUTO EXEMPLO',
manufacturerCode: 'FAB001',
},
] as any);
const result = await context.service.products('7891234567890');
expect(result).toHaveLength(1);
expect(result[0].id).toBe('12345');
expect(context.mockRepository.findProducts).toHaveBeenCalledWith(
'7891234567890',
);
});
it('should search products by CODPROD or CODAUXILIAR', async () => {
context.mockRepository.findProducts.mockResolvedValue([
{
id: '12345',
name: 'PRODUTO EXEMPLO',
manufacturerCode: 'FAB001',
},
{
id: '12346',
name: 'OUTRO PRODUTO',
manufacturerCode: 'FAB002',
},
] as any);
const result = await context.service.products('12345');
expect(result).toHaveLength(2);
expect(result[0].id).toBe('12345');
expect(result[1].id).toBe('12346');
});
it('should handle empty result from repository', async () => {
context.mockRepository.findProducts.mockResolvedValue([]);
const result = await context.service.products('99999');
expect(result).toHaveLength(0);
expect(Array.isArray(result)).toBe(true);
});
it('should validate that all products have required properties (id, name)', async () => {
context.mockRepository.findProducts.mockResolvedValue([
{ id: '12345', name: 'PRODUTO 1' },
{ id: '12346', name: 'PRODUTO 2' },
{ id: '12347', name: 'PRODUTO 3' },
] as any);
const result = await context.service.products('12345');
result.forEach((product) => {
expect(product.id).toBeDefined();
expect(product.name).toBeDefined();
});
});
it('should throw error when filter is invalid', async () => {
await expect(
context.service.products(null as any),
).rejects.toThrow(HttpException);
await expect(
context.service.products(undefined as any),
).rejects.toThrow(HttpException);
await expect(
context.service.products('' as any),
).rejects.toThrow(HttpException);
});
it('should log error when repository throws exception', async () => {
const repositoryError = new Error('Database connection failed');
context.mockRepository.findProducts.mockRejectedValue(repositoryError);
await expect(context.service.products('12345')).rejects.toThrow(
HttpException,
);
expect(context.mockLogger.error).toHaveBeenCalledWith(
'Erro ao buscar produtos',
repositoryError,
);
});
});
});
});

View File

@@ -116,13 +116,15 @@ export class DataConsultRepository {
}
async findProducts(filter: string): Promise<ProductDto[]> {
const cleanedFilter = filter.replace(/\D/g, '');
const sql = `
SELECT PCPRODUT.CODPROD as "id",
PCPRODUT.CODPROD || ' - ' || PCPRODUT.DESCRICAO || ' ( ' || PCPRODUT.CODFAB || ' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODPROD = :filter
WHERE PCPRODUT.CODPROD = :0
OR REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '') = :1
`;
const results = await this.executeQuery<ProductDto[]>(sql, [filter]);
const results = await this.executeQuery<ProductDto[]>(sql, [filter, cleanedFilter]);
return results.map((result) => new ProductDto(result));
}