Files
gridpilot.gg/docs/CONTRACT_TESTING.md
2025-12-24 00:01:01 +01:00

7.1 KiB

Contract Testing Documentation

Overview

This document describes the contract testing strategy for ensuring compatibility between the API (apps/api) and the website (apps/website) in the GridPilot monorepo.

Architecture

The contract testing system consists of several layers:

1. API Contract Validation

  • Location: apps/api/src/shared/testing/contractValidation.test.ts
  • Purpose: Validates that API DTOs are consistent and generate valid OpenAPI specs
  • Tests:
    • OpenAPI spec integrity
    • DTO consistency
    • Type generation integrity
    • Contract compatibility

2. Type Generation

  • Scripts:
    • npm run api:generate-spec - Generates OpenAPI spec from DTOs
    • npm run api:generate-types - Generates TypeScript types for website
    • npm run api:sync-types - Runs both in sequence
  • Output: apps/website/lib/types/generated/

3. Website Contract Consumption

  • Location: apps/website/lib/types/contractConsumption.test.ts
  • Purpose: Validates that website can properly consume generated types
  • Tests:
    • Generated types availability
    • Type compatibility
    • Service integration
    • Error handling

4. Compatibility Verification

  • Location: scripts/contract-compatibility.ts
  • Purpose: Detects breaking changes between type versions
  • Features:
    • Backup current types
    • Generate new types
    • Compare and detect changes
    • Report breaking changes

Workflow

Local Development

# Run full contract testing suite
npm run test:contracts

# Or run individual steps:
npm run test:api:contracts          # Validate API contracts
npm run api:generate-spec           # Generate OpenAPI spec
npm run api:generate-types          # Generate types
npm run test:contract:compatibility # Check compatibility
npm run website:type-check          # Verify website types

CI/CD Pipeline

The GitHub Actions workflow (.github/workflows/contract-testing.yml) automatically:

  1. On Pull Requests:

    • Runs all contract tests
    • Validates API contracts
    • Generates types
    • Checks for breaking changes
    • Verifies website type checking
    • Uploads generated types as artifacts
    • Comments results on PR
  2. On Main Branch Push:

    • Runs all tests
    • Generates and commits updated types

Breaking Change Detection

The system detects several types of changes:

Breaking Changes ( Fails CI)

  • Property Removal: Required properties removed from DTOs
  • Type Changes: Property types changed (e.g., stringnumber)
  • Required Field Addition: New required fields added to existing DTOs

Non-Breaking Changes (⚠️ Warning)

  • Property Addition: Optional properties added
  • New DTOs: New DTO types added
  • Documentation Changes: Description updates

Removed Changes ( Fails CI)

  • DTO Removal: Entire DTO types removed
  • Property Removal: Optional properties removed

Generated Types Structure

The generated types follow this pattern:

/**
 * Auto-generated DTO from OpenAPI spec
 * This file is generated by scripts/generate-api-types.ts
 * Do not edit manually - regenerate using: npm run api:sync-types
 */

import type { RelatedDTO } from './RelatedDTO';

export interface MyDTO {
  id: string;
  name: string;
  optionalField?: string;
  related?: RelatedDTO;
}

Testing Strategy

API Layer Tests

// apps/api/src/shared/testing/contractValidation.test.ts
describe('API Contract Validation', () => {
  it('should have valid OpenAPI spec', async () => {
    // Validates spec structure
  });
  
  it('should have no circular references', async () => {
    // Prevents infinite loops
  });
  
  it('should maintain backward compatibility', async () => {
    // Ensures critical DTOs exist
  });
});

Website Layer Tests

// apps/website/lib/types/contractConsumption.test.ts
describe('Website Contract Consumption', () => {
  it('should allow creating valid DTO objects', () => {
    // Type-safe object creation
  });
  
  it('should work with service layer patterns', () => {
    // Integration with services
  });
  
  it('should handle error cases', () => {
    // Error handling patterns
  });
});

Integration Points

Service Layer Integration

Services in the website can import and use generated types:

import type { RequestAvatarGenerationInputDTO } from '@/lib/types/generated/RequestAvatarGenerationInputDTO';
import type { RequestAvatarGenerationOutputDTO } from '@/lib/types/generated/RequestAvatarGenerationOutputDTO';

class AvatarService {
  async generateAvatar(input: RequestAvatarGenerationInputDTO): Promise<RequestAvatarGenerationOutputDTO> {
    const response = await apiClient.post('/avatar/generate', input);
    return response.data; // Type-safe
  }
}

View Model Integration

View models can transform DTOs for UI consumption:

import type { RaceDTO } from '@/lib/types/generated/RaceDTO';

class RaceViewModel {
  constructor(private dto: RaceDTO) {}
  
  get displayDate(): string {
    return new Date(this.dto.startTime).toLocaleDateString();
  }
}

Troubleshooting

Common Issues

  1. Type Generation Fails

    • Check that DTOs have proper @ApiProperty decorators
    • Verify OpenAPI spec is valid JSON
    • Ensure all referenced types exist
  2. Breaking Changes Detected

    • Review the change report
    • Update website code to handle new types
    • Consider versioning strategy for major changes
  3. Website Type Errors

    • Run npm run api:sync-types to regenerate
    • Check import paths in website code
    • Verify TypeScript configuration

Debugging Steps

  1. Check OpenAPI Spec:

    npm run api:generate-spec
    cat apps/api/openapi.json | jq '.components.schemas'
    
  2. Compare Generated Types:

    # Backup current types
    cp -r apps/website/lib/types/generated /tmp/types-backup
    
    # Regenerate
    npm run api:generate-types
    
    # Compare
    diff -r /tmp/types-backup apps/website/lib/types/generated
    
  3. Run Individual Tests:

    # API tests
    npm run test:api:contracts
    
    # Website type checking
    npm run website:type-check
    

Best Practices

For API Developers

  1. Always use @ApiProperty decorators with proper types
  2. Mark optional fields explicitly with required: false
  3. Use proper TypeScript types (avoid any)
  4. Add descriptions to DTO properties
  5. Test DTOs with contract validation tests

For Website Developers

  1. Import from generated types, not manual types
  2. Use type assertions when consuming API responses
  3. Handle optional fields properly
  4. Run contract tests before committing type changes
  5. Update view models when DTOs change

For CI/CD

  1. Run contract tests on every PR
  2. Block merges on breaking changes
  3. Generate types on main branch pushes
  4. Upload artifacts for debugging
  5. Comment results on PRs

Future Enhancements

  • Add API versioning support
  • Generate client SDKs from OpenAPI
  • Add contract testing for WebSocket events
  • Implement schema registry
  • Add automated migration scripts for breaking changes