spawner/TEST_VERIFICATION.md
XPS\Micro 79cf304ccf feat: implement multi-container MVP with dev and prod templates
Add full support for 2 container types (development and production):

Backend Changes:
- New UserContainer model with unique constraint on (user_id, container_type)
- Removed single-container fields from User model (container_id, container_port)
- Added CONTAINER_TEMPLATES config with dev and prod templates
- Implemented spawn_multi_container() method in ContainerManager
- Added 2 new API endpoints:
  * GET /api/user/containers - list all containers with status
  * POST /api/container/launch/<type> - on-demand container creation
- Multi-container container names and Traefik routing with type suffix

Frontend Changes:
- New Container, ContainersResponse, LaunchResponse types
- Implemented getUserContainers() and launchContainer() API functions
- Completely redesigned dashboard with 2 container cards
- Status display with icons for each container type
- "Create & Open" and "Open Service" buttons based on container status
- Responsive grid layout

Templates:
- user-template-next already configured with Tailwind CSS and Shadcn/UI

Documentation:
- Added IMPLEMENTATION_SUMMARY.md with complete feature list
- Added TEST_VERIFICATION.md with detailed testing guide
- Updated .env.example with USER_TEMPLATE_IMAGE_DEV/PROD variables

This MVP allows each user to manage 2 distinct containers with:
- On-demand lazy creation
- Status tracking per container
- Unique URLs: /{slug}-dev and /{slug}-prod
- Proper Traefik routing with StripPrefix middleware

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-31 20:33:07 +01:00

448 lines
11 KiB
Markdown

