149 lines
7.3 KiB
Markdown
149 lines
7.3 KiB
Markdown
---
|
|
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*
|