---
phase: 02-driver-management
plan: "02"
subsystem: api
tags: [fastapi, htmx, jinja2, peewee, zipfile, sha256, dedup, inf-parser]

# Dependency graph
requires:
  - phase: 02-01
    provides: INF parser service (parse_inf, ParsedInf, _detect_encoding)
  - phase: 01-foundation
    provides: FastAPI app shell, DriverStore, Driver model, init_db, base templates
provides:
  - POST /drivers/upload endpoint with ZIP validation, INF parsing, SHA256 dedup, Peewee persistence
  - GET /drivers page with HTMX upload form and driver library table
  - HTMX partial (partials/driver_list.html) returned on upload with select dropdown and unused-file notice
  - Integration test suite (8 tests) for driver upload flow
affects: [03-printer-management, 04-package-generation]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - HTMX outerHTML swap: upload endpoint returns partial HTML fragment replacing #driver-list div
    - Dynamic config read: import imptune.config as _cfg and read _cfg.DRIVERS_DIR at call time for monkeypatch compatibility
    - TDD workflow: RED (test commit) -> GREEN (impl commit) within same task

key-files:
  created:
    - imptune/api/drivers.py
    - imptune/templates/drivers.html
    - imptune/templates/partials/driver_list.html
    - tests/test_driver_upload.py
  modified:
    - imptune/api/pages.py
    - imptune/main.py
    - tests/conftest.py

key-decisions:
  - "Always render <select> even for single-model drivers — simplifies template logic and consistent UI"
  - "Dynamic DRIVERS_DIR read (import config module, not top-level constant) so monkeypatch works in tests"
  - "TestClient context manager in conftest client fixture — required for lifespan/init_db to trigger in integration tests"
  - "HTMX-friendly 400 error: return HTMLResponse with <div id='driver-list'> wrapper so HTMX can swap error inline"

patterns-established:
  - "HTMX partial pattern: upload returns <div id='driver-list'> fragment; page has matching hx-target; outerHTML swap replaces entire div"
  - "driver_data pattern: routes build list of dicts with {'driver': orm_obj, 'names': list[str]} to pre-parse JSON in Python rather than Jinja2"
  - "Config monkeypatch: endpoints import config module (not constants) so test fixtures can override DRIVERS_DIR/DB_PATH"

requirements-completed: [DRV-01, DRV-03, DRV-04, DRV-05]

# Metrics
duration: 3min
completed: 2026-04-10
---

# Phase 02 Plan 02: Driver Upload Endpoint Summary

**HTMX-driven driver ZIP upload with INF parsing, SHA256 dedup, Peewee persistence, and select dropdown returning 8/8 integration tests green**

## Performance

- **Duration:** ~3 min
- **Started:** 2026-04-10T10:18:52Z
- **Completed:** 2026-04-10T10:22:00Z
- **Tasks:** 2 (Task 1 TDD: RED + GREEN; Task 2 templates completed inline)
- **Files modified:** 7

## Accomplishments

- POST /drivers/upload: validates ZIP, finds INF, parses via INF parser service, saves via DriverStore (SHA256 content-addressed), upserts Driver record with json.dumps(driver_names) in driver_desc — full dedup on re-upload
- GET /drivers page renders upload form with HTMX attributes and existing driver library table
- HTMX partial (partials/driver_list.html): wraps content in `<div id="driver-list">` for outerHTML swap; shows unused-file notice with count and expandable list; renders driver names as `<select>` dropdown
- 8 integration tests written TDD-first (RED commit, then GREEN): page render, valid upload, non-ZIP 400, no-INF 400, select presence, DB persistence, dedup, unused files in response

## Task Commits

1. **Test RED phase: failing integration tests** - `8ecfbf2` (test)
2. **Task 1 + Task 2: upload endpoint, templates, pages route, router registration** - `c648fc5` (feat)

## Files Created/Modified

- `imptune/api/drivers.py` - POST /drivers/upload endpoint with full validation, INF parsing, DriverStore save, Peewee get_or_create
- `imptune/api/pages.py` - Added GET /drivers route with driver_data context
- `imptune/main.py` - Registered drivers.router
- `imptune/templates/drivers.html` - Drivers page extending base.html with HTMX upload form
- `imptune/templates/partials/driver_list.html` - HTMX swap target with table, select dropdown, unused-files notice
- `tests/test_driver_upload.py` - 8 integration tests covering all success and error paths
- `tests/conftest.py` - Fixed client fixture to use TestClient as context manager

## Decisions Made

- Always render `<select>` even for single driver name — uniform UI and simpler template logic
- Read `_cfg.DRIVERS_DIR` dynamically (not top-level constant import) so test monkeypatching works
- `TestClient(app)` must be used as a context manager for Starlette 0.46+ to trigger lifespan and run `init_db()`
- HTMX errors: return `HTMLResponse` with `<div id='driver-list'>` wrapper at status 400 so HTMX can swap error into target area

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 - Blocking] TestClient context manager required for lifespan trigger**
- **Found during:** Task 1 GREEN (first test run)
- **Issue:** `TestClient(app)` without context manager does not run lifespan in Starlette 0.46+, so `init_db()` never called; DB remained deferred (None), causing `InterfaceError` on all ORM queries
- **Fix:** Changed conftest `client` fixture from `return TestClient(app)` to `with TestClient(app) as c: yield c`
- **Files modified:** tests/conftest.py
- **Verification:** All 48 tests pass including pre-existing health, static, DB, and parser tests
- **Committed in:** c648fc5 (Task 1 feat commit)

**2. [Rule 1 - Bug] Dynamic DRIVERS_DIR read to support monkeypatch**
- **Found during:** Task 1 GREEN (test_driver_persisted failure)
- **Issue:** `from imptune.config import DRIVERS_DIR` captured the value at import time; tests patching `cfg.DRIVERS_DIR` had no effect — files written to `/data/drivers` (production path) not the tmp dir
- **Fix:** Changed to `import imptune.config as _cfg` and use `_cfg.DRIVERS_DIR` at call time
- **Files modified:** imptune/api/drivers.py
- **Verification:** test_driver_persisted passes; file found in tmp_data_dir/drivers/
- **Committed in:** c648fc5 (Task 1 feat commit)

**3. [Rule 1 - Bug] Template always renders `<select>` for any non-empty names list**
- **Found during:** Task 1 GREEN (test_upload_returns_select failure)
- **Issue:** Template only showed `<select>` for multiple names; sample INF has 1 driver name, so test failed
- **Fix:** Changed template condition from `{% if item.names | length > 1 %}` to `{% if item.names %}`
- **Files modified:** imptune/templates/partials/driver_list.html
- **Verification:** test_upload_returns_select passes
- **Committed in:** c648fc5 (Task 1 feat commit)

---

**Total deviations:** 3 auto-fixed (1 Rule 3 blocking, 2 Rule 1 bugs)
**Impact on plan:** All three fixes necessary for correct test isolation and behavior. No scope creep.

## Issues Encountered

None beyond the three auto-fixed deviations above.

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

- Driver upload feature fully functional: upload, parse, persist, dedup, UI feedback
- Driver records in SQLite with driver_desc (JSON list), architecture, inf_filename, has_cat_file
- Phase 03 (printer management) can reference drivers via Driver model and driver select dropdowns
- Phase 04 (package generation) can read persisted driver ZIPs from DriverStore using sha256

---
*Phase: 02-driver-management*
*Completed: 2026-04-10*
