45 minlesson

Phase 5: Integration, Shared Infrastructure & Testing

Phase 5: Integration, Shared Infrastructure & Testing

Overview

Now that all three vertical slices are built, it's time to integrate them into a cohesive application. This phase focuses on testing integration points, building shared infrastructure, and ensuring all event-driven communication works seamlessly.

Learning Objectives

  • Test event-driven integration between features
  • Build shared component library
  • Implement unified auth and data layer
  • Write comprehensive integration tests
  • Resolve integration issues collaboratively

Requirements

1. Integration Testing - Event Flow

Test Complete Workflows:

Create src/__tests__/integration/shipment-workflow.test.ts:

typescript
1import { eventBus } from '@/services/event-bus';
2import { EventType } from '@/shared/types/events';
3
4describe('Complete Shipment Workflow', () => {
5 beforeEach(() => {
6 // Reset event bus and clear storage
7 jest.clearAllMocks();
8 });
9
10 it('should create shipment and trigger tracking initialization', async () => {
11 const trackingEvents: any[] = [];
12
13 // Subscribe to SHIPMENT_CREATED (Person 2 simulation)
14 eventBus.subscribe(EventType.SHIPMENT_CREATED, async (event) => {
15 trackingEvents.push(event);
16 });
17
18 // Create shipment (Person 1)
19 const shipment = await createShipment(mockShipmentData);
20
21 // Wait for event propagation
22 await new Promise(resolve => setTimeout(resolve, 100));
23
24 // Verify event was emitted
25 expect(trackingEvents).toHaveLength(1);
26 expect(trackingEvents[0].payload.shipment.id).toBe(shipment.id);
27 });
28
29 it('should propagate status changes to notifications', async () => {
30 const notifications: any[] = [];
31
32 // Subscribe to STATUS_CHANGED (Person 3 simulation)
33 eventBus.subscribe(EventType.SHIPMENT_STATUS_CHANGED, async (event) => {
34 notifications.push(event);
35 });
36
37 // Update status (Person 2)
38 await updateShipmentStatus('ship-1', ShipmentStatus.DELIVERED);
39
40 await new Promise(resolve => setTimeout(resolve, 100));
41
42 // Verify notification triggered
43 expect(notifications).toHaveLength(1);
44 expect(notifications[0].payload.newStatus).toBe(ShipmentStatus.DELIVERED);
45 });
46
47 it('should support reorder workflow', async () => {
48 // Person 1: Create initial shipment
49 const originalShipment = await createShipment(mockShipmentData);
50
51 // Person 3: Reorder using facade
52 const facade = new OrderManagementFacade();
53 const newOrder = await facade.reorder('user-1', originalShipment.id);
54
55 // Verify new shipment created with same details
56 expect(newOrder.orderId).not.toBe(originalShipment.orderId);
57 expect(newOrder.cost).toBe(originalShipment.cost);
58 });
59});

2. Shared Component Library

Build Common UI Components:

Create src/shared/components/ui/ with:

typescript
1// Button.tsx
2export function Button({ variant, size, children, ...props }: ButtonProps) {
3 return (
4 <button
5 className={cn(
6 'rounded-lg font-medium transition-colors',
7 variantStyles[variant],
8 sizeStyles[size]
9 )}
10 {...props}
11 >
12 {children}
13 </button>
14 );
15}
16
17// Card.tsx
18export function Card({ title, children }: CardProps) {
19 return (
20 <div className="bg-white rounded-lg shadow-md p-6">
21 {title && <h3 className="text-lg font-semibold mb-4">{title}</h3>}
22 {children}
23 </div>
24 );
25}
26
27// Input.tsx
28export function Input({ label, error, ...props }: InputProps) {
29 return (
30 <div>
31 {label && <label className="block text-sm font-medium mb-1">{label}</label>}
32 <input
33 className={cn(
34 'w-full rounded-md border px-3 py-2',
35 error ? 'border-red-500' : 'border-gray-300'
36 )}
37 {...props}
38 />
39 {error && <p className="text-sm text-red-500 mt-1">{error}</p>}
40 </div>
41 );
42}
43
44// Badge.tsx
45export function Badge({ status }: { status: ShipmentStatus }) {
46 const styles = {
47 [ShipmentStatus.CREATED]: 'bg-gray-100 text-gray-800',
48 [ShipmentStatus.PICKED_UP]: 'bg-blue-100 text-blue-800',
49 [ShipmentStatus.IN_TRANSIT]: 'bg-yellow-100 text-yellow-800',
50 [ShipmentStatus.OUT_FOR_DELIVERY]: 'bg-orange-100 text-orange-800',
51 [ShipmentStatus.DELIVERED]: 'bg-green-100 text-green-800',
52 [ShipmentStatus.EXCEPTION]: 'bg-red-100 text-red-800',
53 };
54
55 return (
56 <span className={cn('px-2 py-1 rounded-full text-xs font-medium', styles[status])}>
57 {status}
58 </span>
59 );
60}
61
62// LoadingSpinner.tsx
63export function LoadingSpinner({ size = 'md' }: { size?: 'sm' | 'md' | 'lg' }) {
64 return (
65 <div className={cn('animate-spin rounded-full border-b-2', sizeClasses[size])} />
66 );
67}

