| Service | Status | Endpoint |
|---|---|---|
| Ollama | ✅ Running | http://localhost:11434 |
| pgvector | ✅ Available | Docker container postgres-pgvector |
| Redis | ✅ Available | Docker container redis-local |
| Mockoon CLI | Available | Can start for API mocking |
qwen2.5-coder:3b- Main coding modelnomic-embed-text:latest- Embeddings
- Write test first - Define expected behavior before implementation
- Run tests - Ensure new tests FAIL initially
- Implement - Write minimal code to pass test
- Refactor - Improve while keeping tests green
- Run full suite - Ensure no regressions
// tests/unit/cart-tools.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
describe('Cart Tools', () => {
let mockPrisma: any;
beforeEach(() => {
mockPrisma = {
cart: {
findUnique: vi.fn(),
update: vi.fn(),
},
cartItem: {
findFirst: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
},
};
});
describe('cart.update_quantity', () => {
it('should increase quantity when positive number provided', async () => {
// Arrange
const tool = await import('@/lib/mcp/tools/cart');
mockPrisma.cart.findUnique.mockResolvedValue({
id: 'user-1',
items: [{ productId: 'prod-1', quantity: 1 }],
});
mockPrisma.cartItem.findFirst.mockResolvedValue({
id: 'item-1',
productId: 'prod-1',
quantity: 1,
});
// Act
const result = await tool.updateQuantity('user-1', 'prod-1', 3);
// Assert
expect(result.success).toBe(true);
expect(result.data.quantity).toBe(3);
});
it('should remove item when quantity is 0', async () => {
// Arrange - remove item on quantity 0
mockPrisma.cartItem.delete.mockResolvedValue(true);
// Act
const result = await tool.updateQuantity('user-1', 'prod-1', 0);
// Assert
expect(mockPrisma.cartItem.delete).toHaveBeenCalled();
expect(result.success).toBe(true);
});
it('should return error for negative quantity', async () => {
// Act
const result = await tool.updateQuantity('user-1', 'prod-1', -1);
// Assert
expect(result.success).toBe(false);
expect(result.error).toContain('positive');
});
});
});// lib/mcp/tools/cart.ts
import { z } from 'zod';
export const UpdateQuantitySchema = z.object({
productId: z.string(),
quantity: z.number().int().min(0),
});
export async function updateQuantity(userId: string, productId: string, quantity: number) {
if (quantity < 0) {
return { success: false, error: 'Quantity must be non-negative' };
}
if (quantity === 0) {
await prisma.cartItem.delete({ where: { productId } });
} else {
await prisma.cartItem.update({
where: { productId },
data: { quantity },
});
}
return { success: true, data: { quantity } };
}describe('cart.remove_item', () => {
it('should remove item from cart', async () => {
// Test removes item by productId
});
it('should return error if item not in cart', async () => {
// Test handles missing item
});
});describe('cart.apply_coupon', () => {
it('should apply valid coupon', async () => {
// Test applies discount
});
it('should reject expired coupon', async () => {
// Test handles expired coupon
});
it('should reject invalid code', async () => {
// Test handles invalid coupon
});
});// tests/unit/payments.test.ts
describe('payments.create_checkout_session', () => {
it('should create Stripe checkout session', async () => {
// Mock Stripe
const mockStripe = {
checkout: {
sessions: {
create: vi.fn().mockResolvedValue({ id: 'cs_test', url: 'https://checkout.stripe.com/pay/cs_test' }),
},
},
};
const result = await createCheckoutSession({
cartId: 'cart-1',
successUrl: 'https://example.com/success',
cancelUrl: 'https://example.com/cancel',
});
expect(result.success).toBe(true);
expect(result.data.url).toContain('checkout.stripe.com');
});
it('should return error for empty cart', async () => {
const result = await createCheckoutSession({
cartId: 'empty-cart',
});
expect(result.success).toBe(false);
expect(result.error).toContain('empty');
});
});describe('orders.create_from_cart', () => {
it('should create order from cart items', async () => {
const result = await createOrderFromCart({
cartId: 'cart-1',
addressId: 'addr-1',
});
expect(result.success).toBe(true);
expect(result.data.orderId).toBeDefined();
expect(result.data.total).toBeGreaterThan(0);
});
it('should clear cart after order', async () => {
await createOrderFromCart({ cartId: 'cart-1' });
expect(mockPrisma.cartItem.deleteMany).toHaveBeenCalled();
});
});// tests/unit/genui/adapter.test.ts
describe('GenUI Adapter', () => {
it('should map cart tool result to CartDrawer component', () => {
const result = {
success: true,
data: { items: [], total: 0 },
};
const ui = mapToGenUI(result, 'cart');
expect(ui.component).toBe('CartDrawer');
expect(ui.props.items).toEqual([]);
});
it('should map search result to ProductGrid', () => {
const result = {
success: true,
data: { products: [{ id: '1', name: 'Test' }] },
};
const ui = mapToGenUI(result, 'search');
expect(ui.component).toBe('ProductGrid');
});
});// tests/components/cart-drawer.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
describe('CartDrawer', () => {
it('should display cart items', () => {
const items = [
{ productId: '1', name: 'Product 1', quantity: 2, price: 10 },
];
render(<CartDrawer items={items} total={20} />);
expect(screen.getByText('Product 1')).toBeInTheDocument();
expect(screen.getByText('$20.00')).toBeInTheDocument();
});
it('should call updateQuantity when + button clicked', async () => {
const updateQty = vi.fn();
render(<CartDrawer items={[]} onUpdateQuantity={updateQty} />);
fireEvent.click(screen.getByText('+'));
expect(updateQty).toHaveBeenCalled();
});
});// tests/unit/ucp/actions.test.ts
import { UCPAction, validateUCPAction } from '@/lib/ucp/actions';
describe('UCP Actions', () => {
it('should validate ADD_TO_CART payload', () => {
const payload = {
productId: 'prod-1',
quantity: 2,
};
const result = validateUCPAction('ADD_TO_CART', payload);
expect(result.valid).toBe(true);
});
it('should reject invalid payload', () => {
const payload = {
// missing productId
quantity: 2,
};
const result = validateUCPAction('ADD_TO_CART', payload);
expect(result.valid).toBe(false);
});
it('should generate correlation ID', () => {
const id = generateUCPCorrelationId('ADD_TO_CART', 'user-1');
expect(id).toMatch(/^add_to_cart:user-1:/);
});
});// tests/e2e/commerce-flow.test.ts
import { test, expect } from '@playwright/test';
test.describe('Commerce Flow', () => {
test('complete purchase flow', async ({ page }) => {
// 1. Search for product
await page.goto('/store');
await page.fill('[data-testid=search]', 'headphones');
await page.click('[data-testid=search-button]');
// 2. Add to cart
await page.click('[data-testid=add-to-cart-1]');
// 3. Open cart
await page.click('[data-testid=cart-button]');
expect(page.locator('[data-testid=cart-item]')).toHaveCount(1);
// 4. Checkout
await page.click('[data-testid=checkout-button]');
await page.fill('[data-testid=address]', '123 Main St');
await page.click('[data-testid=place-order]');
// 5. Verify confirmation
await expect(page.locator('[data-testid=order-confirmation]')).toBeVisible();
});
});- 1.1 Write test:
tests/unit/cart-tools.test.tsforupdate_quantity - 1.2 Run test → FAIL
- 1.3 Implement:
lib/mcp/tools/cart.ts-updateQuantity() - 1.4 Run test → PASS
- 1.5 Write test:
remove_item - 1.6 Implement:
removeItem() - 1.7 Write test:
apply_coupon - 1.8 Implement:
applyCoupon() - 1.9 Run full test suite → ensure no regressions
- 2.1 Write test:
tests/unit/payments.test.ts- checkout session - 2.2 Implement:
lib/mcp/tools/payments.ts- create checkout - 2.3 Write test: orders.create_from_cart
- 2.4 Implement: create from cart flow
- 3.1 Write test: GenUI adapter mapping
- 3.2 Implement:
lib/genui/adapter.ts - 3.3 Write test: CartDrawer component
- 3.4 Implement:
components/commerce/cart-drawer.tsx - 3.5 Integration test: tool → GenUI render
- 4.1 Write test: UCP action validation
- 4.2 Implement:
lib/ucp/actions.ts- real actions - 4.3 Update tools to return UCP format
- 4.4 Run full test suite
- 5.1 Write E2E test: full commerce flow
- 5.2 Run E2E test → FAIL
- 5.3 Fix any integration issues
- 5.4 Run E2E test → PASS
- 5.5 Final regression test
# Run unit tests
npm test -- tests/unit/
# Run specific test file
npm test -- tests/unit/cart-tools.test.ts
# Run with coverage
npm test -- --coverage# Run component tests
npm test -- tests/components/
# Run with vitest UI
npm test -- --ui# Run E2E tests
npm run test:e2e
# Run specific E2E test
npm run test:e2e -- tests/e2e/commerce-flow.test.ts
# Run in headed mode (see browser)
npm run test:e2e -- --headed# Run full test suite
npm test && npm run test:e2e| Command | Description |
|---|---|
npm test |
Run unit tests |
npm run test:e2e |
Run E2E tests |
npm run dev |
Start dev server |
npm run build |
Build for production |
By following this TDD plan:
- No regressions - Every change verified by tests
- Documented behavior - Tests serve as spec
- Confidence - Green tests = working features
- Faster debugging - Failed tests pinpoint issues
- Portfolio ready - Demonstrates TDD competency
After completing all phases:
# Run full test suite
npm test
# Run E2E tests
npm run test:e2e
# Check coverage
npm test -- --coverage
# Start dev server for manual testing
npm run devSuccess Criteria:
- ✅ All unit tests passing
- ✅ All E2E tests passing
- ✅ No regressions in existing tests
- ✅ Full commerce flow working end-to-end