fix tests

This commit is contained in:
2026-01-03 22:28:12 +01:00
parent bc7cb2e20a
commit 5308d3ee61
10 changed files with 26 additions and 865 deletions

View File

@@ -1,148 +0,0 @@
# Docker Auth/Session Test Fixes Summary
## Problem
The docker-compose.test.yml setup had 18 failing tests related to authentication session issues. The main problems were:
1. **Service dependency issues**: Website container started before deps container finished installing
2. **Cookie domain problems**: Mock API cookies weren't working properly in Docker environment
3. **Network connectivity**: Website couldn't reach API due to timing and configuration issues
## Root Causes
### 1. Missing Service Dependencies
- Website container didn't wait for deps container to complete
- API container didn't wait for deps container
- This caused "next: not found" and module resolution errors
### 2. Cookie Domain Issues
- Mock API set cookies without domain specification
- In Docker, cookies need proper domain settings to work across containers
- Browser at localhost:3100 couldn't access cookies from API at localhost:3101
### 3. Slow npm Install
- deps container took too long to install packages
- Website container would timeout waiting
- No proper health checks or completion signals
## Fixes Applied
### 1. Updated `docker-compose.test.yml`
**Before:**
```yaml
website:
depends_on:
api:
condition: service_healthy
```
**After:**
```yaml
deps:
command: ["sh", "-lc", "echo '[deps] Ready' && sleep infinity"]
# Simple command that completes immediately
api:
depends_on:
deps:
condition: service_started
# Added deps dependency
website:
depends_on:
deps:
condition: service_started # Wait for deps
api:
condition: service_healthy # Wait for API
command:
- sh
- -lc
- |
# Check if node_modules exist, install if needed
if [ ! -d "node_modules" ] || [ ! -f "node_modules/.bin/next" ]; then
echo "[website] Installing dependencies..."
npm install --no-package-lock --include-workspace-root --no-audit --fund=false --prefer-offline
else
echo "[website] node_modules already present"
fi
echo "[website] Starting Next.js dev server..."
npm run dev
```
### 2. Fixed `testing/mock-api-server.cjs`
**Before:**
```javascript
const cookies = [
`gp_session=${encodeURIComponent(gpSessionValue)}; Path=/; HttpOnly`,
`gridpilot_demo_mode=${encodeURIComponent(mode)}; Path=/`,
];
```
**After:**
```javascript
// Set cookies with proper domain for Docker environment
const domain = 'localhost';
const cookies = [
`gp_session=${encodeURIComponent(gpSessionValue)}; Path=/; HttpOnly; Domain=${domain}`,
`gridpilot_demo_mode=${encodeURIComponent(mode)}; Path=/; Domain=${domain}`,
];
```
### 3. Verified `playwright.website.config.ts`
- Already correctly configured for Docker
- Uses `http://localhost:3100` when `DOCKER_SMOKE=true`
- Proper timeout and retry settings
## Key Configuration Changes
### Environment Variables
- `API_BASE_URL=http://api:3000` (internal Docker network)
- `NEXT_PUBLIC_API_BASE_URL=http://localhost:3101` (external for browser)
- `DOCKER_SMOKE=true` (tells tests to use Docker ports)
### Cookie Settings
- Added `Domain=localhost` to all Set-Cookie headers
- Ensures cookies work across localhost:3100 and localhost:3101
### Service Dependencies
- deps → api → website (proper startup order)
- Health checks ensure services are ready before dependent services start
## Testing the Fixes
### Quick Test
```bash
# Start services
docker-compose -f docker-compose.test.yml up -d
# Wait for startup
sleep 30
# Run tests
DOCKER_SMOKE=true npx playwright test --config=playwright.website.config.ts
```
### Verification Steps
1. Check deps container starts immediately
2. API container waits for deps and becomes healthy
3. Website container waits for both deps and API
4. Cookies are set with proper domain
5. Tests can access both website and API
## Expected Results
- All 93 tests should pass
- No "next: not found" errors
- No connection refused errors
- Auth sessions work properly in Docker
- Cookie-based authentication flows correctly
## Files Modified
1. `docker-compose.test.yml` - Service dependencies and startup logic
2. `testing/mock-api-server.cjs` - Cookie domain settings
3. `test-docker-fix.sh` - Verification script (new)
## Notes
- The fixes address the core infrastructure issues that were causing auth/session failures
- The mock API now properly simulates real authentication flows
- Docker networking is properly configured for cross-container communication

View File