3. Unified Authentication

Create src/shared/services/auth-service.ts:

typescript
1export interface User {
2 id: string;
3 email: string;
4 name: string;
5 role: 'customer' | 'admin' | 'driver';
6}
7
8class AuthService {
9 private currentUser: User | null = null;
10
11 async login(email: string, password: string): Promise<User> {
12 // Mock authentication
13 const user: User = {
14 id: crypto.randomUUID(),
15 email,
16 name: 'Test User',
17 role: 'customer',
18 };
19
20 this.currentUser = user;
21 localStorage.setItem('user', JSON.stringify(user));
22
23 return user;
24 }
25
26 async logout(): Promise<void> {
27 this.currentUser = null;
28 localStorage.removeItem('user');
29 }
30
31 getCurrentUser(): User | null {
32 if (this.currentUser) {
33 return this.currentUser;
34 }
35
36 const stored = localStorage.getItem('user');
37 if (stored) {
38 this.currentUser = JSON.parse(stored);
39 }
40
41 return this.currentUser;
42 }
43
44 isAuthenticated(): boolean {
45 return this.getCurrentUser() !== null;
46 }
47
48 hasRole(role: User['role']): boolean {
49 const user = this.getCurrentUser();
50 return user?.role === role;
51 }
52}
53
54export const authService = new AuthService();

Create src/shared/components/ProtectedRoute.tsx:

typescript
1'use client'
2
3export function ProtectedRoute({ children, requireRole }: ProtectedRouteProps) {
4 const user = authService.getCurrentUser();
5
6 if (!user) {
7 return <Navigate to="/login" />;
8 }
9
10 if (requireRole && user.role !== requireRole) {
11 return <div>Unauthorized</div>;
12 }
13
14 return <>{children}</>;
15}

4. Unified Data Layer

Create src/shared/services/storage-service.ts:

typescript
1class StorageService {
2 private readonly SHIPMENTS_KEY = 'shipments';
3 private readonly TRACKING_KEY = 'tracking_events';
4 private readonly NOTIFICATIONS_KEY = 'notifications';
5
6 // Shipments (Person 1)
7 async saveShipment(shipment: Shipment): Promise<void> {
8 const shipments = this.getShipments();
9 const index = shipments.findIndex(s => s.id === shipment.id);
10
11 if (index >= 0) {
12 shipments[index] = shipment;
13 } else {
14 shipments.push(shipment);
15 }
16
17 localStorage.setItem(this.SHIPMENTS_KEY, JSON.stringify(shipments));
18 }
19
20 getShipments(): Shipment[] {
21 const data = localStorage.getItem(this.SHIPMENTS_KEY);
22 return data ? JSON.parse(data) : [];
23 }
24
25 getShipment(id: string): Shipment | null {
26 const shipments = this.getShipments();
27 return shipments.find(s => s.id === id) || null;
28 }
29
30 // Tracking Events (Person 2)
31 async saveTrackingEvent(event: TrackingEvent): Promise<void> {
32 const events = this.getTrackingEvents();
33 events.push(event);
34 localStorage.setItem(this.TRACKING_KEY, JSON.stringify(events));
35 }
36
37 getTrackingEvents(shipmentId?: string): TrackingEvent[] {
38 const data = localStorage.getItem(this.TRACKING_KEY);
39 const events: TrackingEvent[] = data ? JSON.parse(data) : [];
40
41 return shipmentId
42 ? events.filter(e => e.shipmentId === shipmentId)
43 : events;
44 }
45
46 // Notifications (Person 3)
47 async saveNotification(notification: Notification): Promise<void> {
48 const notifications = this.getNotifications();
49 notifications.push(notification);
50 localStorage.setItem(this.NOTIFICATIONS_KEY, JSON.stringify(notifications));
51 }
52
53 getNotifications(userId?: string): Notification[] {
54 const data = localStorage.getItem(this.NOTIFICATIONS_KEY);
55 const notifications: Notification[] = data ? JSON.parse(data) : [];
56
57 return userId
58 ? notifications.filter(n => n.userId === userId)
59 : notifications;
60 }
61
62 // Clear all data
63 clearAll(): void {
64 localStorage.removeItem(this.SHIPMENTS_KEY);
65 localStorage.removeItem(this.TRACKING_KEY);
66 localStorage.removeItem(this.NOTIFICATIONS_KEY);
67 }
68}
69
70export const storageService = new StorageService();

