210 lines
7.2 KiB
TypeScript
210 lines
7.2 KiB
TypeScript
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')
|
|
})
|
|
})
|