@@ -1,175 +0,0 @@
# Docker Setup Analysis & Verification
## Summary
I have thoroughly analyzed and tested the Docker setup for both development and production environments. Here's what I found:
## ✅ Development Setup - WORKING PERFECTLY
### Status: **OPERATIONAL**
- **API Service**: Running on port 3000 (with debug on 9229)
- **Website Service**: Running on port 3001
- **Database Service**: PostgreSQL 15-alpine on port 5432
- **Hot Reloading**: Enabled via volume mounts
- **Health Checks**: All services healthy
### Commands:
```bash
# Start development
npm run docker:dev:build
# View logs
npm run docker:dev:logs
# Stop services
npm run docker:dev:down
# Clean everything
npm run docker:dev:clean
```
### Architecture:
- **API**: NestJS with TypeScript, hot-reload enabled
- **Website**: Next.js with hot-reload enabled
- **Database**: PostgreSQL with persistent volume
- **Network**: Custom bridge network (gridpilot-network)
## ⚠️ Production Setup - NEEDS ATTENTION
### Status: **CONFIGURATION COMPLETE, BUILD PENDING**
### Issues Found & Fixed:
#### 1. **Missing .env.production.example** ✅ FIXED
- **Issue**: No example file for production environment variables
- **Solution**: Created `.env.production.example` with all required variables
- **Action Required**: Copy to `.env.production` and update with real credentials
#### 2. **SSL Directory Missing** ✅ FIXED
- **Issue**: `nginx/ssl/` directory referenced but didn't exist
- **Solution**: Created empty directory for future SSL certificates
- **Note**: HTTPS server is commented out in nginx config for local testing
#### 3. **Environment Variables** ✅ FIXED
- **Issue**: Production env file had placeholder values that could cause issues
- **Solution**: Updated `.env.production` with safe defaults for local testing
- **Action Required**: Update with real production credentials before deployment
#### 4. **Docker Build Resource Constraints** ⚠️ IDENTIFIED
- **Issue**: Production builds are resource-intensive and may get killed
- **Solution**: Build in stages or increase Docker resource limits
- **Recommendation**: Use `docker-compose -f docker-compose.prod.yml build --no-cache` with adequate resources
### Production Architecture:
- **API**: Multi-stage build, optimized production image
- **Website**: Next.js production build with optimized dependencies
- **Database**: PostgreSQL 15-alpine with performance tuning
- **Redis**: Cache layer with LRU eviction and persistence
- **Nginx**: Reverse proxy with rate limiting, security headers, caching
### Commands:
```bash
# Build production images (may need increased resources)
npm run docker:prod:build
# Start production (detached)
npm run docker:prod
# View logs
npm run docker:prod:logs
# Stop services
npm run docker:prod:down
# Clean everything
npm run docker:prod:clean
```
## 🔧 Files Created/Updated
### New Files:
- `.env.production.example` - Production environment template
- `nginx/ssl/` - Directory for SSL certificates
- `DOCKER_SETUP_ANALYSIS.md` - This analysis document
### Updated Files:
- `.env.production` - Fixed with safe defaults
- `.dockerignore` - Enhanced to include production example
## 🚀 Deployment Checklist
Before deploying to production:
1. **Environment Variables**:
```bash
cp .env.production.example .env.production
# Edit .env.production with real credentials
```
2. **SSL Certificates** (for HTTPS):
- Place certificates in `nginx/ssl/`
- Uncomment HTTPS server block in `nginx/nginx.conf`
- Update domain names in environment variables
3. **Database Credentials**:
- Update `POSTGRES_PASSWORD` with strong password
- Update `DATABASE_URL` with production database
4. **Redis Password**:
- Update `REDIS_PASSWORD` with a strong password
- No `REDIS_URL` is required (the Redis container is configured via `REDIS_PASSWORD` in `docker-compose.prod.yml`)
5. **Vercel KV** (if using):
- Get credentials from Vercel dashboard
- Update `KV_REST_API_URL` and `KV_REST_API_TOKEN`
6. **Domain Configuration**:
- Update `NEXT_PUBLIC_SITE_URL` with your domain
- Update `NEXT_PUBLIC_API_BASE_URL` with your public API base (often `https://your-domain.com/api` when nginx proxies `/api`)
7. **Build & Deploy**:
```bash
# Build with adequate resources
docker-compose -f docker-compose.prod.yml build
# Start services
docker-compose -f docker-compose.prod.yml up -d
# Verify health
docker-compose -f docker-compose.prod.yml ps
```
## 📊 Health Check Endpoints
### API Health:
- **URL**: `http://localhost:3000/health` (dev) or `http://localhost/api/health` (prod)
- **Response**: `{"status":"ok"}`
### Website Health:
- **URL**: `http://localhost:3001` (dev) or `http://localhost` (prod)
- **Response**: Next.js application running
### Nginx Health:
- **URL**: `http://localhost/health`
- **Response**: `healthy`
## 🎯 Key Improvements Made
1. **Documentation**: Created comprehensive environment example
2. **Security**: Added SSL directory structure
3. **Reliability**: Fixed environment variable placeholders
4. **Maintainability**: Enhanced .dockerignore rules
5. **Testing**: Verified both dev and prod configurations
## 📝 Notes
- **Development**: Fully operational and ready for use
- **Production**: Configuration complete, ready for deployment with proper credentials
- **Performance**: Production setup includes resource limits and health checks
- **Security**: Nginx configured with rate limiting and security headers
- **Scalability**: Ready for container orchestration (Kubernetes, etc.)
## 🎉 Conclusion
The Docker setup is **production-ready**! Both development and production configurations are properly set up. The development environment works perfectly, and the production environment is configured correctly - it just needs real credentials and adequate build resources.
**Next Steps**: Follow the deployment checklist above to deploy to production.

View File

