import { describe, it, expect, vi, beforeEach } from 'vitest' import { useProducts } from '~/composables/useProducts' import { mockProductFromApi, mockConstructeurSKF, wrapCollection } from '../fixtures/mockData' // --------------------------------------------------------------------------- // Mocks // --------------------------------------------------------------------------- const mockGet = vi.fn() const mockPost = vi.fn() const mockPatch = vi.fn() const mockDel = vi.fn() vi.mock('~/composables/useApi', () => ({ useApi: () => ({ get: mockGet, post: mockPost, patch: mockPatch, put: vi.fn(), delete: mockDel, postFormData: vi.fn(), }), })) vi.mock('~/composables/useToast', () => ({ useToast: () => ({ showSuccess: vi.fn(), showError: vi.fn(), showInfo: vi.fn(), showToast: vi.fn(), toasts: { value: [] }, clearAll: vi.fn(), }), })) vi.mock('~/composables/useConstructeurs', () => ({ useConstructeurs: () => ({ ensureConstructeurs: vi.fn().mockResolvedValue([]), }), })) beforeEach(() => { vi.clearAllMocks() const { clearProductsCache } = useProducts() clearProductsCache() }) // --------------------------------------------------------------------------- // createProduct // --------------------------------------------------------------------------- describe('createProduct', () => { it('sends all fields including supplierPrice in POST payload', async () => { const created = { ...mockProductFromApi, id: 'prod-new' } mockPost.mockResolvedValue({ success: true, data: created }) const { createProduct } = useProducts() await createProduct({ name: 'Graisse LGMT2', reference: 'LUB-LGMT2', supplierPrice: 45.90, typeProductId: 'tprod-grease-001', }) expect(mockPost).toHaveBeenCalledWith('/products', expect.objectContaining({ name: 'Graisse LGMT2', reference: 'LUB-LGMT2', supplierPrice: 45.90, typeProduct: '/api/model_types/tprod-grease-001', })) }) it('strips constructeur fields from payload', async () => { const created = { ...mockProductFromApi, id: 'prod-new' } mockPost.mockResolvedValue({ success: true, data: created }) const { createProduct } = useProducts() await createProduct({ name: 'Test Product', constructeurIds: ['cstr-skf-001'], constructeurs: [mockConstructeurSKF] as any, }) const payload = mockPost.mock.calls[0]![1] expect(payload).not.toHaveProperty('constructeurIds') expect(payload).not.toHaveProperty('constructeurs') expect(payload).not.toHaveProperty('constructeurId') expect(payload).not.toHaveProperty('constructeur') }) it('adds created product to cache (products array and total)', async () => { const created = { ...mockProductFromApi, id: 'prod-new' } mockPost.mockResolvedValue({ success: true, data: created }) const { createProduct, products, total } = useProducts() const result = await createProduct({ name: 'New Product' }) expect(result.success).toBe(true) expect(products.value).toHaveLength(1) expect(products.value[0]!.id).toBe('prod-new') expect(total.value).toBe(1) }) }) // --------------------------------------------------------------------------- // updateProduct // --------------------------------------------------------------------------- describe('updateProduct', () => { it('patches with supplied fields and updates cache', async () => { // Seed cache first const original = { ...mockProductFromApi } mockPost.mockResolvedValue({ success: true, data: original }) const { createProduct, updateProduct, products } = useProducts() await createProduct({ name: 'Graisse LGMT2' }) const updated = { ...mockProductFromApi, name: 'Updated Name', supplierPrice: 99.99 } mockPatch.mockResolvedValue({ success: true, data: updated }) const result = await updateProduct(mockProductFromApi.id, { name: 'Updated Name', supplierPrice: 99.99, }) expect(mockPatch).toHaveBeenCalledWith(`/products/${mockProductFromApi.id}`, expect.objectContaining({ name: 'Updated Name', supplierPrice: 99.99, })) expect(result.success).toBe(true) expect(products.value.find(p => p.id === mockProductFromApi.id)?.name).toBe('Updated Name') }) }) // --------------------------------------------------------------------------- // deleteProduct // --------------------------------------------------------------------------- describe('deleteProduct', () => { it('removes product from cache on success', async () => { // Seed cache mockPost.mockResolvedValue({ success: true, data: { ...mockProductFromApi } }) const { createProduct, deleteProduct, products, total } = useProducts() await createProduct({ name: 'To Delete' }) expect(products.value).toHaveLength(1) mockDel.mockResolvedValue({ success: true }) const result = await deleteProduct(mockProductFromApi.id) expect(result.success).toBe(true) expect(products.value).toHaveLength(0) expect(total.value).toBe(0) }) it('does not remove on failure', async () => { // Seed cache mockPost.mockResolvedValue({ success: true, data: { ...mockProductFromApi } }) const { createProduct, deleteProduct, products, total } = useProducts() await createProduct({ name: 'Should Stay' }) expect(products.value).toHaveLength(1) mockDel.mockResolvedValue({ success: false, error: 'Server error' }) const result = await deleteProduct(mockProductFromApi.id) expect(result.success).toBe(false) expect(products.value).toHaveLength(1) expect(total.value).toBe(1) }) }) // --------------------------------------------------------------------------- // getProduct // --------------------------------------------------------------------------- describe('getProduct', () => { it('returns cached product if available with constructeurs (no extra API call)', async () => { // Seed cache with a product that has resolved constructeurs const productWithConstructeurs = { ...mockProductFromApi, constructeurs: [mockConstructeurSKF], } mockPost.mockResolvedValue({ success: true, data: productWithConstructeurs }) const { createProduct, getProduct } = useProducts() await createProduct({ name: 'Cached' }) mockGet.mockClear() const result = await getProduct(mockProductFromApi.id) expect(result.success).toBe(true) expect(result.data?.id).toBe(mockProductFromApi.id) expect(mockGet).not.toHaveBeenCalled() }) it('fetches from API with force: true', async () => { // Seed cache with a product that has resolved constructeurs const productWithConstructeurs = { ...mockProductFromApi, constructeurs: [mockConstructeurSKF], } mockPost.mockResolvedValue({ success: true, data: productWithConstructeurs }) const { createProduct, getProduct } = useProducts() await createProduct({ name: 'Cached' }) const freshData = { ...mockProductFromApi, name: 'Fresh from API' } mockGet.mockResolvedValue({ success: true, data: freshData }) const result = await getProduct(mockProductFromApi.id, { force: true }) expect(mockGet).toHaveBeenCalledWith(`/products/${mockProductFromApi.id}`) expect(result.success).toBe(true) expect(result.data?.name).toBe('Fresh from API') }) })