723 lines
17 KiB
Markdown
723 lines
17 KiB
Markdown
# Integration & Polish Operation
|
|
|
|
Complete integration testing, performance optimization, security hardening, and documentation for a feature.
|
|
|
|
## Parameters
|
|
|
|
**Received**: `$ARGUMENTS` (after removing 'integrate' operation name)
|
|
|
|
Expected format: `feature:"feature name" [scope:"e2e|performance|security|documentation"] [priority:"high|medium|low"]`
|
|
|
|
## Workflow
|
|
|
|
### 1. End-to-End Testing
|
|
|
|
Create comprehensive E2E tests covering critical user workflows.
|
|
|
|
#### Using Playwright
|
|
|
|
```typescript
|
|
// e2e/products.spec.ts
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Product Management', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('http://localhost:3000');
|
|
});
|
|
|
|
test('should complete full product browsing flow', async ({ page }) => {
|
|
// Navigate to products page
|
|
await page.click('text=Products');
|
|
await expect(page).toHaveURL(/\/products/);
|
|
|
|
// Verify products are loaded
|
|
await expect(page.locator('.product-card')).toHaveCount(20, { timeout: 10000 });
|
|
|
|
// Filter by category
|
|
await page.click('text=Electronics');
|
|
await expect(page.locator('.product-card')).toHaveCount(5);
|
|
|
|
// Search for product
|
|
await page.fill('input[placeholder="Search products"]', 'laptop');
|
|
await page.keyboard.press('Enter');
|
|
await expect(page.locator('.product-card')).toHaveCount(2);
|
|
|
|
// Click on first product
|
|
await page.click('.product-card:first-child');
|
|
await expect(page).toHaveURL(/\/products\/[a-z0-9-]+/);
|
|
|
|
// Verify product details
|
|
await expect(page.locator('h1')).toContainText('Laptop');
|
|
await expect(page.locator('.product-price')).toBeVisible();
|
|
|
|
// Add to cart
|
|
await page.click('button:has-text("Add to Cart")');
|
|
await expect(page.locator('.cart-badge')).toContainText('1');
|
|
});
|
|
|
|
test('should handle error states gracefully', async ({ page }) => {
|
|
// Simulate network error
|
|
await page.route('**/api/products', (route) => route.abort());
|
|
|
|
await page.goto('http://localhost:3000/products');
|
|
|
|
// Should show error message
|
|
await expect(page.locator('text=Failed to load products')).toBeVisible();
|
|
|
|
// Should have retry button
|
|
await expect(page.locator('button:has-text("Retry")')).toBeVisible();
|
|
});
|
|
|
|
test('should handle authentication flow', async ({ page }) => {
|
|
// Try to create product without auth
|
|
await page.goto('http://localhost:3000/products/new');
|
|
|
|
// Should redirect to login
|
|
await expect(page).toHaveURL(/\/login/);
|
|
|
|
// Login
|
|
await page.fill('input[name="email"]', 'admin@example.com');
|
|
await page.fill('input[name="password"]', 'Password123');
|
|
await page.click('button:has-text("Login")');
|
|
|
|
// Should redirect back to product creation
|
|
await expect(page).toHaveURL(/\/products\/new/);
|
|
|
|
// Create product
|
|
await page.fill('input[name="name"]', 'New Test Product');
|
|
await page.fill('textarea[name="description"]', 'Test description');
|
|
await page.fill('input[name="price"]', '99.99');
|
|
await page.fill('input[name="stockQuantity"]', '10');
|
|
|
|
await page.click('button:has-text("Create Product")');
|
|
|
|
// Should show success message
|
|
await expect(page.locator('text=Product created successfully')).toBeVisible();
|
|
});
|
|
|
|
test('should be accessible', async ({ page }) => {
|
|
await page.goto('http://localhost:3000/products');
|
|
|
|
// Check for proper heading hierarchy
|
|
const h1 = await page.locator('h1').count();
|
|
expect(h1).toBeGreaterThan(0);
|
|
|
|
// Check for alt text on images
|
|
const images = page.locator('img');
|
|
const count = await images.count();
|
|
for (let i = 0; i < count; i++) {
|
|
const alt = await images.nth(i).getAttribute('alt');
|
|
expect(alt).toBeTruthy();
|
|
}
|
|
|
|
// Check for keyboard navigation
|
|
await page.keyboard.press('Tab');
|
|
const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
|
|
expect(focusedElement).toBeTruthy();
|
|
});
|
|
|
|
test('should work on mobile devices', async ({ page, viewport }) => {
|
|
// Set mobile viewport
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
|
|
await page.goto('http://localhost:3000/products');
|
|
|
|
// Mobile menu should be visible
|
|
await expect(page.locator('[aria-label="Menu"]')).toBeVisible();
|
|
|
|
// Products should be in single column
|
|
const gridColumns = await page.locator('.product-grid').evaluate((el) => {
|
|
return window.getComputedStyle(el).gridTemplateColumns.split(' ').length;
|
|
});
|
|
|
|
expect(gridColumns).toBe(1);
|
|
});
|
|
});
|
|
```
|
|
|
|
### 2. Performance Optimization
|
|
|
|
#### Frontend Performance
|
|
|
|
```typescript
|
|
// Performance monitoring
|
|
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';
|
|
|
|
function sendToAnalytics(metric) {
|
|
console.log(metric);
|
|
// Send to analytics service
|
|
}
|
|
|
|
onCLS(sendToAnalytics);
|
|
onFID(sendToAnalytics);
|
|
onLCP(sendToAnalytics);
|
|
onFCP(sendToAnalytics);
|
|
onTTFB(sendToAnalytics);
|
|
|
|
// Code splitting
|
|
const ProductList = React.lazy(() => import('./features/products/components/ProductList'));
|
|
const ProductDetail = React.lazy(() => import('./features/products/components/ProductDetail'));
|
|
|
|
// Image optimization
|
|
<img
|
|
src={product.images[0].url}
|
|
srcSet={`
|
|
${product.images[0].url}?w=320 320w,
|
|
${product.images[0].url}?w=640 640w,
|
|
${product.images[0].url}?w=1024 1024w
|
|
`}
|
|
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
|
loading="lazy"
|
|
decoding="async"
|
|
/>
|
|
|
|
// Memoization
|
|
const MemoizedProductCard = React.memo(ProductCard, (prevProps, nextProps) => {
|
|
return prevProps.product.id === nextProps.product.id &&
|
|
prevProps.product.stockQuantity === nextProps.product.stockQuantity;
|
|
});
|
|
|
|
// Virtualization for long lists
|
|
import { FixedSizeList } from 'react-window';
|
|
|
|
const ProductVirtualList = ({ products }) => (
|
|
<FixedSizeList
|
|
height={600}
|
|
itemCount={products.length}
|
|
itemSize={200}
|
|
width="100%"
|
|
>
|
|
{({ index, style }) => (
|
|
<div style={style}>
|
|
<ProductCard product={products[index]} />
|
|
</div>
|
|
)}
|
|
</FixedSizeList>
|
|
);
|
|
```
|
|
|
|
#### Backend Performance
|
|
|
|
```typescript
|
|
// Database query optimization
|
|
// Add indexes (already in database.md)
|
|
|
|
// Query result caching
|
|
import { Redis } from 'ioredis';
|
|
const redis = new Redis();
|
|
|
|
async function getCachedProducts(filters: ProductFilters) {
|
|
const cacheKey = `products:${JSON.stringify(filters)}`;
|
|
const cached = await redis.get(cacheKey);
|
|
|
|
if (cached) {
|
|
return JSON.parse(cached);
|
|
}
|
|
|
|
const products = await productRepository.findAll(filters, pagination);
|
|
await redis.setex(cacheKey, 300, JSON.stringify(products)); // 5 minutes
|
|
|
|
return products;
|
|
}
|
|
|
|
// N+1 query prevention
|
|
const products = await productRepository.find({
|
|
relations: ['category', 'images', 'tags'], // Eager load
|
|
});
|
|
|
|
// Response compression
|
|
import compression from 'compression';
|
|
app.use(compression());
|
|
|
|
// Connection pooling (already configured in database setup)
|
|
|
|
// API response caching
|
|
import apicache from 'apicache';
|
|
app.use('/api/products', apicache.middleware('5 minutes'));
|
|
```
|
|
|
|
### 3. Security Hardening
|
|
|
|
#### Input Validation & Sanitization
|
|
|
|
```typescript
|
|
// Backend validation (already in backend.md with Zod)
|
|
|
|
// SQL Injection prevention (using parameterized queries with TypeORM)
|
|
|
|
// XSS Prevention
|
|
import DOMPurify from 'dompurify';
|
|
|
|
function sanitizeHtml(dirty: string): string {
|
|
return DOMPurify.sanitize(dirty);
|
|
}
|
|
|
|
// In component
|
|
<div dangerouslySetInnerHTML={{ __html: sanitizeHtml(product.description) }} />
|
|
```
|
|
|
|
#### Security Headers
|
|
|
|
```typescript
|
|
// helmet middleware
|
|
import helmet from 'helmet';
|
|
|
|
app.use(helmet());
|
|
|
|
app.use(helmet.contentSecurityPolicy({
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
scriptSrc: ["'self'"],
|
|
imgSrc: ["'self'", 'data:', 'https:'],
|
|
connectSrc: ["'self'", process.env.API_URL],
|
|
},
|
|
}));
|
|
|
|
// CORS configuration
|
|
import cors from 'cors';
|
|
|
|
app.use(cors({
|
|
origin: process.env.FRONTEND_URL,
|
|
credentials: true,
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
}));
|
|
```
|
|
|
|
#### Rate Limiting
|
|
|
|
```typescript
|
|
import rateLimit from 'express-rate-limit';
|
|
|
|
// General API rate limit
|
|
const apiLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 100, // 100 requests per window
|
|
message: 'Too many requests from this IP',
|
|
});
|
|
|
|
app.use('/api/', apiLimiter);
|
|
|
|
// Stricter rate limit for mutations
|
|
const createLimiter = rateLimit({
|
|
windowMs: 60 * 60 * 1000, // 1 hour
|
|
max: 10, // 10 creates per hour
|
|
});
|
|
|
|
app.use('/api/products', createLimiter);
|
|
```
|
|
|
|
#### Authentication & Authorization
|
|
|
|
```typescript
|
|
// JWT validation middleware (already in backend.md)
|
|
|
|
// RBAC (Role-Based Access Control)
|
|
function authorize(...allowedRoles: string[]) {
|
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
if (!req.user) {
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
if (!allowedRoles.includes(req.user.role)) {
|
|
return res.status(403).json({ error: 'Forbidden' });
|
|
}
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
// Usage
|
|
router.post('/products', authenticate, authorize('admin', 'editor'), createProduct);
|
|
```
|
|
|
|
### 4. Error Handling & Logging
|
|
|
|
```typescript
|
|
// Centralized error handler
|
|
class AppError extends Error {
|
|
constructor(
|
|
public statusCode: number,
|
|
public message: string,
|
|
public isOperational = true
|
|
) {
|
|
super(message);
|
|
Object.setPrototypeOf(this, AppError.prototype);
|
|
}
|
|
}
|
|
|
|
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
if (err instanceof AppError) {
|
|
return res.status(err.statusCode).json({
|
|
error: {
|
|
message: err.message,
|
|
statusCode: err.statusCode,
|
|
},
|
|
});
|
|
}
|
|
|
|
// Log unexpected errors
|
|
console.error('Unexpected error:', err);
|
|
|
|
res.status(500).json({
|
|
error: {
|
|
message: 'Internal server error',
|
|
statusCode: 500,
|
|
},
|
|
});
|
|
});
|
|
|
|
// Structured logging
|
|
import winston from 'winston';
|
|
|
|
const logger = winston.createLogger({
|
|
level: 'info',
|
|
format: winston.format.json(),
|
|
transports: [
|
|
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
|
new winston.transports.File({ filename: 'combined.log' }),
|
|
],
|
|
});
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
logger.add(new winston.transports.Console({
|
|
format: winston.format.simple(),
|
|
}));
|
|
}
|
|
|
|
// Request logging
|
|
import morgan from 'morgan';
|
|
app.use(morgan('combined', { stream: { write: (msg) => logger.info(msg) } }));
|
|
```
|
|
|
|
### 5. Documentation
|
|
|
|
#### API Documentation
|
|
|
|
```yaml
|
|
# openapi.yaml
|
|
openapi: 3.0.0
|
|
info:
|
|
title: Product API
|
|
version: 1.0.0
|
|
description: API for managing products
|
|
|
|
servers:
|
|
- url: http://localhost:3000/api
|
|
description: Local server
|
|
- url: https://api.example.com
|
|
description: Production server
|
|
|
|
paths:
|
|
/products:
|
|
get:
|
|
summary: List products
|
|
description: Retrieve a paginated list of products with optional filters
|
|
parameters:
|
|
- name: page
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 1
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 20
|
|
- name: categoryId
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
- name: search
|
|
in: query
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Product'
|
|
meta:
|
|
type: object
|
|
properties:
|
|
page:
|
|
type: integer
|
|
totalPages:
|
|
type: integer
|
|
total:
|
|
type: integer
|
|
|
|
post:
|
|
summary: Create product
|
|
security:
|
|
- bearerAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateProductInput'
|
|
responses:
|
|
'201':
|
|
description: Product created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Product'
|
|
'401':
|
|
description: Unauthorized
|
|
'400':
|
|
description: Invalid input
|
|
|
|
/products/{id}:
|
|
get:
|
|
summary: Get product by ID
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Product'
|
|
'404':
|
|
description: Product not found
|
|
|
|
components:
|
|
schemas:
|
|
Product:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
description:
|
|
type: string
|
|
price:
|
|
type: number
|
|
format: decimal
|
|
currency:
|
|
type: string
|
|
stockQuantity:
|
|
type: integer
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
|
|
CreateProductInput:
|
|
type: object
|
|
required:
|
|
- name
|
|
- price
|
|
- stockQuantity
|
|
properties:
|
|
name:
|
|
type: string
|
|
maxLength: 255
|
|
description:
|
|
type: string
|
|
price:
|
|
type: number
|
|
minimum: 0
|
|
stockQuantity:
|
|
type: integer
|
|
minimum: 0
|
|
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
```
|
|
|
|
#### Feature Documentation
|
|
|
|
```markdown
|
|
# Product Management Feature
|
|
|
|
## Overview
|
|
|
|
Complete product catalog management with support for categories, images, tags, search, and filtering.
|
|
|
|
## Features
|
|
|
|
- Product listing with pagination
|
|
- Product search and filtering
|
|
- Category hierarchy
|
|
- Multiple product images
|
|
- Tag management
|
|
- Stock tracking
|
|
- Soft delete support
|
|
|
|
## User Flows
|
|
|
|
### Browsing Products
|
|
|
|
1. User navigates to products page
|
|
2. Products are loaded with pagination (20 per page)
|
|
3. User can filter by category
|
|
4. User can search by name/description
|
|
5. User clicks on product to view details
|
|
|
|
### Creating Product (Admin)
|
|
|
|
1. Admin logs in
|
|
2. Admin navigates to "Create Product"
|
|
3. Admin fills in product details
|
|
4. Admin uploads product images
|
|
5. Admin selects category and tags
|
|
6. Admin submits form
|
|
7. Product is created and admin is redirected to product page
|
|
|
|
## API Usage Examples
|
|
|
|
### List Products
|
|
|
|
\`\`\`bash
|
|
curl -X GET "http://localhost:3000/api/products?page=1&limit=20&categoryId=abc123"
|
|
\`\`\`
|
|
|
|
### Create Product
|
|
|
|
\`\`\`bash
|
|
curl -X POST "http://localhost:3000/api/products" \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "New Product",
|
|
"description": "Product description",
|
|
"price": 99.99,
|
|
"stockQuantity": 10,
|
|
"categoryId": "abc123"
|
|
}'
|
|
\`\`\`
|
|
|
|
## Performance Characteristics
|
|
|
|
- API response time: <100ms (p95)
|
|
- Page load time: <2s (p95)
|
|
- Database queries: Optimized with indexes
|
|
- Image loading: Lazy loaded with srcSet
|
|
- List rendering: Virtualized for 1000+ items
|
|
|
|
## Security Measures
|
|
|
|
- JWT authentication for mutations
|
|
- Role-based access control (RBAC)
|
|
- Input validation on backend
|
|
- XSS protection with DOMPurify
|
|
- SQL injection prevention
|
|
- Rate limiting (100 req/15min)
|
|
- CORS configured
|
|
- Security headers with Helmet
|
|
|
|
## Known Limitations
|
|
|
|
- Maximum 10 images per product
|
|
- Product names limited to 255 characters
|
|
- Search limited to name and description
|
|
- Bulk operations not yet supported
|
|
|
|
## Future Enhancements
|
|
|
|
- [ ] Bulk product import/export
|
|
- [ ] Product variants (size, color)
|
|
- [ ] Advanced inventory management
|
|
- [ ] Product recommendations
|
|
- [ ] Analytics dashboard
|
|
```
|
|
|
|
## Output Format
|
|
|
|
```markdown
|
|
# Integration & Polish: {Feature Name}
|
|
|
|
## E2E Test Results
|
|
|
|
### Test Suites
|
|
- {suite_name}: {passed/failed} ({count} tests)
|
|
|
|
### Coverage
|
|
- User flows covered: {percentage}%
|
|
- Edge cases tested: {count}
|
|
|
|
## Performance Metrics
|
|
|
|
### Frontend
|
|
- LCP: {time}ms
|
|
- FID: {time}ms
|
|
- CLS: {score}
|
|
|
|
### Backend
|
|
- API response time (p95): {time}ms
|
|
- Database query time (p95): {time}ms
|
|
- Memory usage: {mb}MB
|
|
|
|
### Optimizations Applied
|
|
- {optimization_description}
|
|
|
|
## Security Audit
|
|
|
|
### Vulnerabilities Fixed
|
|
- {vulnerability}: {fix_description}
|
|
|
|
### Security Measures
|
|
- {measure_description}
|
|
|
|
## Documentation
|
|
|
|
### API Documentation
|
|
- {documentation_location}
|
|
|
|
### User Guide
|
|
- {guide_location}
|
|
|
|
### Developer Documentation
|
|
- {docs_location}
|
|
|
|
## Deployment Checklist
|
|
|
|
- [ ] All tests passing
|
|
- [ ] Performance benchmarks met
|
|
- [ ] Security audit completed
|
|
- [ ] Documentation updated
|
|
- [ ] Environment variables documented
|
|
- [ ] Monitoring configured
|
|
- [ ] Backup strategy in place
|
|
|
|
## Known Issues
|
|
|
|
- {issue_description}: {workaround}
|
|
|
|
## Next Steps
|
|
|
|
- {future_enhancement}
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
- If tests fail: Provide failure details and suggested fixes
|
|
- If performance targets not met: Suggest optimizations
|
|
- If security issues found: Provide remediation steps
|