5. Integration Issue Resolution

Common Integration Issues:

  1. Event Not Received

    • Check EventBus subscription is active
    • Verify event type matches exactly
    • Ensure publish is awaited
  2. Type Mismatches

    • Use shared types from src/shared/types/
    • Never duplicate type definitions
    • Run npm run typecheck frequently
  3. Merge Conflicts

    • Communicate before changing shared files
    • Pull from develop frequently
    • Resolve conflicts immediately
  4. Data Inconsistency

    • Use centralized StorageService
    • Validate data at boundaries
    • Test with real data scenarios

6. Code Review Process

Team Code Review Checklist:

For each PR, reviewers check:

  • Follows shared type definitions
  • Pattern implementation is correct
  • Tests included and passing
  • No duplicate code from other slices
  • Event integration works correctly
  • No console.logs or debugging code
  • TypeScript errors resolved
  • Accessibility compliant
  • Mobile responsive

Review Rotation:

  • Person 1's code reviewed by Person 2 & 3
  • Person 2's code reviewed by Person 1 & 3
  • Person 3's code reviewed by Person 1 & 2

7. End-to-End Testing

Create e2e/complete-workflow.spec.ts:

typescript
1import { test, expect } from '@playwright/test';
2
3test.describe('Complete Platform Workflow', () => {
4 test('should create shipment, track status, and receive notification', async ({ page }) => {
5 // Login
6 await page.goto('http://localhost:3000/login');
7 await page.fill('[name="email"]', 'test@example.com');
8 await page.fill('[name="password"]', 'password');
9 await page.click('button:text("Login")');
10
11 // Create shipment (Person 1's feature)
12 await page.goto('/shipments/create');
13 await page.fill('[name="package.weight"]', '10');
14 await page.fill('[name="origin.postalCode"]', '10001');
15 await page.fill('[name="destination.postalCode"]', '90001');
16 await page.click('button:text("Create Shipment")');
17
18 // Verify tracking initialized (Person 2's feature)
19 const trackingNumber = await page.locator('[data-testid="tracking-number"]').textContent();
20 await page.goto(`/tracking/${trackingNumber}`);
21 await expect(page.locator('text=/created/i')).toBeVisible();
22
23 // Check notification sent (Person 3's feature)
24 await page.goto('/portal/notifications');
25 await expect(page.locator('text=/shipment created/i')).toBeVisible();
26
27 // Verify analytics updated (Person 3's feature)
28 await page.goto('/portal/analytics');
29 await expect(page.locator('[data-testid="total-shipments"]')).toContainText('1');
30 });
31});

Deliverables

  • All vertical slices integrated successfully
  • Event-driven communication tested and working
  • Shared component library (10+ components)
  • Unified authentication system
  • Centralized data layer (StorageService)
  • Integration tests (20+ tests, all workflows covered)
  • E2E tests (5+ critical paths)
  • Code reviews completed for all features
  • Test coverage 70%+ overall
  • No TypeScript errors across entire codebase

Testing Requirements Summary

Unit Tests: Each person maintains 70%+ coverage for their vertical slice

Integration Tests: Team writes together

  • Shipment creation → Tracking initialization
  • Status update → Notification sending
  • Reorder → Shipment creation

E2E Tests: Team writes together

  • Complete user journey (create → track → notify)
  • Exception handling workflow
  • Analytics updates

Total Coverage Target: 70%+


Validation Checklist

  • All event subscribers receive events correctly
  • No duplicate data in storage
  • Type safety maintained across boundaries
  • All team members can run full test suite
  • No merge conflicts in develop branch
  • Shared components used consistently
  • Authentication works across all features
  • Mobile and desktop views work correctly

Team Coordination

Integration Sprint Schedule:

Week 1:

  • Day 1-2: Integrate Person 1 & 2 (Shipment → Tracking)
  • Day 3-4: Integrate Person 2 & 3 (Tracking → Notifications)
  • Day 5: Integrate Person 3 & 1 (Portal → Shipments)

Week 2:

  • Day 1-2: Build shared components
  • Day 3-4: Write integration tests
  • Day 5: Code reviews and refinements

Resources


Estimated Time

  • Integration: 8 hours (team)
  • Shared infrastructure: 6 hours (distributed)
  • Testing: 10 hours (team)
  • Code reviews: 4 hours (team)
  • Total: 28 hours (distributed across team)

Next Steps

Proceed to Phase 6: Demo, Documentation & Deployment for final polish, comprehensive documentation, team demo creation, and deployment.