contract testing

This commit is contained in:
2025-12-24 00:01:01 +01:00
parent 43a8afe7a9
commit 5e491d9724
52 changed files with 2058 additions and 612 deletions

271
docs/CONTRACT_TESTING.md Normal file
View File

@@ -0,0 +1,271 @@
# 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
```bash
# 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., `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:
```typescript
/**
* 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
```typescript
// 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
```typescript
// 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:
```typescript
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:
```typescript
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**:
```bash
npm run api:generate-spec
cat apps/api/openapi.json | jq '.components.schemas'
```
2. **Compare Generated Types**:
```bash
# 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**:
```bash
# 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

View File

@@ -0,0 +1,168 @@
# Contract Testing Quick Start Guide
## 🚀 Quick Setup
### 1. Run the Full Contract Test Suite
```bash
npm run test:contracts
```
This single command will:
- ✅ Validate API contracts
- ✅ Generate OpenAPI spec
- ✅ Generate TypeScript types
- ✅ Check for breaking changes
- ✅ Verify website type compatibility
### 2. Individual Commands
```bash
# Validate API contracts only
npm run test:api:contracts
# Generate types (after making DTO changes)
npm run api:sync-types
# Check compatibility (detect breaking changes)
npm run test:contract:compatibility
# Verify website can consume types
npm run website:type-check
```
## 📁 What Gets Created
### Generated Types
- **Location**: `apps/website/lib/types/generated/`
- **Files**: One `.ts` file per DTO (e.g., `RaceDTO.ts`, `DriverDTO.ts`)
- **Usage**: Import directly in website code
### Test Files
- **API Tests**: `apps/api/src/shared/testing/contractValidation.test.ts`
- **Website Tests**: `apps/website/lib/types/contractConsumption.test.ts`
### CI/CD
- **Workflow**: `.github/workflows/contract-testing.yml`
- **Triggers**: Pull requests and main branch pushes
## 🎯 Common Workflows
### Making API Changes
1. **Update DTO in API**:
```typescript
// apps/api/src/domain/race/dtos/RaceDTO.ts
export class RaceDTO {
@ApiProperty()
id: string;
@ApiProperty()
name: string;
@ApiProperty({ required: false })
description?: string; // New optional field
}
```
2. **Run contract tests**:
```bash
npm run test:contracts
```
3. **If tests pass**, commit your changes. The CI will regenerate types automatically.
### Updating Website Code
1. **Import generated types**:
```typescript
import type { RaceDTO } from '@/lib/types/generated/RaceDTO';
function RaceComponent({ race }: { race: RaceDTO }) {
return <div>{race.name}</div>;
}
```
2. **TypeScript will catch errors** if contracts change.
### Detecting Breaking Changes
```bash
# This will show you exactly what changed
npm run test:contract:compatibility
```
Output example:
```
🚨 BREAKING CHANGES DETECTED:
• RaceDTO.status: Property status was removed (BREAKING)
❌ REMOVED:
• OldDTO: DTO OldDTO was removed
ADDED:
• NewDTO: New DTO NewDTO was added
```
## 🔧 Troubleshooting
### "Cannot find module" errors
```bash
# Regenerate types
npm run api:sync-types
```
### Type generation fails
1. Check DTOs have `@ApiProperty` decorators
2. Verify OpenAPI spec is valid: `cat apps/api/openapi.json`
3. Run individual steps:
```bash
npm run api:generate-spec
npm run api:generate-types
```
### CI fails on breaking changes
- Review what changed
- Update website code to handle new types
- Or revert the breaking change if unintended
## 📋 Checklist Before Committing
- [ ] Run `npm run test:contracts` locally
- [ ] All tests pass
- [ ] No breaking changes (or they're intentional)
- [ ] Website code updated to handle new types
- [ ] Generated types are committed (if needed)
## 🎓 Key Concepts
### What is a "Contract"?
A contract is the agreement between API and website about what data looks like:
- DTO definitions
- Property types
- Required vs optional fields
### What are "Breaking Changes"?
Changes that would break the website:
- Removing required fields
- Changing field types
- Adding required fields to existing DTOs
### Why Generate Types?
- **Type Safety**: Catch errors at compile time
- **Auto-completion**: Better IDE experience
- **Documentation**: Types serve as living documentation
- **Consistency**: Single source of truth
## 🚨 Important Notes
1. **Never manually edit generated files** - they're auto-generated
2. **Always run tests before committing** - prevents breaking changes
3. **The CI will regenerate types** - but local verification is faster
4. **Breaking changes need review** - consider versioning strategy
## 📚 More Resources
- Full documentation: `docs/CONTRACT_TESTING.md`
- API examples: `apps/api/src/shared/testing/contractValidation.test.ts`
- Website examples: `apps/website/lib/types/contractConsumption.test.ts`
- CI/CD workflow: `.github/workflows/contract-testing.yml`