@@ -62,16 +62,19 @@ Access:
### Testing (Docker)
The goal of Docker-backed tests is to catch *wiring* issues between Website ↔ API (wrong hostnames/ports/env vars, missing CORS for credentialed requests, etc.) in a deterministic environment.
- `npm run test:docker:website` - Start a dedicated test stack (ports `3100/3101`) and run Playwright smoke tests against it.
- Uses [`docker-compose.test.yml`](docker-compose.test.yml:1).
- Runs the Website in Docker and talks to an **API mock** container.
- Validates that pages render and that core API fetches succeed (hostname + CORS + routing).
- `npm run test:docker:website` - Start API/DB in Docker, run website locally via Playwright, and execute e2e tests.
- Uses [`docker-compose.test.yml`](docker-compose.test.yml:1) for API and PostgreSQL.
- Playwright starts the website locally via `webServer` config (not in Docker).
- Tests run against `http://localhost:3000` (website) talking to `http://localhost:3101` (API).
- Validates that pages render, middleware works, and API connections succeed.
**Important**: The website runs locally (not in Docker) to avoid Next.js SWC/compilation issues in containers.
Supporting scripts:
- `npm run docker:test:deps` - Install monorepo deps inside a reusable Docker volume (one-shot).
- `npm run docker:test:up` - Bring up the test stack.
- `npm run docker:test:wait` - Wait for `http://localhost:3100` and `http://localhost:3101/health` to be ready.
- `npm run docker:test:down` - Tear the test stack down (including volumes).
- `npm run docker:test:deps` - Verify monorepo dependencies are installed.
- `npm run docker:test:up` - Start API and PostgreSQL containers.
- `npm run docker:test:wait` - Wait for API health check at `http://localhost:3101/health`.
- `npm run docker:test:down` - Stop containers and clean up.
## Environment Variables
@@ -117,16 +120,23 @@ The single source of truth for "what base URL should I use?" is [`getWebsiteApiB
#### Test Docker defaults (docker-compose.test.yml)
This stack is intended for deterministic smoke tests and uses different host ports to avoid colliding with `docker:dev`:
- Website: `http://localhost:3100`
- API mock: `http://localhost:3101` (maps to container `api:3000`)
- Website: `http://localhost:3000` (started by Playwright webServer, not Docker)
- API: `http://localhost:3101` (maps to container `api:3000`)
- PostgreSQL: `localhost:5433` (maps to container `5432`)
- `NEXT_PUBLIC_API_BASE_URL=http://localhost:3101` (browser → host port)
- `API_BASE_URL=http://api:3000` (website container → api container)
- `API_BASE_URL=http://localhost:3101` (Playwright webServer → host port)
Important: the test stack's API is a mock server defined inline in [`docker-compose.test.yml`](docker-compose.test.yml:24). It exists to validate Website ↔ API wiring, not domain correctness.
**Important**:
- The website runs locally via Playwright's `webServer` config to avoid Next.js SWC compilation issues in Docker.
- The API is a real TypeORM/PostgreSQL server (not a mock) for testing actual database interactions.
- Playwright automatically starts the website server before running tests.
#### Troubleshooting
- If `docker:dev` is running, use `npm run docker:dev:down` before `npm run test:docker:website` to avoid port conflicts.
- If Docker volumes get stuck, run `npm run docker:test:down` (it uses `--remove-orphans` + `rm -f`).
- **Port conflicts**: If `docker:dev` is running, use `npm run docker:dev:down` before `npm run test:docker:website` to avoid port conflicts (dev uses 3001, test uses 3101).
- **Website not starting**: Playwright's webServer may fail if dependencies are missing. Run `npm install` first.
- **Cookie errors**: The `WebsiteAuthManager` requires both `url` and `path` properties for cookies. Check Playwright version compatibility.
- **Docker volumes stuck**: Run `npm run docker:test:down` (uses `--remove-orphans` + `rm -f`).
- **SWC compilation issues**: If website fails to start in Docker, use the local webServer approach (already configured in `playwright.website.config.ts`).
### API "Real vs In-Memory" Mode

View File

@@ -1,5 +0,0 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 1767404531 gp_session gp_6b1738c6-8a80-407d-b934-b14fb9834ba1

119
split.js
View File

@@ -1,119 +0,0 @@
const fs = require('fs');
const path = require('path');
const filePath = 'apps/api/src/domain/league/dtos/LeagueDto.ts';
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
let allImports = lines.filter(line => line.startsWith('import '));
let classBlocks = [];
let currentClassLines = [];
let inClass = false;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line.startsWith('export class ')) {
if (inClass) {
classBlocks.push(currentClassLines);
currentClassLines = [];
}
inClass = true;
}
if (inClass) {
currentClassLines.push(line);
if (line.trim() === '}') {
classBlocks.push(currentClassLines);
currentClassLines = [];
inClass = false;
}
}
}
if (currentClassLines.length > 0) {
classBlocks.push(currentClassLines);
}
// Function to get imports for class
function getImportsForClass(classLines, className) {
let classContent = classLines.join('\n');
let neededImports = [];
// ApiProperty
if (classContent.includes('@ApiProperty')) {
neededImports.push("import { ApiProperty } from '@nestjs/swagger';");
}
// class-validator
let validators = [];
if (classContent.includes('@IsString')) validators.push('IsString');
if (classContent.includes('@IsNumber')) validators.push('IsNumber');
if (classContent.includes('@IsBoolean')) validators.push('IsBoolean');
if (classContent.includes('@IsDate')) validators.push('IsDate');
if (classContent.includes('@IsOptional')) validators.push('IsOptional');
if (classContent.includes('@IsEnum')) validators.push('IsEnum');
if (classContent.includes('@IsArray')) validators.push('IsArray');
if (classContent.includes('@ValidateNested')) validators.push('ValidateNested');
if (validators.length > 0) {
neededImports.push(`import { ${validators.join(', ')} } from 'class-validator';`);
}
// class-transformer
if (classContent.includes('@Type')) {
neededImports.push("import { Type } from 'class-transformer';");
}
// Other DTOs
if (classContent.includes('DriverDto')) {
neededImports.push("import { DriverDto } from '../../driver/dto/DriverDto';");
}
if (classContent.includes('RaceDto')) {
neededImports.push("import { RaceDto } from '../../race/dto/RaceDto';");
}
// Local DTOs
let localDTOs = ['LeagueSettingsDTO', 'LeagueWithCapacityDTO', 'LeagueSummaryDTO', 'AllLeaguesWithCapacityDTO', 'AllLeaguesWithCapacityAndScoringDTO', 'LeagueStatsDTO', 'ProtestDTO', 'SeasonDTO', 'LeagueJoinRequestDTO', 'GetLeagueJoinRequestsQueryDTO', 'ApproveJoinRequestInputDTO', 'ApproveJoinRequestOutputDTO', 'RejectJoinRequestInputDTO', 'RejectJoinRequestOutputDTO', 'GetLeagueAdminPermissionsInputDTO', 'LeagueAdminPermissionsDTO', 'RemoveLeagueMemberInputDTO', 'RemoveLeagueMemberOutputDTO', 'UpdateLeagueMemberRoleInputDTO', 'UpdateLeagueMemberRoleOutputDTO', 'GetLeagueOwnerSummaryQueryDTO', 'LeagueOwnerSummaryDTO', 'LeagueConfigFormModelBasicsDTO', 'LeagueConfigFormModelStructureDTO', 'LeagueConfigFormModelScoringDTO', 'LeagueConfigFormModelDropPolicyDTO', 'LeagueConfigFormModelStewardingDTO', 'LeagueConfigFormModelTimingsDTO', 'LeagueConfigFormModelDTO', 'GetLeagueAdminConfigQueryDTO', 'GetLeagueAdminConfigOutputDTO', 'LeagueAdminConfigDTO', 'GetLeagueProtestsQueryDTO', 'LeagueAdminProtestsDTO', 'GetLeagueSeasonsQueryDTO', 'LeagueSeasonSummaryDTO', 'LeagueAdminDTO', 'LeagueMemberDTO', 'LeagueMembershipsDTO', 'LeagueStandingDTO', 'LeagueStandingsDTO', 'LeagueScheduleDTO', 'LeagueStatsDTO', 'CreateLeagueInputDTO', 'CreateLeagueOutputDTO'];
for (let dto of localDTOs) {
if (dto !== className && classContent.includes(dto)) {
neededImports.push(`import { ${dto} } from './${dto}';`);
}
}
return neededImports;
}
for (let classLines of classBlocks) {
let classNameLine = classLines.find(line => line.startsWith('export class '));
if (classNameLine) {
let match = classNameLine.match(/export class (\w+)/);
if (match) {
let className = match[1];
// Rename
if (className.endsWith('ViewModel')) {
className = className.replace('ViewModel', 'DTO');
} else if (className.endsWith('Dto')) {
className = className.replace('Dto', 'DTO');
} else if (className.endsWith('Input')) {
className = className + 'DTO';
} else if (className.endsWith('Output')) {
className = className + 'DTO';
} else if (className.endsWith('Query')) {
className = className + 'DTO';
} else {
className = className + 'DTO';
}
let fileName = className + '.ts';
// Update class name in lines
classLines[0] = classLines[0].replace(/export class \w+/, 'export class ' + className);
// Update references in the class
for (let i = 1; i < classLines.length; i++) {
classLines[i] = classLines[i].replace(/ViewModel/g, 'DTO').replace(/Dto/g, 'DTO').replace(/Input\b/g, 'InputDTO').replace(/Output\b/g, 'OutputDTO').replace(/Query\b/g, 'QueryDTO');
classLines[i] = classLines[i].replace(/DriverDTO/g, 'DriverDto').replace(/RaceDTO/g, 'RaceDto');
}
let imports = getImportsForClass(classLines, className);
let fileContent = imports.join('\n') + '\n\n' + classLines.join('\n');
fs.writeFileSync(path.join('apps/api/src/domain/league/dtos', fileName), fileContent);
}
}
}

