---
estimated_steps: 42
estimated_files: 4
skills_used: []
---

# T02: Write integration tests proving ingestion, creator auto-detection, and idempotent re-upload

Set up pytest + pytest-asyncio test infrastructure and write integration tests for the ingest endpoint. Tests run against a real PostgreSQL database using httpx.AsyncClient on the FastAPI app.

## Steps

1. Add test dependencies to `backend/requirements.txt`: `pytest>=8.0`, `pytest-asyncio>=0.24`, `python-multipart>=0.0.9` (if not already present).
2. Install: `cd backend && pip install pytest pytest-asyncio`.
3. Create `tests/conftest.py` with:
   - Import `create_async_engine`, `async_sessionmaker` from SQLAlchemy.
   - Create a test database URL fixture using env var `TEST_DATABASE_URL` with default `postgresql+asyncpg://chrysopedia:changeme@localhost:5433/chrysopedia_test`.
   - `@pytest_asyncio.fixture` for `db_engine` that creates all tables via `Base.metadata.create_all` at setup and drops them at teardown (use `run_sync`).
   - `@pytest_asyncio.fixture` for `db_session` that yields an AsyncSession.
   - `@pytest_asyncio.fixture` for `client` that patches `get_session` dependency override on the FastAPI app and yields an `httpx.AsyncClient` with `ASGITransport`.
   - `@pytest.fixture` for `sample_transcript_path` returning `tests/fixtures/sample_transcript.json`.
   - `@pytest.fixture` for `tmp_transcript_dir` using `tmp_path` to override `transcript_storage_path`.
4. Create `tests/test_ingest.py` with `@pytest.mark.asyncio` tests:
   - `test_ingest_creates_creator_and_video`: POST sample_transcript.json → 200, response has video_id, creator_id, segments_stored=5, creator_name='Skope'. Query DB to confirm Creator with folder_name='Skope' and slug='skope' exists. Confirm SourceVideo with processing_status='transcribed' exists. Confirm 5 TranscriptSegment rows with segment_index 0-4.
   - `test_ingest_reuses_existing_creator`: Pre-create a Creator with folder_name='Skope'. POST transcript → response creator_id matches pre-created ID. Only 1 Creator row in DB.
   - `test_ingest_idempotent_reupload`: POST same transcript twice → second returns is_reupload=True, same video_id. Still only 5 segments (not 10). Only 1 SourceVideo row.
   - `test_ingest_saves_json_to_disk`: POST transcript → raw JSON file exists at the expected path in tmp_transcript_dir.
   - `test_ingest_rejects_invalid_json`: POST a file with invalid JSON → 400/422 error.
   - `test_ingest_rejects_missing_fields`: POST JSON without `creator_folder` → 400/422 error.
5. Add a `pytest.ini` or `pyproject.toml` section with `asyncio_mode = "auto"` to avoid per-test markers.

## Must-Haves

- [ ] pytest and pytest-asyncio installed and configured
- [ ] conftest.py with async DB fixtures and ASGI test client
- [ ] test_ingest_creates_creator_and_video passes
- [ ] test_ingest_reuses_existing_creator passes
- [ ] test_ingest_idempotent_reupload passes
- [ ] test_ingest_saves_json_to_disk passes
- [ ] test_ingest_rejects_invalid_json passes
- [ ] test_ingest_rejects_missing_fields passes
- [ ] All tests pass: `cd backend && python -m pytest tests/test_ingest.py -v`

## Failure Modes

| Dependency | On error | On timeout | On malformed response |
|------------|----------|-----------|----------------------|
| PostgreSQL (test DB) | Tests skip or fail with clear connection error | pytest-asyncio timeout | N/A |
| FastAPI test client | Test fails with assertion error | httpx timeout | Response schema mismatch caught by assertions |

## Verification

- `cd backend && python -m pytest tests/test_ingest.py -v` — all 6 tests pass
- `docker compose config` exits 0 (no regressions)

## Observability Impact

- Signals added/changed: test output showing pass/fail per test case with timing
- How a future agent inspects this: `cd backend && python -m pytest tests/test_ingest.py -v --tb=short`
- Failure state exposed: pytest output shows which assertion failed, with full diff

## Inputs

- ``backend/routers/ingest.py` — the ingestion endpoint to test (created in T01)`
- ``backend/main.py` — FastAPI app with ingest router mounted (modified in T01)`
- ``backend/schemas.py` — TranscriptIngestResponse schema (modified in T01)`
- ``backend/models.py` — Creator, SourceVideo, TranscriptSegment models for DB assertions`
- ``backend/database.py` — get_session dependency to override in tests`
- ``backend/config.py` — Settings to override transcript_storage_path in tests`
- ``tests/fixtures/sample_transcript.json` — test input file with 5 segments`

## Expected Output

- ``tests/conftest.py` — pytest fixtures for async DB, test client, and transcript paths`
- ``tests/test_ingest.py` — 6 integration tests covering happy path, idempotency, and error cases`
- ``pytest.ini` — pytest configuration with asyncio_mode=auto`
- ``backend/requirements.txt` — modified with pytest and pytest-asyncio dependencies`

## Verification

cd backend && python -m pytest tests/test_ingest.py -v && docker compose config > /dev/null 2>&1