# Multi-Container MVP - Test & Verification Guide
## Backend Syntax Verification
### Python Compilation Check
```bash
python -m py_compile models.py container_manager.py api.py config.py
# ✅ Alle Dateien erfolgreich kompiliert
```
### Imports Verification
```python
# models.py - UserContainer ist importierbar
from models import UserContainer, User, db
# api.py - UserContainer Import
from models import UserContainer
# container_manager.py - Neue Methode
from container_manager import ContainerManager
mgr = ContainerManager()
mgr.spawn_multi_container(user_id, slug, container_type)
```
---
## Code Structure Verification
### UserContainer Model (models.py)
```python
class UserContainer(db.Model):
__tablename__ = 'user_container'
# ✅ Alle erforderlichen Felder:
- id (Primary Key)
- user_id (Foreign Key)
- container_type ('dev' | 'prod')
- container_id (Docker ID)
- container_port (Port)
- template_image (Image Name)
- created_at (Timestamp)
- last_used (Timestamp)
# ✅ Unique Constraint
__table_args__ = (
db.UniqueConstraint('user_id', 'container_type'),
)
```
### User Model (models.py)
```python
class User(db.Model):
# ✅ Entfernt:
- container_id
- container_port
# ✅ Hinzugefügt:
- containers = db.relationship('UserContainer')
```
### Config Templates (config.py)
```python
CONTAINER_TEMPLATES = {
'dev': {
'image': 'user-service-template:latest', # FROM ENV
'display_name': 'Development Container',
'description': 'Nginx-basierter Development Container'
},
'prod': {
'image': 'user-template-next:latest', # FROM ENV
'display_name': 'Production Container',
'description': 'Next.js Production Build'
}
}
```
---
## API Endpoint Verification
### GET /api/user/containers
**Request:**
```bash
curl -H "Authorization: Bearer <JWT>" \
http://localhost:5000/api/user/containers
```
**Expected Response:**
```json
{
"containers": [
{
"type": "dev",
"display_name": "Development Container",
"description": "Nginx-basierter Development Container",
"status": "running|stopped|not_created|error",
"service_url": "https://coder.domain.com/slug-dev",
"container_id": "abc123def456...",
"created_at": "2025-01-31T10:00:00",
"last_used": "2025-01-31T11:30:00"
},
{
"type": "prod",
"display_name": "Production Container",
"description": "Next.js Production Build",
"status": "not_created",
"service_url": "https://coder.domain.com/slug-prod",
"container_id": null,
"created_at": null,
"last_used": null
}
]
}
```
### POST /api/container/launch/<container_type>
**Request:**
```bash
curl -X POST -H "Authorization: Bearer <JWT>" \
http://localhost:5000/api/container/launch/dev
```
**Expected Response (First Call):**
```json
{
"message": "Container bereit",
"service_url": "https://coder.domain.com/slug-dev",
"container_id": "abc123def456...",
"status": "running"
}
```
**Expected Response (Subsequent Calls):**
- Wenn Container läuft: Gibt selbe Response zurück, aktualisiert last_used
- Wenn Container gestoppt: Startet Container neu mit `start_container()`
- Wenn Container gelöscht: Erstellt neuen Container mit `spawn_multi_container()`
---
## Frontend TypeScript Verification
### api.ts Types
```typescript
// ✅ Container Type
export interface Container {
type: string;
display_name: string;
description: string;
status: 'not_created' | 'running' | 'stopped' | 'error';
service_url: string;
container_id: string | null;
created_at: string | null;
last_used: string | null;
}
// ✅ ContainersResponse Type
export interface ContainersResponse {
containers: Container[];
}
// ✅ LaunchResponse Type
export interface LaunchResponse {
message: string;
service_url: string;
container_id: string;
status: string;
}
// ✅ API Functions
api.getUserContainers(): Promise<ApiResponse<ContainersResponse>>
api.launchContainer(containerType: string): Promise<ApiResponse<LaunchResponse>>
```
### Dashboard Component
```typescript
// ✅ State Management
const [containers, setContainers] = useState<Container[]>([]);
const [loading, setLoading] = useState(true);
const [launching, setLaunching] = useState<string | null>(null);
const [error, setError] = useState("");
// ✅ Load Containers
const loadContainers = async () => {
const { data, error } = await api.getUserContainers();
setContainers(data.containers);
}
// ✅ Launch Container
const handleLaunchContainer = async (containerType: string) => {
const { data } = await api.launchContainer(containerType);
window.open(data.service_url, "_blank");
await loadContainers();
}
// ✅ Rendering
- 2 Container-Cards (dev + prod)
- Status-Icons (running, stopped, error, not_created)
- "Erstellen & Öffnen" oder "Service öffnen" Button
- Loading States während Launch
- Error-Handling
```
---
## Docker Container Verification
### spawn_multi_container() Method
```python
def spawn_multi_container(self, user_id: int, slug: str, container_type: str) -> tuple:
"""
✅ Prüft:
- Template-Typ ist gültig
- Image existiert
- Container-Name ist eindeutig (user-{slug}-{type}-{id})
✅ Setzt:
- Traefik-Labels mit Typ-Suffix
- Environment Variablen (USER_ID, USER_SLUG, CONTAINER_TYPE)
- Memory/CPU Limits
- Restart Policy
✅ Gibt zurück:
- (container_id, port_8080)
"""
```
### Traefik Labels
```python
# ✅ Router mit Typ-Suffix
f'traefik.http.routers.user{user_id}-{container_type}.rule':
f'Host(`{base_host}`) && PathPrefix(`/{slug_with_suffix}`)'
# ✅ StripPrefix Middleware
f'traefik.http.middlewares.user{user_id}-{container_type}-strip.stripprefix.prefixes':
f'/{slug_with_suffix}'
# ✅ Service Routing
f'traefik.http.services.user{user_id}-{container_type}.loadbalancer.server.port':
'8080'
# ✅ TLS/HTTPS
f'traefik.http.routers.user{user_id}-{container_type}.tls': 'true'
f'traefik.http.routers.user{user_id}-{container_type}.tls.certresolver':
Config.TRAEFIK_CERTRESOLVER
```
### URL Routing Test
```
User Request: https://coder.domain.com/slug-dev/path
Traefik: Rule match (Host + PathPrefix)
Middleware: StripPrefix entfernt /slug-dev
Container: Erhält http://localhost:8080/path
```
---
## End-to-End Test Workflow
### Schritt 1: Setup
```bash
# Clean Slate
rm spawner.db
docker ps -a | grep user- | awk '{print $1}' | xargs docker rm -f
# Build Templates
docker build -t user-service-template:latest user-template/
docker build -t user-template-next:latest user-template-next/
# Start Services
docker-compose up -d --build
# Überprüfe Logs
docker-compose logs -f spawner
```
### Schritt 2: Registrierung
```
1. Öffne https://coder.domain.com
2. Klick "Registrieren"
3. Gib Email ein: test@example.com
4. Klick "Magic Link senden"
5. Überprüfe Email
6. Klick Magic Link in Email
7. User wird registriert und zu Dashboard weitergeleitet
8. Überprüfe: 2 Container-Cards sollten sichtbar sein (beide "Noch nicht erstellt")
```
### Schritt 3: Dev-Container
```
1. Auf Dashboard: Dev-Container Card "Erstellen & Öffnen" Button
2. Klick Button
3. Warte auf Loading State
4. Neuer Tab öffnet sich mit: https://coder.domain.com/test-dev
5. Seite zeigt Nginx-Willkommensseite
6. Zurück zum Dashboard
7. Überprüfe: Dev-Container Status = "Läuft"
8. Button ändert sich zu "Service öffnen"
```
### Schritt 4: Prod-Container
```
1. Auf Dashboard: Prod-Container Card "Erstellen & Öffnen" Button
2. Klick Button
3. Warte auf Loading State
4. Neuer Tab öffnet sich mit: https://coder.domain.com/test-prod
5. Seite zeigt Next.js Demo mit Shadcn/UI
6. Zurück zum Dashboard
7. Überprüfe: Prod-Container Status = "Läuft"
8. Button ändert sich zu "Service öffnen"
```
### Schritt 5: Container-Verwaltung
```
1. Klick "Service öffnen" für Dev-Container
→ Sollte bestehenden Tab neu laden
2. Refresh Dashboard
→ Beide Container sollten Status "Läuft" haben
3. Mit Dev-Container: http://{service_url}/
→ Sollte Seite ohne /test-dev/ anzeigen
4. Mit Prod-Container: http://{service_url}/
→ Sollte Seite ohne /test-prod/ anzeigen
```
---
## Verification Checklist
### Database
- [ ] UserContainer Tabelle existiert
- [ ] user_container.user_id Foreign Key funktioniert
- [ ] user_container.container_type ist VARCHAR(50)
- [ ] Unique Constraint (user_id, container_type) existiert
- [ ] User.containers Relationship lädt Container
### API
- [ ] GET /api/user/containers funktioniert
- [ ] POST /api/container/launch/dev funktioniert
- [ ] POST /api/container/launch/prod funktioniert
- [ ] Invalid container_type gibt 400 zurück
- [ ] Missing JWT gibt 401 zurück
### Docker
- [ ] spawn_multi_container() erstellt Container
- [ ] Container-Namen haben Typ-Suffix (-dev, -prod)
- [ ] Traefik-Labels haben richtige Routen
- [ ] StripPrefix funktioniert korrekt
- [ ] Beide Images sind vorhanden
### Frontend
- [ ] Dashboard zeigt 2 Container-Cards
- [ ] API Calls funktionieren ohne Errors
- [ ] "Erstellen & Öffnen" Button funktioniert
- [ ] Service-URLs öffnen sich in neuem Tab
- [ ] Status aktualisiert sich nach Launch
- [ ] Loading States sind sichtbar
### Integration
- [ ] User kann Dev-Container erstellen und öffnen
- [ ] User kann Prod-Container erstellen und öffnen
- [ ] Beide Container funktionieren gleichzeitig
- [ ] URL-Routing funktioniert für beide Container-Typen
- [ ] StripPrefix funktioniert korrekt für beide
---
## Debugging Commands
### Backend Debugging
```bash
# Logs
docker-compose logs -f spawner
# Container-Liste prüfen
docker ps | grep user-
# Inspect Container-Labels
docker inspect user-testuser-dev-1 | grep -A20 traefik
# Python Shell
docker exec -it spawner python
from models import UserContainer
UserContainer.query.all()
```
### Traefik Debugging
```bash
# Traefik Dashboard
curl http://localhost:8080/api/http/routers
# Spezifische Router
curl http://localhost:8080/api/http/routers | grep user
# Logs
docker-compose logs -f traefik | grep user
```
### Frontend Debugging
```bash
# Browser Console
window.localStorage.getItem('token')
# Network Tab
- GET /api/user/containers
- POST /api/container/launch/dev
# Redux DevTools (falls installiert)
Store überprüfen
```
---
## Known Issues & Solutions
### Issue: Container spawnt nicht
**Symptom**: POST /api/container/launch/dev gibt 500 zurück
**Debug**:
```bash
docker-compose logs spawner | tail -50
# Prüfe: Image existiert? Docker API funktioniert?
```
### Issue: Traefik routet nicht
**Symptom**: URL https://coder.domain.com/slug-dev gibt 404
**Debug**:
```bash
docker logs traefik | grep user
docker inspect user-testuser-dev-1 | grep traefik
```
### Issue: StripPrefix funktioniert nicht
**Symptom**: Container erhält /slug-dev in Request-Path
**Debug**:
```bash
# Container-Logs
docker logs user-testuser-dev-1
# Prüfe Traefik Middleware
curl http://localhost:8080/api/http/middlewares
```
---
**Status**: ✅ Alle Tests ready für Durchführung
**Hinweis**: Aktuelle Umgebung hat kein Docker - Tests müssen auf Target-System durchgeführt werden