# S01: Docker Compose + Database + Whisper Script — UAT

**Milestone:** M001
**Written:** 2026-03-29T22:02:45.503Z

## UAT: S01 — Docker Compose + Database + Whisper Script

### Preconditions
- Docker and Docker Compose v2 installed
- Python 3.10+ available
- Project cloned to local filesystem
- No .env file required (defaults used)

---

### TC-01: Docker Compose Configuration Validates
**Steps:**
1. Run `docker compose config > /dev/null 2>&1`
2. Check exit code

**Expected:** Exit code 0. All 5 services (chrysopedia-db, chrysopedia-redis, chrysopedia-api, chrysopedia-worker, chrysopedia-web) present in output.

---

### TC-02: Docker Compose Validates Without .env File
**Steps:**
1. Ensure no .env file exists in project root
2. Run `docker compose config > /dev/null 2>&1`

**Expected:** Exit code 0. env_file `required: false` allows validation without .env present. POSTGRES_PASSWORD falls back to default.

---

### TC-03: All 7 SQLAlchemy Models Load
**Steps:**
1. Run `python3 -c "import sys; sys.path.insert(0,'backend'); from models import Creator, SourceVideo, TranscriptSegment, KeyMoment, TechniquePage, RelatedTechniqueLink, Tag; print('OK')"`

**Expected:** Prints "OK" with exit code 0. All 7 entity models importable without errors.

---

### TC-04: Alembic Migration Files Present
**Steps:**
1. Verify `alembic.ini` exists
2. Verify `alembic/env.py` exists
3. Verify `alembic/versions/001_initial.py` exists

**Expected:** All three files present. Migration creates 7 tables matching the data model spec.

---

### TC-05: Pydantic Schemas Load for All Entities
**Steps:**
1. Run `python3 -c "import sys; sys.path.insert(0,'backend'); from schemas import CreatorRead, SourceVideoRead, TranscriptSegmentRead, KeyMomentRead, TechniquePageRead, TagRead, HealthResponse; print('OK')"`

**Expected:** Prints "OK" with exit code 0. All schemas importable.

---

### TC-06: FastAPI Routers Load with Correct Routes
**Steps:**
1. Run `python3 -c "import sys; sys.path.insert(0,'backend'); from routers.health import router as h; from routers.creators import router as c; from routers.videos import router as v; print(f'{len(h.routes)} {len(c.routes)} {len(v.routes)}')"`

**Expected:** Prints "1 2 1" — health has 1 route, creators has 2 (list + get-by-slug), videos has 1 (list).

---

### TC-07: Whisper Script Shows Help
**Steps:**
1. Run `python3 whisper/transcribe.py --help`

**Expected:** Exit code 0. Output shows usage with --input, --output-dir, --model, --device, --creator, -v flags. Includes examples section.

---

### TC-08: Whisper Script Validates ffmpeg Availability
**Steps:**
1. Run `python3 whisper/transcribe.py --input /tmp/nonexistent.mp4 --output-dir /tmp/out`

**Expected:** Exit code 1 with error message about ffmpeg not being found or file not found. Script does not crash with unhandled exception.

---

### TC-09: Sample Transcript JSON Is Valid
**Steps:**
1. Run `python3 -c "import json; d=json.load(open('tests/fixtures/sample_transcript.json')); assert 'source_file' in d; assert 'creator_folder' in d; assert 'duration_seconds' in d; assert len(d['segments'])==5; assert all('words' in s for s in d['segments']); print('PASS')"`

**Expected:** Prints "PASS". JSON has required top-level keys and 5 segments each containing words array with word-level timestamps.

---

### TC-10: Canonical Tags Configuration Valid
**Steps:**
1. Run `python3 -c "import yaml; d=yaml.safe_load(open('config/canonical_tags.yaml')); cats=d.get('categories',d.get('topic_categories',[])); assert len(cats)==6; print('PASS')"`

**Expected:** Prints "PASS". 6 top-level topic categories loaded from YAML.

---

### TC-11: README Covers Required Sections
**Steps:**
1. Verify README.md contains: project overview, architecture diagram, Docker Compose setup instructions, Whisper setup instructions, environment variable documentation, development workflow, API endpoint reference, project structure.

**Expected:** All 8 sections present. README provides sufficient information for a new developer to set up the project.

---

### TC-12: .env.example Documents All Variables
**Steps:**
1. Verify .env.example exists
2. Check it contains at minimum: POSTGRES_PASSWORD, DATABASE_URL, OPENAI_API_BASE, OPENAI_API_KEY, EMBEDDING_MODEL

**Expected:** All required environment variables documented with descriptions.

---

### Edge Cases

### TC-13: Models Handle Column Name Clash
**Steps:**
1. Verify RelatedTechniqueLink model has a `relationship` column (enum type) that doesn't conflict with SQLAlchemy's `relationship()` function.

**Expected:** Model imports without "MappedColumn is not callable" error. Uses `sa_relationship` alias pattern documented in KNOWLEDGE.md.

---

### TC-14: Docker Compose Network Avoids Existing Ranges
**Steps:**
1. Verify docker-compose.yml network subnet is 172.24.0.0/24 (not overlapping 172.16-172.23 or 172.29-172.30).

**Expected:** Network subnet configured to avoid conflicts with existing Docker networks on ub01.