View File

@@ -1,42 +0,0 @@
#!/bin/bash
# Test API signup endpoint
echo "Testing API signup endpoint..."
echo ""
# Test 1: Check if API is reachable
echo "1. Checking if API is reachable..."
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" http://localhost:3001/health || echo "Health endpoint not found"
echo ""
# Test 2: Try signup with correct data
echo "2. Attempting signup with correct data..."
curl -X POST http://localhost:3001/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "testuser@example.com",
"password": "TestPass123",
"displayName": "Test User"
}' \
-w "\nHTTP Status: %{http_code}\n" \
-s
echo ""
# Test 3: Try signup with extra fields (should fail with whitelist error)
echo "3. Attempting signup with extra fields (should fail)..."
curl -X POST http://localhost:3001/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "testuser2@example.com",
"password": "TestPass123",
"displayName": "Test User 2",
"extraField": "should not exist"
}' \
-w "\nHTTP Status: %{http_code}\n" \
-s
echo ""
echo "Done."

View File

@@ -1,38 +0,0 @@
#!/bin/bash
# Test script to verify docker auth/session fixes
echo "=== Testing Docker Auth/Session Fixes ==="
# Clean up any existing containers
echo "1. Cleaning up existing containers..."
docker-compose -f docker-compose.test.yml down -v 2>/dev/null || true
# Start services
echo "2. Starting services..."
docker-compose -f docker-compose.test.yml up -d
# Wait for services to be ready
echo "3. Waiting for services to be ready..."
sleep 30
# Check service status
echo "4. Checking service status..."
docker-compose -f docker-compose.test.yml ps
# Check website logs for any errors
echo "5. Checking website logs..."
docker-compose -f docker-compose.test.yml logs --tail=10 website
# Check API health
echo "6. Testing API health..."
curl -f http://localhost:3101/health && echo " ✓ API is healthy" || echo " ✗ API health check failed"
# Test website accessibility
echo "7. Testing website accessibility..."
curl -f http://localhost:3100/ && echo " ✓ Website is accessible" || echo " ✗ Website accessibility failed"
echo ""
echo "=== Setup Complete ==="
echo "To run tests: DOCKER_SMOKE=true npx playwright test --config=playwright.website.config.ts"
echo "To stop: docker-compose -f docker-compose.test.yml down"

View File

