Test your Crave API integration thoroughly
# Test Environment - connects to production API with test location
CRAVE_API_BASE_URL=https://api.cravejs.com
CRAVE_API_KEY=your_storefront_api_key_here
NEXT_PUBLIC_LOCATION_ID=your_test_location_id_here
# Required headers for all API calls
X-API-Key: your_storefront_api_key_here
Content-Type: application/json
// __tests__/api-client.test.js
import { fetchProducts, createCart, addItemToCart } from '../lib/api/client';
describe('API Client', () => {
const locationId = 'your_test_location_id_here';
const apiKey = 'your_storefront_api_key_here';
beforeEach(() => {
// Mock fetch for isolated testing
global.fetch = jest.fn();
process.env.CRAVE_API_KEY = apiKey;
});
afterEach(() => {
jest.resetAllMocks();
});
describe('fetchProducts', () => {
it('should fetch products successfully', async () => {
const mockProducts = [
{ _id: '1', name: 'Test Product', price: 10.99, availability: { isAvailable: true } }
];
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockProducts
});
const products = await fetchProducts(locationId);
expect(global.fetch).toHaveBeenCalledWith(
`https://api.cravejs.com/api/v1/locations/${locationId}/products`,
expect.objectContaining({
method: 'GET',
headers: expect.objectContaining({
'X-API-Key': apiKey
})
})
);
expect(products).toEqual(mockProducts);
});
it('should handle API errors', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 404,
json: async () => ({ message: 'Not found' })
});
await expect(fetchProducts(locationId))
.rejects.toThrow('Not found');
});
});
describe('createCart', () => {
it('should create cart successfully', async () => {
const mockResponse = { cartId: 'cart123' };
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockResponse
});
const cart = await createCart(locationId);
expect(global.fetch).toHaveBeenCalledWith(
`https://api.cravejs.com/api/v1/locations/${locationId}/carts`,
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
'X-API-Key': apiKey,
'Content-Type': 'application/json'
}),
body: JSON.stringify({
currentCartId: "",
orderType: "pickup",
orderTime: new Date().toISOString()
})
})
);
expect(cart).toEqual(mockResponse);
});
});
});
// __tests__/cart-provider.test.js
import { renderHook, act } from '@testing-library/react';
import { CartProvider, useCart } from '../providers/cart-provider';
describe('CartProvider', () => {
const wrapper = ({ children }) => (
<CartProvider>{children}</CartProvider>
);
it('should add item to cart', async () => {
const { result } = renderHook(() => useCart(), { wrapper });
const testItem = {
id: '1',
name: 'Test Item',
price: 10.99,
options: { giftBox: false }
};
await act(async () => {
await result.current.addToCart(testItem);
});
expect(result.current.items).toHaveLength(1);
expect(result.current.items[0].name).toBe('Test Item');
expect(result.current.isCartOpen).toBe(true);
});
it('should calculate totals correctly', async () => {
const { result } = renderHook(() => useCart(), { wrapper });
const testItem = {
id: '1',
name: 'Test Item',
price: 10.00,
options: { giftBox: true } // +$5
};
await act(async () => {
await result.current.addToCart(testItem);
});
expect(result.current.subtotal).toBe(15.00); // $10 + $5 gift box
expect(result.current.tax).toBeCloseTo(1.33); // 8.875% NYC tax
expect(result.current.total).toBeCloseTo(16.33);
});
});
// __tests__/e2e/cart-flow.test.js
describe('Cart Flow Integration', () => {
const locationId = 'your_test_location_id_here';
it('should complete full cart flow', async () => {
// 1. Create cart
const cartResponse = await createCart(locationId);
expect(cartResponse.cartId).toBeDefined();
const cartId = cartResponse.cartId;
// 2. Fetch products
const products = await fetchProducts(locationId);
expect(products.length).toBeGreaterThan(0);
// 3. Add item to cart
const testProduct = products[0];
const addItemResponse = await addItemToCart(locationId, cartId, {
productId: testProduct._id,
quantity: 1,
modifiers: [] // Add any required modifiers
});
expect(addItemResponse.success).toBe(true);
// 4. Get updated cart
const updatedCart = await getCart(locationId, cartId);
expect(updatedCart.items).toHaveLength(1);
// 5. Update quantity
const cartItemId = updatedCart.items[0]._id;
await updateCartItemQuantity(locationId, cartId, cartItemId, 2);
// 6. Validate cart
const validationResponse = await validateAndUpdateCart(locationId, cartId, {
customerName: 'Test Customer',
emailAddress: 'test@example.com',
phoneNumber: '+15550123456'
});
expect(validationResponse.success).toBe(true);
// 7. Create payment intent
const paymentIntent = await createPaymentIntent(locationId, cartId);
expect(paymentIntent.clientSecret).toBeDefined();
// 8. Clean up
await deleteCart(locationId, cartId);
});
});
// stories/cart-sidebar.stories.js
import { LeclercCart } from '../components/leclerc-cart';
export default {
title: 'Components/LeclercCart',
component: LeclercCart,
decorators: [
(Story) => (
<CartProvider>
<Story />
</CartProvider>
)
]
};
export const EmptyCart = {
args: {
isOpen: true,
onClose: () => {}
}
};
export const CartWithItems = {
args: {
isOpen: true,
onClose: () => {}
},
decorators: [
(Story) => {
// Mock cart with items
const mockCart = {
items: [
{
cartId: '1',
name: 'Chocolate Chip Cookie',
price: 3.99,
quantity: 2,
image: '/images/cookie.jpg',
options: { giftBox: false }
}
]
};
return (
<CartProvider initialCart={mockCart}>
<Story />
</CartProvider>
);
}
]
};
// __tests__/performance/api-performance.test.js
describe('API Performance', () => {
const locationId = 'your_test_location_id_here';
it('should load products within acceptable time', async () => {
const start = Date.now();
const products = await fetchProducts(locationId);
const duration = Date.now() - start;
expect(duration).toBeLessThan(2000); // 2 seconds max
expect(products.length).toBeGreaterThan(0);
});
it('should load menus with required parameters', async () => {
const orderDate = new Date().toISOString().split('T')[0];
const orderTime = '12:00';
const start = Date.now();
const menus = await fetchMenus(locationId, orderDate, orderTime);
const duration = Date.now() - start;
expect(duration).toBeLessThan(2000);
expect(menus).toBeDefined();
});
it('should handle concurrent requests', async () => {
const requests = Array(10).fill().map(() => fetchProducts(locationId));
const results = await Promise.all(requests);
results.forEach(products => {
expect(products).toBeDefined();
expect(Array.isArray(products)).toBe(true);
});
});
});
// scripts/load-test.js
const API_BASE = 'https://api.cravejs.com/api/v1';
const API_KEY = process.env.CRAVE_API_KEY;
const LOCATION_ID = process.env.NEXT_PUBLIC_LOCATION_ID;
const loadTest = async () => {
const concurrent = 50;
const requests = [];
for (let i = 0; i < concurrent; i++) {
requests.push(
fetch(`${API_BASE}/locations/${LOCATION_ID}/products`, {
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json'
}
})
);
}
const start = Date.now();
const responses = await Promise.all(requests);
const duration = Date.now() - start;
console.log(`${concurrent} requests completed in ${duration}ms`);
console.log(`Average: ${duration / concurrent}ms per request`);
const successCount = responses.filter(r => r.ok).length;
console.log(`Success rate: ${(successCount / concurrent) * 100}%`);
};
loadTest();
// __tests__/error-scenarios.test.js
describe('Error Scenarios', () => {
it('should handle network timeout', async () => {
// Mock network timeout
global.fetch = jest.fn().mockRejectedValue(new Error('Network timeout'));
await expect(fetchProducts(locationId))
.rejects.toThrow('Network timeout');
});
it('should handle rate limiting', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: false,
status: 429,
json: async () => ({ message: 'Rate limit exceeded' })
});
await expect(fetchProducts(locationId))
.rejects.toThrow('Rate limit exceeded');
});
it('should handle invalid API key', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: false,
status: 401,
json: async () => ({ message: 'Invalid API key' })
});
await expect(fetchProducts(locationId))
.rejects.toThrow('Invalid API key');
});
});
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/$1'
},
testMatch: [
'**/__tests__/**/*.test.js',
'**/?(*.)+(spec|test).js'
],
collectCoverageFrom: [
'lib/**/*.js',
'components/**/*.js',
'providers/**/*.js',
'!**/*.stories.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run test:unit
- run: npm run test:integration
- run: npm run test:e2e
- name: Upload coverage
uses: codecov/codecov-action@v3
// lib/api/client.js
const API_BASE = process.env.CRAVE_API_BASE_URL || 'https://api.cravejs.com/api/v1';
const API_KEY = process.env.CRAVE_API_KEY;
const apiRequest = async (endpoint, options = {}) => {
const url = `${API_BASE}${endpoint}`;
const config = {
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
...options.headers
},
...options
};
const response = await fetch(url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
};
export const fetchProducts = (locationId, productIds = null) => {
const query = productIds ? `?productIds=${productIds.join(',')}` : '';
return apiRequest(`/locations/${locationId}/products${query}`);
};
export const fetchMenus = (locationId, orderDate, orderTime) => {
return apiRequest(`/locations/${locationId}/menus?orderDate=${orderDate}&orderTime=${orderTime}`);
};
export const createCart = (locationId, cartData) => {
return apiRequest(`/locations/${locationId}/carts`, {
method: 'POST',
body: JSON.stringify({
currentCartId: '',
orderType: 'pickup',
orderTime: new Date().toISOString(),
...cartData
})
});
};
export const getCart = (locationId, cartId) => {
return apiRequest(`/locations/${locationId}/carts/${cartId}`);
};
export const addItemToCart = (locationId, cartId, itemData) => {
return apiRequest(`/locations/${locationId}/carts/${cartId}/cart-item`, {
method: 'POST',
body: JSON.stringify(itemData)
});
};
export const updateCartItemQuantity = (locationId, cartId, itemId, quantity) => {
return apiRequest(`/locations/${locationId}/carts/${cartId}/cart-item/${itemId}/quantity`, {
method: 'PATCH',
body: JSON.stringify({ quantity })
});
};
export const validateAndUpdateCart = (locationId, cartId, customerData) => {
return apiRequest(`/locations/${locationId}/cart/${cartId}/validate-and-update`, {
method: 'PUT',
body: JSON.stringify(customerData)
});
};
export const createPaymentIntent = (locationId, cartId) => {
return apiRequest(`/stripe/payment-intent?locationId=${locationId}&cartId=${cartId}`);
};
export const deleteCart = (locationId, cartId) => {
return apiRequest(`/locations/${locationId}/carts/${cartId}`, {
method: 'DELETE'
});
};
export const applyDiscount = (locationId, code, cartId, customerId = '') => {
return apiRequest(`/locations/${locationId}/discounts/apply-discount`, {
method: 'POST',
body: JSON.stringify({ code, cartId, customerId })
});
};
export const removeDiscount = (locationId, cartId) => {
return apiRequest(`/locations/${locationId}/discounts/apply-discount`, {
method: 'DELETE',
body: JSON.stringify({ cartId })
});
};