Add OMS WMS ERP platform
Validators (Pushes and Pull Requests) / UI & Storage Validation (pull_request) Failing after 12s
Validators (Pushes and Pull Requests) / Database & Schema Validation (pull_request) Successful in 13s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (pull_request) Failing after 28s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / CI Workflow Lint (pull_request) Failing after 10s
Validators (Pushes and Pull Requests) / Security & Secrets (pull_request) Successful in 12s
Validators (Pushes and Pull Requests) / Notify PR Results (pull_request) Successful in 2s
Frontend CI Pipeline / ci-frontend-8-steps (pull_request) Failing after 2m50s

This commit is contained in:
2026-07-27 00:45:39 +09:00
parent 15dc3685df
commit b34b0dd7d6
163 changed files with 32596 additions and 0 deletions
@@ -0,0 +1,111 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useCustomerStore } from '@/stores'
describe('Customer Workflow Integration Tests', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
describe('Customer Management', () => {
it('should create and manage customers', async () => {
const customerStore = useCustomerStore()
const customer = await customerStore.createCustomer({
code: 'CUST-001',
name: 'Test Customer',
email: 'test@example.com',
phone: '123-456-7890',
address: '123 Main St',
city: 'Springfield',
country: 'USA',
creditLimit: 10000
})
expect(customer.code).toBe('CUST-001')
expect(customer.status).toBe('ACTIVE')
expect(customer.creditUsed).toBe(0)
})
it('should track credit usage', async () => {
const customerStore = useCustomerStore()
const customer = await customerStore.createCustomer({
code: 'CUST-002',
name: 'Credit Test',
email: 'credit@test.com',
phone: '999-999-9999',
address: 'Test',
city: 'Test',
country: 'USA',
creditLimit: 5000
})
await customerStore.updateCreditUsage(customer.id, 2000)
const updated = customerStore.customers.find((c) => c.id === customer.id)
expect(updated?.creditUsed).toBe(2000)
})
it('should identify at-risk customers', async () => {
const customerStore = useCustomerStore()
const customer = await customerStore.createCustomer({
code: 'CUST-RISK',
name: 'Risk Customer',
email: 'risk@test.com',
phone: '888-888-8888',
address: 'Risk Ave',
city: 'Risk City',
country: 'USA',
creditLimit: 1000
})
// Use 85% of credit
await customerStore.updateCreditUsage(customer.id, 850)
const atRisk = customerStore.atRiskCustomers
expect(atRisk.some((c) => c.id === customer.id)).toBe(true)
})
it('should update customer status', async () => {
const customerStore = useCustomerStore()
const customer = await customerStore.createCustomer({
code: 'CUST-003',
name: 'Status Test',
email: 'status@test.com',
phone: '777-777-7777',
address: 'Status St',
city: 'Status City',
country: 'USA'
})
await customerStore.updateCustomer(customer.id, {
status: 'SUSPENDED'
})
const updated = customerStore.customers.find((c) => c.id === customer.id)
expect(updated?.status).toBe('SUSPENDED')
})
})
describe('Customer Validation', () => {
it('should maintain active customer count', async () => {
const customerStore = useCustomerStore()
const initial = customerStore.activeCustomers.length
await customerStore.createCustomer({
code: 'CUST-NEW',
name: 'New Customer',
email: 'new@test.com',
phone: '666-666-6666',
address: 'New St',
city: 'New City',
country: 'USA'
})
expect(customerStore.activeCustomers.length).toBe(initial + 1)
})
})
})
+14
View File
@@ -0,0 +1,14 @@
/**
* Integration Tests Index
* Tests for store + component interactions
*
* Test Coverage:
* - Order Workflow (create → update → delete, filtering, totals)
* - Inventory Workflow (reserve, release, low stock alerts)
* - Customer Workflow (CRUD, credit management, at-risk detection)
* - Multi-store coordination tests
*/
export * from './order-workflow.spec'
export * from './inventory-workflow.spec'
export * from './customer-workflow.spec'
@@ -0,0 +1,111 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useInventoryStore, useWarehouseStore } from '@/stores'
describe('Inventory Workflow Integration Tests', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
describe('Stock Management', () => {
it('should update quantity correctly', async () => {
const inventoryStore = useInventoryStore()
// Initial state
expect(inventoryStore.totalQuantity).toBe(0)
// Update quantity
await inventoryStore.updateQuantity('item-001', 100)
expect(inventoryStore.totalQuantity).toBeGreaterThan(0)
})
it('should reserve and release quantity', async () => {
const inventoryStore = useInventoryStore()
// Setup
await inventoryStore.updateQuantity('item-001', 100)
const initial = inventoryStore.availableQuantity
// Reserve
await inventoryStore.reserveQuantity('item-001', 30)
expect(inventoryStore.availableQuantity).toBeLessThan(initial)
// Release
await inventoryStore.releaseQuantity('item-001', 30)
expect(inventoryStore.availableQuantity).toBe(initial)
})
it('should prevent overselling', async () => {
const inventoryStore = useInventoryStore()
await inventoryStore.updateQuantity('item-001', 50)
try {
await inventoryStore.reserveQuantity('item-001', 100)
expect.fail('Should throw error')
} catch (err: any) {
expect(err.message).toContain('Insufficient')
}
})
it('should track low stock items', async () => {
const inventoryStore = useInventoryStore()
// Setup low stock
await inventoryStore.updateQuantity('item-001', 5) // Below reorder level
const lowStock = inventoryStore.lowStockItems
expect(lowStock.length).toBeGreaterThan(0)
})
})
describe('Warehouse Integration', () => {
it('should coordinate inventory with warehouses', async () => {
const inventoryStore = useInventoryStore()
const warehouseStore = useWarehouseStore()
await warehouseStore.fetchWarehouses()
expect(warehouseStore.warehouseCount).toBeGreaterThanOrEqual(0)
// Inventory in warehouses
await inventoryStore.fetchInventory()
expect(inventoryStore.items).toBeDefined()
})
it('should update warehouse load based on inventory', async () => {
const warehouseStore = useWarehouseStore()
await warehouseStore.createWarehouse({
code: 'WH-TEST',
name: 'Test Warehouse',
location: 'Test Location',
capacity: 1000
})
const warehouses = warehouseStore.warehouses
expect(warehouses.length).toBeGreaterThan(0)
// Update load
const warehouse = warehouses[0]
await warehouseStore.updateLoad(warehouse.id, 500)
expect(warehouse.currentLoad).toBe(500)
})
it('should alert on overloaded warehouses', async () => {
const warehouseStore = useWarehouseStore()
await warehouseStore.createWarehouse({
code: 'WH-FULL',
name: 'Full Warehouse',
location: 'Location',
capacity: 100
})
const warehouse = warehouseStore.warehouses[0]
await warehouseStore.updateLoad(warehouse.id, 95) // 95% capacity
const overloaded = warehouseStore.overloadedWarehouses
expect(overloaded.some((w) => w.id === warehouse.id)).toBe(true)
})
})
})
@@ -0,0 +1,201 @@
/**
* Integration Tests: Inventory (WMS) Store
* Tests warehouse inventory management with mocked API
*
* Run: npm run test:integration
*/
import { beforeEach, describe, expect, it } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { server } from '@/tests/mocks/server'
import { http, HttpResponse } from 'msw'
/**
* Mock Inventory Store (placeholder until actual store is created)
*/
class MockInventoryStore {
inventory: any[] = []
loading = false
error: string | null = null
async fetchInventory() {
this.loading = true
try {
const response = await fetch(`${process.env.VITE_API_BASE_URL || 'http://localhost:3000/api'}/inventory`)
const data = await response.json()
this.inventory = data.data || []
} catch (err) {
this.error = (err as Error).message
} finally {
this.loading = false
}
}
async updateInventory(inventoryId: string, payload: any) {
this.loading = true
try {
const response = await fetch(
`${process.env.VITE_API_BASE_URL || 'http://localhost:3000/api'}/inventory/${inventoryId}`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
}
)
if (!response.ok) throw new Error('Update failed')
const data = await response.json()
const index = this.inventory.findIndex((i) => i.inventoryId === inventoryId)
if (index !== -1) this.inventory[index] = data
return data
} catch (err) {
this.error = (err as Error).message
throw err
} finally {
this.loading = false
}
}
}
describe('Inventory (WMS) Store Integration', () => {
let store: MockInventoryStore
beforeEach(() => {
store = new MockInventoryStore()
})
describe('fetchInventory', () => {
it('loads inventory from WMS API', async () => {
await store.fetchInventory()
expect(store.inventory).toHaveLength(1)
expect(store.inventory[0].inventoryId).toBe('INV-001')
expect(store.inventory[0].qtyOnHand).toBe(1000)
expect(store.loading).toBe(false)
expect(store.error).toBeNull()
})
it('calculates available quantity correctly', async () => {
await store.fetchInventory()
const inv = store.inventory[0]
const available = inv.qtyOnHand - inv.qtyReserved
expect(available).toBe(900)
expect(inv.qtyAvailable).toBe(available)
})
it('handles API errors gracefully', async () => {
server.use(
http.get('*/api/inventory', () => {
return HttpResponse.json(
{ message: 'Database connection failed' },
{ status: 500 }
)
})
)
await store.fetchInventory()
expect(store.error).not.toBeNull()
expect(store.inventory).toHaveLength(0)
})
})
describe('updateInventory', () => {
beforeEach(async () => {
await store.fetchInventory()
})
it('updates inventory quantities', async () => {
const updates = { qtyOnHand: 1200, qtyReserved: 150 }
const updated = await store.updateInventory('INV-001', updates)
expect(updated.qtyOnHand).toBe(1200)
expect(updated.qtyReserved).toBe(150)
expect(updated.qtyAvailable).toBe(1050)
})
it('reflects updates in store', async () => {
const updates = { qtyOnHand: 800, qtyReserved: 100 }
await store.updateInventory('INV-001', updates)
const inv = store.inventory.find((i) => i.inventoryId === 'INV-001')
expect(inv?.qtyOnHand).toBe(800)
})
it('handles update validation errors', async () => {
server.use(
http.patch('*/api/inventory/:inventoryId', () => {
return HttpResponse.json(
{ message: 'Quantity cannot be negative' },
{ status: 400 }
)
})
)
await expect(
store.updateInventory('INV-001', { qtyOnHand: -100 })
).rejects.toThrow()
})
})
describe('stock availability checks', () => {
beforeEach(async () => {
await store.fetchInventory()
})
it('reserves stock correctly', async () => {
const initial = store.inventory[0]
const reservation = 100
const updated = await store.updateInventory('INV-001', {
qtyOnHand: initial.qtyOnHand,
qtyReserved: initial.qtyReserved + reservation
})
expect(updated.qtyAvailable).toBe(initial.qtyAvailable - reservation)
})
it('prevents over-reservation', async () => {
const current = store.inventory[0]
// Try to reserve more than available
server.use(
http.patch('*/api/inventory/:inventoryId', () => {
return HttpResponse.json(
{ message: 'Cannot reserve more than available quantity' },
{ status: 422 }
)
})
)
await expect(
store.updateInventory('INV-001', {
qtyOnHand: current.qtyOnHand,
qtyReserved: 10000 // Exceed available
})
).rejects.toThrow()
})
})
describe('multi-warehouse scenarios', () => {
it('handles inventory across multiple warehouses', async () => {
server.use(
http.get('*/api/inventory', () => {
return HttpResponse.json({
data: [
{ inventoryId: 'INV-001', warehouseId: 'WH-SEOUL', qtyOnHand: 1000 },
{ inventoryId: 'INV-002', warehouseId: 'WH-BUSAN', qtyOnHand: 500 },
{ inventoryId: 'INV-003', warehouseId: 'WH-DAEGU', qtyOnHand: 300 }
]
})
})
)
const tempStore = new MockInventoryStore()
await tempStore.fetchInventory()
expect(tempStore.inventory).toHaveLength(3)
const totalQty = tempStore.inventory.reduce((sum, inv) => sum + inv.qtyOnHand, 0)
expect(totalQty).toBe(1800)
})
})
})
@@ -0,0 +1,221 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useOrderStore, useCustomerStore, useInventoryStore } from '@/stores'
import type { Order } from '@/stores'
describe('Order Workflow Integration Tests', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
describe('Complete Order Lifecycle', () => {
it('should create order → update → delete workflow', async () => {
const orderStore = useOrderStore()
const customerStore = useCustomerStore()
// Step 1: Fetch customers
await customerStore.fetchCustomers()
expect(customerStore.activeCustomers.length).toBeGreaterThanOrEqual(0)
// Step 2: Create order
const newOrder = await orderStore.createOrder({
customerId: 'cust-001',
customerName: 'Test Customer',
orderDate: '2026-08-01',
dueDate: '2026-08-15',
items: [
{
id: 'item-001',
lineNumber: 1,
productId: 'prod-001',
productSku: 'SKU-12345',
quantity: 100,
unitPrice: 50,
lineTotal: 5000
}
],
subtotal: 5000,
tax: 500,
total: 5500
})
expect(newOrder).toBeDefined()
expect(newOrder.status).toBe('DRAFT')
expect(orderStore.orderCount).toBeGreaterThan(0)
// Step 3: Update order
const updated = await orderStore.updateOrder(newOrder.id, {
status: 'CONFIRMED',
notes: 'Updated via workflow test'
})
expect(updated.status).toBe('CONFIRMED')
expect(updated.notes).toBe('Updated via workflow test')
// Step 4: Delete order
await orderStore.deleteOrder(newOrder.id)
expect(orderStore.orders.find((o) => o.id === newOrder.id)).toBeUndefined()
})
it('should filter orders by status', async () => {
const orderStore = useOrderStore()
await orderStore.fetchOrders()
// Set filter
orderStore.setFilter({ status: 'CONFIRMED' })
expect(orderStore.filter.status).toBe('CONFIRMED')
// Filter computed
const filtered = orderStore.filteredOrders
expect(filtered.every((o) => o.status === 'CONFIRMED')).toBe(true)
// Clear filter
orderStore.clearFilter()
expect(orderStore.filter.status).toBe('ALL')
})
it('should calculate order totals correctly', async () => {
const orderStore = useOrderStore()
const order = await orderStore.createOrder({
customerId: 'cust-001',
customerName: 'Test',
orderDate: '2026-08-01',
dueDate: '2026-08-15',
items: [
{
id: '1',
lineNumber: 1,
productId: 'p1',
productSku: 'SKU-001',
quantity: 100,
unitPrice: 50,
lineTotal: 5000
},
{
id: '2',
lineNumber: 2,
productId: 'p2',
productSku: 'SKU-002',
quantity: 50,
unitPrice: 30,
lineTotal: 1500
}
],
subtotal: 6500,
tax: 650,
total: 7150
})
expect(order.subtotal).toBe(6500)
expect(order.tax).toBe(650)
expect(order.total).toBe(7150)
})
it('should track recent orders', async () => {
const orderStore = useOrderStore()
// Create multiple orders
for (let i = 0; i < 3; i++) {
await orderStore.createOrder({
customerId: `cust-${i}`,
customerName: `Customer ${i}`,
orderDate: '2026-08-01',
dueDate: '2026-08-15',
items: [],
subtotal: 1000,
tax: 100,
total: 1100
})
}
const recent = orderStore.recentOrders
expect(recent.length).toBeLessThanOrEqual(5)
})
})
describe('Order Status Transitions', () => {
it('should transition through valid statuses', async () => {
const orderStore = useOrderStore()
const order = await orderStore.createOrder({
customerId: 'cust-001',
customerName: 'Test',
orderDate: '2026-08-01',
dueDate: '2026-08-15',
items: [],
subtotal: 0,
tax: 0,
total: 0
})
const statuses = ['DRAFT', 'PENDING', 'CONFIRMED', 'SHIPPED', 'DELIVERED']
let current = order
for (const status of statuses) {
current = await orderStore.updateOrder(current.id, { status: status as any })
expect(current.status).toBe(status)
}
})
it('should count orders by status', async () => {
const orderStore = useOrderStore()
for (let i = 0; i < 3; i++) {
await orderStore.createOrder({
customerId: 'cust-001',
customerName: 'Test',
orderDate: '2026-08-01',
dueDate: '2026-08-15',
items: [],
subtotal: 0,
tax: 0,
total: 0,
status: i === 0 ? 'CONFIRMED' : 'PENDING'
} as any)
}
const byStatus = orderStore.ordersByStatus
expect(byStatus['CONFIRMED']).toBeGreaterThan(0)
expect(byStatus['PENDING']).toBeGreaterThan(0)
})
})
describe('Order Validation', () => {
it('should require customer for order', async () => {
const orderStore = useOrderStore()
const order = await orderStore.createOrder({
customerId: '',
customerName: '',
orderDate: '2026-08-01',
dueDate: '2026-08-15',
items: [],
subtotal: 0,
tax: 0,
total: 0
})
expect(order.customerId).toBe('')
// Validation would be enforced at form level
})
it('should handle empty order items', async () => {
const orderStore = useOrderStore()
const order = await orderStore.createOrder({
customerId: 'cust-001',
customerName: 'Test',
orderDate: '2026-08-01',
dueDate: '2026-08-15',
items: [],
subtotal: 0,
tax: 0,
total: 0
})
expect(order.items.length).toBe(0)
})
})
})
@@ -0,0 +1,221 @@
/**
* Integration Tests: Orders Store with API
* Tests Pinia store integration with mocked API endpoints
*
* Run: npm run test:integration
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useOrderStore } from '@/stores/modules/orders'
import { ordersApi } from '@/services/api/client'
import { server } from '@/tests/mocks/server'
import { http, HttpResponse } from 'msw'
/**
* Test Suite: Orders Store
*/
describe('Orders Store with API Integration', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
describe('fetchOrders', () => {
it('loads orders from API', async () => {
const store = useOrderStore()
await store.fetchOrders(100, 0)
expect(store.orders).toHaveLength(2)
expect(store.orders[0].orderNo).toBe('ORD-001')
expect(store.loading).toBe(false)
expect(store.error).toBeNull()
})
it('sets loading state during fetch', async () => {
const store = useOrderStore()
// Delay the API response
server.use(
http.get('*/api/orders', async () => {
await new Promise((resolve) => setTimeout(resolve, 100))
return HttpResponse.json({ data: [] })
})
)
const fetchPromise = store.fetchOrders()
expect(store.loading).toBe(true)
await fetchPromise
expect(store.loading).toBe(false)
})
it('handles API errors gracefully', async () => {
const store = useOrderStore()
server.use(
http.get('*/api/orders', () => {
return HttpResponse.json(
{ message: 'Internal Server Error' },
{ status: 500 }
)
})
)
await store.fetchOrders()
expect(store.error).not.toBeNull()
expect(store.orders).toHaveLength(0)
})
})
describe('fetchOrderById', () => {
it('loads single order by ID', async () => {
const store = useOrderStore()
await store.fetchOrderById('ORD-001')
expect(store.selectedOrder).not.toBeNull()
expect(store.selectedOrder?.orderId).toBe('ORD-001')
expect(store.loading).toBe(false)
})
it('handles 404 not found', async () => {
const store = useOrderStore()
server.use(
http.get('*/api/orders/:orderId', () => {
return HttpResponse.json(
{ message: 'Not Found' },
{ status: 404 }
)
})
)
await store.fetchOrderById('ORD-NONEXISTENT')
expect(store.error).not.toBeNull()
expect(store.selectedOrder).toBeNull()
})
})
describe('createOrder', () => {
it('creates new order and updates store', async () => {
const store = useOrderStore()
const payload = {
customerId: 'CUST-001',
totalAmount: 100000
}
const createdOrder = await store.createOrder(payload)
expect(createdOrder).toBeDefined()
expect(createdOrder.orderId).toBe('ORD-NEW')
expect(store.selectedOrder?.orderId).toBe('ORD-NEW')
expect(store.loading).toBe(false)
})
it('adds created order to store', async () => {
const store = useOrderStore()
const initialCount = store.orders.length
await store.createOrder({ customerId: 'CUST-002', totalAmount: 50000 })
expect(store.orders.length).toBeGreaterThan(initialCount)
})
it('throws error on creation failure', async () => {
const store = useOrderStore()
server.use(
http.post('*/api/orders', () => {
return HttpResponse.json(
{ message: 'Validation failed' },
{ status: 400 }
)
})
)
await expect(
store.createOrder({ customerId: 'CUST-001' })
).rejects.toThrow()
})
})
describe('updateOrder', () => {
it('updates order and reflects changes', async () => {
const store = useOrderStore()
const orderId = 'ORD-001'
const updates = { status: 'CONFIRMED', totalAmount: 150000 }
const updatedOrder = await store.updateOrder(orderId, updates)
expect(updatedOrder?.status).toBe('CONFIRMED')
expect(updatedOrder?.totalAmount).toBe(150000)
})
})
describe('deleteOrder', () => {
it('removes order from store', async () => {
const store = useOrderStore()
// Mock: add an order first
store.orders.push({
orderId: 'ORD-001',
orderNo: 'ORD-001',
status: 'DRAFT',
totalAmount: 100000
} as any)
const initialCount = store.orders.length
await store.deleteOrder('ORD-001')
expect(store.orders.length).toBeLessThan(initialCount)
})
})
describe('filtering', () => {
it('applies filters to orders', () => {
const store = useOrderStore()
store.setFilter('status', 'DRAFT')
store.setFilter('customerId', 'CUST-001')
expect(store.filters.status).toBe('DRAFT')
expect(store.filters.customerId).toBe('CUST-001')
})
it('clears all filters', () => {
const store = useOrderStore()
store.setFilter('status', 'DRAFT')
store.setFilter('customerId', 'CUST-001')
store.clearFilters()
expect(Object.keys(store.filters)).toHaveLength(0)
})
})
})
/**
* Test Suite: Orders API Client
*/
describe('Orders API Client', () => {
it('lists orders via API', async () => {
const orders = await ordersApi.listOrders({ limit: 100 })
expect(orders).toBeDefined()
expect(Array.isArray(orders.data || orders)).toBe(true)
})
it('gets single order via API', async () => {
const order = await ordersApi.getOrder('ORD-001')
expect(order.orderId).toBe('ORD-001')
})
it('creates order via API', async () => {
const payload = { customerId: 'CUST-001', totalAmount: 100000 }
const created = await ordersApi.createOrder(payload)
expect(created.orderId).toBeDefined()
})
})
@@ -0,0 +1,297 @@
/**
* Integration Tests: Products (ERP) Store
* Tests product catalog management with mocked API
*
* Run: npm run test:integration
*/
import { beforeEach, describe, expect, it } from 'vitest'
import { server } from '@/tests/mocks/server'
import { http, HttpResponse } from 'msw'
/**
* Mock Products Store (placeholder until actual store is created)
*/
class MockProductsStore {
products: any[] = []
loading = false
error: string | null = null
filters: Record<string, any> = {}
async fetchProducts() {
this.loading = true
try {
const response = await fetch(
`${process.env.VITE_API_BASE_URL || 'http://localhost:3000/api'}/products`
)
const data = await response.json()
this.products = data.data || []
} catch (err) {
this.error = (err as Error).message
} finally {
this.loading = false
}
}
async createProduct(payload: any) {
this.loading = true
try {
const response = await fetch(
`${process.env.VITE_API_BASE_URL || 'http://localhost:3000/api'}/products`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
}
)
if (!response.ok) throw new Error('Creation failed')
const data = await response.json()
this.products.push(data)
return data
} catch (err) {
this.error = (err as Error).message
throw err
} finally {
this.loading = false
}
}
async getProduct(productId: string) {
this.loading = true
try {
const response = await fetch(
`${process.env.VITE_API_BASE_URL || 'http://localhost:3000/api'}/products/${productId}`
)
if (!response.ok) throw new Error('Not found')
const data = await response.json()
return data
} catch (err) {
this.error = (err as Error).message
throw err
} finally {
this.loading = false
}
}
setFilter(key: string, value: any) {
this.filters[key] = value
}
clearFilters() {
this.filters = {}
}
}
describe('Products (ERP) Store Integration', () => {
let store: MockProductsStore
beforeEach(() => {
store = new MockProductsStore()
})
describe('fetchProducts', () => {
it('loads products from ERP API', async () => {
await store.fetchProducts()
expect(store.products).toHaveLength(1)
expect(store.products[0].productId).toBe('PROD-001')
expect(store.products[0].productName).toBe('Product A')
expect(store.loading).toBe(false)
})
it('includes product metadata', async () => {
await store.fetchProducts()
const product = store.products[0]
expect(product).toHaveProperty('sku')
expect(product).toHaveProperty('categoryId')
expect(product).toHaveProperty('status')
expect(product).toHaveProperty('createdAt')
})
it('handles empty product list', async () => {
server.use(
http.get('*/api/products', () => {
return HttpResponse.json({ data: [] })
})
)
await store.fetchProducts()
expect(store.products).toHaveLength(0)
expect(store.error).toBeNull()
})
})
describe('createProduct', () => {
it('creates new product with required fields', async () => {
const payload = {
sku: 'SKU-NEW',
productName: 'New Product',
categoryId: 'CAT-002'
}
const created = await store.createProduct(payload)
expect(created.productId).toBe('PROD-NEW')
expect(created.sku).toBe('SKU-NEW')
expect(created.productName).toBe('New Product')
expect(created.status).toBe('ACTIVE')
})
it('adds created product to store', async () => {
const initialCount = store.products.length
await store.createProduct({
sku: 'SKU-NEW-2',
productName: 'Another Product',
categoryId: 'CAT-003'
})
expect(store.products.length).toBeGreaterThan(initialCount)
})
it('validates required fields', async () => {
server.use(
http.post('*/api/products', () => {
return HttpResponse.json(
{ message: 'Product name is required' },
{ status: 400 }
)
})
)
await expect(
store.createProduct({ sku: 'SKU-INVALID' })
).rejects.toThrow()
})
it('ensures unique SKU', async () => {
server.use(
http.post('*/api/products', () => {
return HttpResponse.json(
{ message: 'SKU already exists' },
{ status: 409 }
)
})
)
await expect(
store.createProduct({ sku: 'SKU-001', productName: 'Duplicate' })
).rejects.toThrow()
})
})
describe('getProduct', () => {
it('fetches single product by ID', async () => {
const product = await store.getProduct('PROD-001')
expect(product.productId).toBe('PROD-001')
expect(product.productName).toBe('Product Sample')
})
it('handles product not found', async () => {
server.use(
http.get('*/api/products/:productId', () => {
return HttpResponse.json(
{ message: 'Product not found' },
{ status: 404 }
)
})
)
await expect(store.getProduct('PROD-INVALID')).rejects.toThrow()
expect(store.error).not.toBeNull()
})
})
describe('product filtering', () => {
beforeEach(async () => {
server.use(
http.get('*/api/products', () => {
return HttpResponse.json({
data: [
{ productId: 'PROD-001', categoryId: 'CAT-001', status: 'ACTIVE' },
{ productId: 'PROD-002', categoryId: 'CAT-001', status: 'ACTIVE' },
{ productId: 'PROD-003', categoryId: 'CAT-002', status: 'INACTIVE' },
{ productId: 'PROD-004', categoryId: 'CAT-002', status: 'ACTIVE' }
]
})
})
)
await store.fetchProducts()
})
it('filters by category', () => {
store.setFilter('categoryId', 'CAT-001')
const filtered = store.products.filter((p) => p.categoryId === store.filters.categoryId)
expect(filtered).toHaveLength(2)
})
it('filters by status', () => {
store.setFilter('status', 'ACTIVE')
const filtered = store.products.filter((p) => p.status === store.filters.status)
expect(filtered.length).toBeGreaterThan(0)
})
it('combines multiple filters', () => {
store.setFilter('categoryId', 'CAT-001')
store.setFilter('status', 'ACTIVE')
const filtered = store.products.filter(
(p) =>
p.categoryId === store.filters.categoryId &&
p.status === store.filters.status
)
expect(filtered).toHaveLength(2)
})
it('clears all filters', () => {
store.setFilter('categoryId', 'CAT-001')
store.setFilter('status', 'ACTIVE')
store.clearFilters()
expect(Object.keys(store.filters)).toHaveLength(0)
})
})
describe('bulk operations', () => {
it('handles bulk product creation', async () => {
server.use(
http.post('*/api/products/bulk', async ({ request }) => {
const body = await request.json() as any
return HttpResponse.json(
{
created: body.items.length,
items: body.items.map((item: any, idx: number) => ({
productId: `PROD-BULK-${idx + 1}`,
...item
}))
},
{ status: 201 }
)
})
)
const items = [
{ sku: 'SKU-BULK-1', productName: 'Bulk Product 1' },
{ sku: 'SKU-BULK-2', productName: 'Bulk Product 2' }
]
const response = await fetch(
`${process.env.VITE_API_BASE_URL || 'http://localhost:3000/api'}/products/bulk`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items })
}
)
const data = await response.json()
expect(data.created).toBe(2)
expect(data.items).toHaveLength(2)
})
})
})