@@ -1,89 +0,0 @@
# Fix Verification for Team Logo Issue
## Problem Summary
- **Issue**: Teams with stale `logoRef` values (`system-default/logo`) persist across force reseeds
- **Root Cause**: `clearExistingRacingData()` didn't clear `racing_teams` table
- **Impact**: API returns `/media/default/logo.png` instead of `/media/teams/{id}/logo`
## Fix Applied
Updated `adapters/bootstrap/SeedRacingData.ts` `clearExistingRacingData()` method to:
1. **Clear team join requests** - Before deleting teams
2. **Clear team memberships** - Before deleting teams
3. **Clear teams** - The critical fix (teams have stale logoRef)
4. **Clear related racing data** - Results, standings, races, etc.
## Key Changes
```typescript
// Before (incomplete):
private async clearExistingRacingData(): Promise<void> {
// Only cleared drivers and leagues
// Missing: teams, team_memberships, team_join_requests
}
// After (complete):
private async clearExistingRacingData(): Promise<void> {
// Clear stats
await this.seedDeps.driverStatsRepository.clear();
await this.seedDeps.teamStatsRepository.clear();
// Clear race registrations
const races = await this.seedDeps.raceRepository.findAll();
for (const race of races) {
await this.seedDeps.raceRegistrationRepository.clearRaceRegistrations(race.id.toString());
}
// Clear team join requests
const teams = await this.seedDeps.teamRepository.findAll();
for (const team of teams) {
const joinRequests = await this.seedDeps.teamMembershipRepository.getJoinRequests(team.id.toString());
for (const request of joinRequests) {
await this.seedDeps.teamMembershipRepository.removeJoinRequest(request.id);
}
}
// Clear team memberships
for (const team of teams) {
const memberships = await this.seedDeps.teamMembershipRepository.getTeamMembers(team.id.toString());
for (const membership of memberships) {
await this.seedDeps.teamMembershipRepository.removeMembership(team.id.toString(), membership.driverId.toString());
}
}
// Clear teams (CRITICAL FIX)
for (const team of teams) {
await this.seedDeps.teamRepository.delete(team.id.toString());
}
// Clear other racing data...
// Results, standings, races, league memberships, etc.
}
```
## Expected Behavior After Fix
### Before Fix:
1. Start dev with `GRIDPILOT_API_FORCE_RESEED=1`
2. Teams from previous seed remain with `logoRef: {"type":"system-default","variant":"logo"}`
3. `GET /teams/all` returns `"logoUrl": "/media/default/logo.png"`
### After Fix:
1. Start dev with `GRIDPILOT_API_FORCE_RESEED=1`
2. All racing data cleared including teams
3. New teams seeded with `logoRef: MediaReference.generated('team', teamId)`
4. `GET /teams/all` returns `"logoUrl": "/media/teams/{id}/logo"`
## Verification Steps
1. **Start with existing data**: Run API with force reseed enabled
2. **Check database**: Verify `racing_teams` table is cleared
3. **Verify new data**: Teams should have generated logoRef
4. **Test API**: `/teams/all` should return correct logo URLs
## Files Modified
- `adapters/bootstrap/SeedRacingData.ts` - Enhanced `clearExistingRacingData()` method
## Related Code
- `RacingTeamFactory.createTeams()` - Sets `logoRef: MediaReference.generated('team', teamId)`
- `AllTeamsPresenter.present()` - Derives `logoUrl` from `logoRef`
- `MediaController.debugResolve()` - Validates media resolution

View File

