diff --git a/src/services/__tests__/productService.test.ts b/src/services/__tests__/productService.test.ts index 9d7d6b67a..0aefbdd04 100644 --- a/src/services/__tests__/productService.test.ts +++ b/src/services/__tests__/productService.test.ts @@ -11,6 +11,8 @@ vi.mock('@/lib/external-db', () => ({ })); describe('productService', () => { + const fetchPromobrindProductsMock = vi.mocked(externalDb.fetchPromobrindProducts); + beforeEach(() => { vi.clearAllMocks(); }); @@ -23,11 +25,11 @@ describe('productService', () => { sku: 'SKU123', stock: 100, }; - - (externalDb.fetchPromobrindProducts as any).mockResolvedValue([mockRawProduct]); + + fetchPromobrindProductsMock.mockResolvedValue([mockRawProduct]); const products = await productService.fetchProducts(); - + expect(products).toHaveLength(1); expect(products[0].id).toBe('123'); expect(products[0].name).toBe('Test Product'); @@ -36,9 +38,11 @@ describe('productService', () => { it('should apply search filter', async () => { await productService.fetchProducts({ search: 'caneca' }); - expect(externalDb.fetchPromobrindProducts).toHaveBeenCalledWith(expect.objectContaining({ - search: 'caneca' - })); + expect(externalDb.fetchPromobrindProducts).toHaveBeenCalledWith( + expect.objectContaining({ + search: 'caneca', + }), + ); }); it('should filter results client-side (category, price, stock)', async () => { @@ -46,7 +50,7 @@ describe('productService', () => { { id: '1', name: 'A', price: 10, category_name: 'Tech', stock: 10 }, { id: '2', name: 'B', price: 50, category_name: 'Office', stock: 0 }, ]; - (externalDb.fetchPromobrindProducts as any).mockResolvedValue(mockProducts); + fetchPromobrindProductsMock.mockResolvedValue(mockProducts); // Filter by price let result = await productService.fetchProducts({ minPrice: 20 }); @@ -58,4 +62,19 @@ describe('productService', () => { expect(result).toHaveLength(1); expect(result[0].id).toBe('1'); }); + + it('should ignore invalid price bounds instead of filtering out products', async () => { + const mockProducts = [ + { id: '1', name: 'A', price: 10, category_name: 'Tech', stock: 10 }, + { id: '2', name: 'B', price: 50, category_name: 'Office', stock: 0 }, + ]; + fetchPromobrindProductsMock.mockResolvedValue(mockProducts); + + const result = await productService.fetchProducts({ + minPrice: Number.NaN, + maxPrice: Number.POSITIVE_INFINITY, + }); + + expect(result.map((product) => product.id)).toEqual(['1', '2']); + }); }); diff --git a/src/services/productService.ts b/src/services/productService.ts index da63a377a..05acc76bd 100644 --- a/src/services/productService.ts +++ b/src/services/productService.ts @@ -2,6 +2,9 @@ import { fetchPromobrindProducts, fetchPromobrindProductById } from '@/lib/exter import { mapPromobrindToProduct } from '@/utils/product-mapper'; import { type Product, type ProductFilters } from '@/types/product-catalog'; +const getFiniteNumber = (value: unknown): number | null => + typeof value === 'number' && Number.isFinite(value) ? value : null; + export const productService = { async fetchProducts(filters?: ProductFilters) { const products = await fetchPromobrindProducts({ @@ -11,20 +14,30 @@ export const productService = { let result = products.map(mapPromobrindToProduct); - if (filters?.category) { + const category = filters?.category; + if (category) { + const normalizedCategory = category.toLowerCase(); result = result.filter( (p) => - p.category_name?.toLowerCase().includes(filters.category!.toLowerCase()) || - (p.category_id !== null && String(p.category_id) === filters.category), + p.category_name?.toLowerCase().includes(normalizedCategory) || + (p.category_id !== null && String(p.category_id) === category), ); } - if (filters?.minPrice !== undefined) { - result = result.filter((p) => p.price >= filters.minPrice!); + const minPrice = getFiniteNumber(filters?.minPrice); + if (minPrice !== null) { + result = result.filter((p) => { + const price = getFiniteNumber(p.price); + return price !== null && price >= minPrice; + }); } - if (filters?.maxPrice !== undefined) { - result = result.filter((p) => p.price <= filters.maxPrice!); + const maxPrice = getFiniteNumber(filters?.maxPrice); + if (maxPrice !== null) { + result = result.filter((p) => { + const price = getFiniteNumber(p.price); + return price !== null && price <= maxPrice; + }); } if (filters?.inStock) {