7.1 KiB
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 DTOsnpm run api:generate-types- Generates TypeScript types for websitenpm 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:
-
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
-
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.,
string→number) - 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
-
Type Generation Fails
- Check that DTOs have proper
@ApiPropertydecorators - Verify OpenAPI spec is valid JSON
- Ensure all referenced types exist
- Check that DTOs have proper
-
Breaking Changes Detected
- Review the change report
- Update website code to handle new types
- Consider versioning strategy for major changes
-
Website Type Errors
- Run
npm run api:sync-typesto regenerate - Check import paths in website code
- Verify TypeScript configuration
- Run
Debugging Steps
-
Check OpenAPI Spec:
npm run api:generate-spec cat apps/api/openapi.json | jq '.components.schemas' -
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 -
Run Individual Tests:
# API tests npm run test:api:contracts # Website type checking npm run website:type-check
Best Practices
For API Developers
- Always use
@ApiPropertydecorators with proper types - Mark optional fields explicitly with
required: false - Use proper TypeScript types (avoid
any) - Add descriptions to DTO properties
- Test DTOs with contract validation tests
For Website Developers
- Import from generated types, not manual types
- Use type assertions when consuming API responses
- Handle optional fields properly
- Run contract tests before committing type changes
- Update view models when DTOs change
For CI/CD
- Run contract tests on every PR
- Block merges on breaking changes
- Generate types on main branch pushes
- Upload artifacts for debugging
- 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