@@ -33,11 +33,12 @@ export class WebsiteAuthManager {
const token = await WebsiteAuthManager.loginViaApi(request, apiBaseUrl, role);
// Critical: the website (localhost:3000) must receive `gp_session` so middleware can forward it.
// Playwright cookie format - either url OR domain+path
await context.addCookies([
{
name: 'gp_session',
value: token,
url: baseURL,
domain: 'localhost',
path: '/',
httpOnly: true,
sameSite: 'Lax',

View File

@@ -1,234 +0,0 @@
# Type Inventory & Classification
This document catalogs all types from the monolithic `apps/website/lib/apiClient.ts` file (lines 13-634) and inline DTOs from `apps/website/app/races/[id]/results/page.tsx` (lines 17-56). Types are organized by domain and classified according to Clean Architecture principles.
## Summary
- **Total Types**: 89 types in apiClient.ts + 5 inline DTOs = 94 types
- **Domains**: Common/Shared, League, Driver, Team, Race, Sponsor, Media, Analytics, Auth, Payments
- **Classification Breakdown**:
- DTO: 45 (pure transport objects, no business logic)
- ViewModel: 44 (UI-ready with potential computed properties)
- Input: 3 (request parameters)
- Output: 2 (response wrappers)
## Extraction Strategy Overview
- **DTOs**: Move to `apps/website/lib/dtos/{domain}/` with filename `{TypeName}.ts`
- **ViewModels**: Move to `apps/website/lib/view-models/{domain}/` with filename `{TypeName}.ts`
- **Inputs/Outputs**: Treat as DTOs in appropriate domain
- **Dependencies**: Update imports after extraction
- **No Empty Files**: Ensure each extracted file has content
- **Clean Architecture**: Maintain separation between transport (DTO) and presentation (ViewModel) layers
---
## Common/Shared Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| DriverDTO | 13-19 | DTO | `apps/website/lib/dtos/common/DriverDto.ts` | None | LeagueAdminPresenter.ts, TeamAdminPresenter.ts, presenters (multiple) |
| ProtestViewModel | 21-29 | ViewModel | `apps/website/lib/view-models/common/ProtestViewModel.ts` | None | LeagueAdminProtestsDto.ts |
| LeagueMemberViewModel | 31-36 | ViewModel | `apps/website/lib/view-models/league/LeagueMemberViewModel.ts` | DriverDTO | LeagueMembershipsDto.ts, LeagueAdminDto.ts, leagueMembership.ts |
| StandingEntryViewModel | 38-46 | ViewModel | `apps/website/lib/view-models/league/StandingEntryViewModel.ts` | DriverDTO | LeagueStandingsDto.ts, LeagueStandingsPresenter.ts |
| ScheduledRaceViewModel | 48-54 | ViewModel | `apps/website/lib/view-models/race/ScheduledRaceViewModel.ts` | None | LeagueScheduleDto.ts |
---
## League Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| LeagueSummaryViewModel | 57-70 | ViewModel | `apps/website/lib/view-models/league/LeagueSummaryViewModel.ts` | None | AllLeaguesWithCapacityAndScoringPresenter.ts |
| AllLeaguesWithCapacityViewModel | 72-74 | ViewModel | `apps/website/lib/view-models/league/AllLeaguesWithCapacityViewModel.ts` | LeagueSummaryViewModel | ScheduleRaceFormPresenter.ts, AllLeaguesWithCapacityAndScoringPresenter.ts |
| LeagueStatsDto | 76-79 | DTO | `apps/website/lib/dtos/league/LeagueStatsDto.ts` | None | None found |
| LeagueJoinRequestViewModel | 80-86 | ViewModel | `apps/website/lib/view-models/league/LeagueJoinRequestViewModel.ts` | None | LeagueAdminDto.ts, LeagueAdminPresenter.ts |
| LeagueAdminPermissionsViewModel | 88-95 | ViewModel | `apps/website/lib/view-models/league/LeagueAdminPermissionsViewModel.ts` | None | LeagueAdminPermissionsDto.ts (existing), LeagueAdminPresenter.ts |
| LeagueOwnerSummaryViewModel | 97-102 | ViewModel | `apps/website/lib/view-models/league/LeagueOwnerSummaryViewModel.ts` | None | LeagueAdminPresenter.ts |
| LeagueConfigFormModelDto | 104-111 | DTO | `apps/website/lib/dtos/league/LeagueConfigFormModelDto.ts` | None | LeagueAdminDto.ts |
| LeagueAdminProtestsViewModel | 113-115 | ViewModel | `apps/website/lib/view-models/league/LeagueAdminProtestsViewModel.ts` | ProtestViewModel | LeagueAdminProtestsDto.ts, LeagueAdminPresenter.ts |
| LeagueSeasonSummaryViewModel | 117-123 | ViewModel | `apps/website/lib/view-models/league/LeagueSeasonSummaryViewModel.ts` | None | LeagueAdminPresenter.ts |
| LeagueMembershipsViewModel | 125-127 | ViewModel | `apps/website/lib/view-models/league/LeagueMembershipsViewModel.ts` | LeagueMemberViewModel | LeagueMembershipsDto.ts, leagueMembership.ts |
| LeagueStandingsViewModel | 129-131 | ViewModel | `apps/website/lib/view-models/league/LeagueStandingsViewModel.ts` | StandingEntryViewModel | LeagueStandingsPresenter.ts, TeamStandingsPresenter.ts |
| LeagueScheduleViewModel | 133-135 | ViewModel | `apps/website/lib/view-models/league/LeagueScheduleViewModel.ts` | ScheduledRaceViewModel | LeagueScheduleDto.ts |
| LeagueStatsViewModel | 137-145 | ViewModel | `apps/website/lib/view-models/league/LeagueStatsViewModel.ts` | None | None found |
| LeagueAdminViewModel | 147-151 | ViewModel | `apps/website/lib/view-models/league/LeagueAdminViewModel.ts` | LeagueConfigFormModelDto, LeagueMemberViewModel, LeagueJoinRequestViewModel | None found |
| CreateLeagueInput | 153-159 | Input | `apps/website/lib/dtos/league/CreateLeagueInput.ts` | None | leagueWizardService.ts |
| CreateLeagueOutput | 161-164 | Output | `apps/website/lib/dtos/league/CreateLeagueOutput.ts` | None | None found |
---
## Driver Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| DriverLeaderboardItemViewModel | 167-175 | ViewModel | `apps/website/lib/view-models/driver/DriverLeaderboardItemViewModel.ts` | None | DriversLeaderboardPresenter.ts |
| DriversLeaderboardViewModel | 177-179 | ViewModel | `apps/website/lib/view-models/driver/DriversLeaderboardViewModel.ts` | DriverLeaderboardItemViewModel | DriversLeaderboardPresenter.ts |
| DriverStatsDto | 181-183 | DTO | `apps/website/lib/dtos/driver/DriverStatsDto.ts` | None | None found |
| CompleteOnboardingInput | 185-188 | Input | `apps/website/lib/dtos/driver/CompleteOnboardingInput.ts` | None | None found |
| CompleteOnboardingOutput | 190-193 | Output | `apps/website/lib/dtos/driver/CompleteOnboardingOutput.ts` | None | None found |
| DriverRegistrationStatusViewModel | 195-199 | ViewModel | `apps/website/lib/view-models/driver/DriverRegistrationStatusViewModel.ts` | None | None found |
---
## Team Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| TeamSummaryViewModel | 202-208 | ViewModel | `apps/website/lib/view-models/team/TeamSummaryViewModel.ts` | None | AllTeamsPresenter.ts |
| AllTeamsViewModel | 210-212 | ViewModel | `apps/website/lib/view-models/team/AllTeamsViewModel.ts` | TeamSummaryViewModel | AllTeamsPresenter.ts |
| TeamMemberViewModel | 214-219 | ViewModel | `apps/website/lib/view-models/team/TeamMemberViewModel.ts` | DriverDTO | TeamDetailsPresenter.ts, TeamRosterPresenter.ts |
| TeamJoinRequestItemViewModel | 221-227 | ViewModel | `apps/website/lib/view-models/team/TeamJoinRequestItemViewModel.ts` | None | TeamAdminPresenter.ts |
| TeamDetailsViewModel | 229-237 | ViewModel | `apps/website/lib/view-models/team/TeamDetailsViewModel.ts` | TeamMemberViewModel | TeamDetailsPresenter.ts |
| TeamMembersViewModel | 239-241 | ViewModel | `apps/website/lib/view-models/team/TeamMembersViewModel.ts` | TeamMemberViewModel | TeamRosterPresenter.ts |
| TeamJoinRequestsViewModel | 243-245 | ViewModel | `apps/website/lib/view-models/team/TeamJoinRequestsViewModel.ts` | TeamJoinRequestItemViewModel | None found |
| DriverTeamViewModel | 247-252 | ViewModel | `apps/website/lib/view-models/team/DriverTeamViewModel.ts` | None | DriverTeamPresenter.ts |
| CreateTeamInput | 254-258 | Input | `apps/website/lib/dtos/team/CreateTeamInput.ts` | None | None found |
| CreateTeamOutput | 260-263 | Output | `apps/website/lib/dtos/team/CreateTeamOutput.ts` | None | None found |
| UpdateTeamInput | 265-269 | Input | `apps/website/lib/dtos/team/UpdateTeamInput.ts` | None | TeamAdminPresenter.ts |
| UpdateTeamOutput | 271-273 | Output | `apps/website/lib/dtos/team/UpdateTeamOutput.ts` | None | None found |
---
## Race Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| RaceListItemViewModel | 276-284 | ViewModel | `apps/website/lib/view-models/race/RaceListItemViewModel.ts` | None | None found |
| AllRacesPageViewModel | 286-288 | ViewModel | `apps/website/lib/view-models/race/AllRacesPageViewModel.ts` | RaceListItemViewModel | None found |
| RaceStatsDto | 290-292 | DTO | `apps/website/lib/dtos/race/RaceStatsDto.ts` | None | None found |
| RaceDetailEntryViewModel | 295-302 | ViewModel | `apps/website/lib/view-models/race/RaceDetailEntryViewModel.ts` | None | None found |
| RaceDetailUserResultViewModel | 304-313 | ViewModel | `apps/website/lib/view-models/race/RaceDetailUserResultViewModel.ts` | None | None found |
| RaceDetailRaceViewModel | 315-326 | ViewModel | `apps/website/lib/view-models/race/RaceDetailRaceViewModel.ts` | None | None found |
| RaceDetailLeagueViewModel | 328-336 | ViewModel | `apps/website/lib/view-models/race/RaceDetailLeagueViewModel.ts` | None | None found |
| RaceDetailRegistrationViewModel | 338-341 | ViewModel | `apps/website/lib/view-models/race/RaceDetailRegistrationViewModel.ts` | None | None found |
| RaceDetailViewModel | 343-350 | ViewModel | `apps/website/lib/view-models/race/RaceDetailViewModel.ts` | RaceDetailRaceViewModel, RaceDetailLeagueViewModel, RaceDetailEntryViewModel, RaceDetailRegistrationViewModel, RaceDetailUserResultViewModel | None found |
| RacesPageDataRaceViewModel | 352-364 | ViewModel | `apps/website/lib/view-models/race/RacesPageDataRaceViewModel.ts` | None | races/page.tsx, races/all/page.tsx |
| RacesPageDataViewModel | 366-368 | ViewModel | `apps/website/lib/view-models/race/RacesPageDataViewModel.ts` | RacesPageDataRaceViewModel | races/page.tsx, races/all/page.tsx |
| RaceResultViewModel | 370-381 | ViewModel | `apps/website/lib/view-models/race/RaceResultViewModel.ts` | None | None found |
| RaceResultsDetailViewModel | 383-387 | ViewModel | `apps/website/lib/view-models/race/RaceResultsDetailViewModel.ts` | RaceResultViewModel | races/[id]/results/page.tsx |
| RaceWithSOFViewModel | 389-393 | ViewModel | `apps/website/lib/view-models/race/RaceWithSOFViewModel.ts` | None | races/[id]/results/page.tsx |
| RaceProtestViewModel | 395-405 | ViewModel | `apps/website/lib/view-models/race/RaceProtestViewModel.ts` | None | None found |
| RaceProtestsViewModel | 407-410 | ViewModel | `apps/website/lib/view-models/race/RaceProtestsViewModel.ts` | RaceProtestViewModel | races/[id]/stewarding/page.tsx |
| RacePenaltyViewModel | 412-421 | ViewModel | `apps/website/lib/view-models/race/RacePenaltyViewModel.ts` | None | None found |
| RacePenaltiesViewModel | 423-426 | ViewModel | `apps/website/lib/view-models/race/RacePenaltiesViewModel.ts` | RacePenaltyViewModel | races/[id]/stewarding/page.tsx |
| RegisterForRaceParams | 428-431 | Input | `apps/website/lib/dtos/race/RegisterForRaceParams.ts` | None | None found |
| WithdrawFromRaceParams | 433-435 | Input | `apps/website/lib/dtos/race/WithdrawFromRaceParams.ts` | None | None found |
| ImportRaceResultsInput | 437-439 | Input | `apps/website/lib/dtos/race/ImportRaceResultsInput.ts` | None | None found |
| ImportRaceResultsSummaryViewModel | 441-447 | ViewModel | `apps/website/lib/view-models/race/ImportRaceResultsSummaryViewModel.ts` | None | None found |
---
## Sponsor Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| GetEntitySponsorshipPricingResultDto | 450-454 | DTO | `apps/website/lib/dtos/sponsor/GetEntitySponsorshipPricingResultDto.ts` | None | None found |
| SponsorViewModel | 456-462 | ViewModel | `apps/website/lib/view-models/sponsor/SponsorViewModel.ts` | None | None found |
| GetSponsorsOutput | 463-465 | Output | `apps/website/lib/dtos/sponsor/GetSponsorsOutput.ts` | SponsorViewModel | None found |
| CreateSponsorInput | 467-472 | Input | `apps/website/lib/dtos/sponsor/CreateSponsorInput.ts` | None | None found |
| CreateSponsorOutput | 474-477 | Output | `apps/website/lib/dtos/sponsor/CreateSponsorOutput.ts` | None | None found |
| SponsorDashboardDTO | 479-485 | DTO | `apps/website/lib/dtos/sponsor/SponsorDashboardDto.ts` | None | None found |
| SponsorshipDetailViewModel | 487-497 | ViewModel | `apps/website/lib/view-models/sponsor/SponsorshipDetailViewModel.ts` | None | None found |
| SponsorSponsorshipsDTO | 499-502 | DTO | `apps/website/lib/dtos/sponsor/SponsorSponsorshipsDto.ts` | SponsorshipDetailViewModel | None found |
---
## Media Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| RequestAvatarGenerationInput | 505-508 | Input | `apps/website/lib/dtos/media/RequestAvatarGenerationInput.ts` | None | None found |
| RequestAvatarGenerationOutput | 510-514 | Output | `apps/website/lib/dtos/media/RequestAvatarGenerationOutput.ts` | None | None found |
---
## Analytics Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| RecordPageViewInput | 517-521 | Input | `apps/website/lib/dtos/analytics/RecordPageViewInput.ts` | None | None found |
| RecordPageViewOutput | 523-525 | Output | `apps/website/lib/dtos/analytics/RecordPageViewOutput.ts` | None | None found |
| RecordEngagementInput | 527-532 | Input | `apps/website/lib/dtos/analytics/RecordEngagementInput.ts` | None | None found |
| RecordEngagementOutput | 534-536 | Output | `apps/website/lib/dtos/analytics/RecordEngagementOutput.ts` | None | None found |
---
## Auth Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| LoginParams | 539-542 | Input | `apps/website/lib/dtos/auth/LoginParams.ts` | None | None found |
| SignupParams | 544-548 | Input | `apps/website/lib/dtos/auth/SignupParams.ts` | None | None found |
| SessionData | 550-556 | DTO | `apps/website/lib/dtos/auth/SessionData.ts` | None | None found |
---
## Payments Domain Types
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| PaymentViewModel | 559-565 | ViewModel | `apps/website/lib/view-models/payments/PaymentViewModel.ts` | None | None found |
| GetPaymentsOutput | 567-569 | Output | `apps/website/lib/dtos/payments/GetPaymentsOutput.ts` | PaymentViewModel | None found |
| CreatePaymentInput | 571-577 | Input | `apps/website/lib/dtos/payments/CreatePaymentInput.ts` | None | None found |
| CreatePaymentOutput | 579-582 | Output | `apps/website/lib/dtos/payments/CreatePaymentOutput.ts` | None | None found |
| MembershipFeeViewModel | 584-589 | ViewModel | `apps/website/lib/view-models/payments/MembershipFeeViewModel.ts` | None | None found |
| MemberPaymentViewModel | 591-596 | ViewModel | `apps/website/lib/view-models/payments/MemberPaymentViewModel.ts` | None | None found |
| GetMembershipFeesOutput | 598-601 | Output | `apps/website/lib/dtos/payments/GetMembershipFeesOutput.ts` | MembershipFeeViewModel, MemberPaymentViewModel | None found |
| PrizeViewModel | 603-609 | ViewModel | `apps/website/lib/view-models/payments/PrizeViewModel.ts` | None | None found |
| GetPrizesOutput | 611-613 | Output | `apps/website/lib/dtos/payments/GetPrizesOutput.ts` | PrizeViewModel | None found |
| WalletTransactionViewModel | 615-621 | ViewModel | `apps/website/lib/view-models/payments/WalletTransactionViewModel.ts` | None | None found |
| WalletViewModel | 623-628 | ViewModel | `apps/website/lib/view-models/payments/WalletViewModel.ts` | WalletTransactionViewModel | None found |
| GetWalletOutput | 630-632 | Output | `apps/website/lib/dtos/payments/GetWalletOutput.ts` | WalletViewModel | None found |
---
## Inline DTOs from Results Page
| Type Name | Line Range | Classification | Target Location | Dependencies | Used By |
|-----------|------------|----------------|-----------------|--------------|---------|
| PenaltyTypeDTO | 17-24 | DTO | `apps/website/lib/dtos/race/PenaltyTypeDto.ts` | None | PenaltyTypeDto.ts (existing file) |
| PenaltyData | 26-30 | DTO | `apps/website/lib/dtos/race/PenaltyData.ts` | PenaltyTypeDTO | None found |
| RaceResultRowDTO | 32-41 | DTO | `apps/website/lib/dtos/race/RaceResultRowDto.ts` | None | None found |
| DriverRowDTO | 43-46 | DTO | `apps/website/lib/dtos/race/DriverRowDto.ts` | None | None found |
| ImportResultRowDTO | 48-56 | DTO | `apps/website/lib/dtos/race/ImportResultRowDto.ts` | None | None found |
---
## Extraction Recommendations
1. **Create Directory Structure**:
- `apps/website/lib/dtos/common/`
- `apps/website/lib/dtos/league/`
- `apps/website/lib/dtos/driver/`
- `apps/website/lib/dtos/team/`
- `apps/website/lib/dtos/race/`
- `apps/website/lib/dtos/sponsor/`
- `apps/website/lib/dtos/media/`
- `apps/website/lib/dtos/analytics/`
- `apps/website/lib/dtos/auth/`
- `apps/website/lib/dtos/payments/`
- `apps/website/lib/view-models/common/`
- `apps/website/lib/view-models/league/`
- `apps/website/lib/view-models/driver/`
- `apps/website/lib/view-models/team/`
- `apps/website/lib/view-models/race/`
- `apps/website/lib/view-models/sponsor/`
- `apps/website/lib/view-models/payments/`
2. **Move Types Sequentially**:
- Start with types that have no dependencies
- Update imports in apiClient.ts and dependent files
- Test after each major domain extraction
3. **Update Existing DTOs**:
- Some DTOs already exist (e.g., LeagueAdminPermissionsDto.ts)
- Ensure consistency in naming and structure
4. **Clean Architecture Compliance**:
- DTOs: Pure data transfer, no methods
- ViewModels: May include computed properties for UI
- Clear separation maintained
5. **Post-Extraction**:
- Remove types from apiClient.ts
- Update all import statements
- Run tests to ensure no breaking changes