diff --git a/docs/superpowers/STATE.md b/docs/superpowers/STATE.md index 1a6659b..2c6c08d 100644 --- a/docs/superpowers/STATE.md +++ b/docs/superpowers/STATE.md @@ -1,6 +1,6 @@ # Premium Mechanic PWA — Implementation State -> Last updated: 2026-05-16 (end of session 2 — Phase B done) +> Last updated: 2026-05-17 (end of session 2 — Phase B + Phase C done) ## What is done @@ -15,10 +15,19 @@ | B | B2: SQLAlchemy models registered + tests | ✅ DONE | **already shipped in A4** (`be0553e`) — 38 metadata tests in `backend/tests/test_mechanic_models.py`, no extra B2 commit | | B | B3: Pydantic schemas | ✅ DONE | commit `2739cc8` + fix `2fb978d` — 14 schemas + 6 Literals in `backend/app/api/mechanic_schemas.py`, 15 behavioral tests in `backend/tests/test_mechanic_schemas.py`; fix added `= None` defaults to `Optional[X]` fields and tightened `MarkerOut.damage_type`/`severity` to `DamageType`/`Severity` Literals | | B | B4: Auth + DB session dependencies | ✅ DONE | commit `f0d7e83` — `backend/app/api/mechanic_deps.py` re-exports `get_db`+`get_current_user`, adds `require_mechanic` guard (admin OR member of any active `dept_type='svc'` department); 9 tests in `backend/tests/test_mechanic_deps.py` use fake-DB stubs (no real session needed) +| C | C1: `GET /me` endpoint | ✅ DONE | commit `a0bdcaa` + signature fix `1e5b822` — appended to `backend/app/api/mechanic.py`; returns `MeOut` from `require_mechanic` user; `permissions` is `["admin", "mechanic:inspect"]` for admins else `["mechanic:inspect"]`. Test fixtures (`client`, `override_mechanic`, `_fake_user`) in `backend/tests/test_mechanic_endpoints.py` use FastAPI `TestClient` + `app.dependency_overrides` per Option A — no real DB / no async. 6 endpoint tests. | +| C | C2: `GET /vehicles` list with `q`+`limit` search | ✅ DONE | commit `7be8783` — new `backend/app/api/mechanic_service.py` with `search_vehicles_with_last_inspection` (single aggregated subquery + outerjoin to avoid N+1 for last completed inspection per vehicle); endpoint in `mechanic.py`; new `VehicleListResponse` schema; 8 tests (6 endpoint + 2 service-stmt) with `_FakeSession.execute()` returning canned rows. Search hits plate (`CarV2.number`) + VIN only. | +| C | C3: `GET /vehicles/{id}` detail + bound /vehicles limit | ✅ DONE | commit `9109383` + style fix `90b8c66` — new `VehicleDetail(VehicleSummary)` + `InspectionSummary(_Base)` schemas; service `get_vehicle_by_id` + `recent_mechanic_inspections`; endpoint raises 404 when missing, `last_inspection_at` derived from first completed inspection in the list; applied `Query(20, ge=1, le=200)` bounds to C2's `/vehicles` limit param. 7 new tests (5 detail + 2 bounds). Service is now FastAPI-free (404 raise lives in endpoint, matching `automations.py` convention). | -**Branch state:** `feat/mechanic-mvp` at `f0d7e83`. Stack: `be0553e` (A4) → `d2113a0` (B1) → `2739cc8` (B3) → `2fb978d` (B3-fix) → `f0d7e83` (B4). All pushed to `origin` (Gitea). +**Branch state:** `feat/mechanic-mvp` at `90b8c66`. Full stack: `be0553e` (A4) → `d2113a0` (B1) → `2739cc8` + `2fb978d` (B3) → `f0d7e83` (B4) → `a0bdcaa` + `1e5b822` (C1) → `7be8783` (C2) → `9109383` + `90b8c66` (C3). All pushed to `origin` (Gitea). -**Test count on branch:** 62 mechanic tests pass (38 models + 15 schemas + 9 deps) in ~0.8s, no real DB needed. +**Test count on branch:** **83 mechanic tests pass** (38 models + 15 schemas + 9 deps + 21 endpoints across C1/C2/C3) in ~2s, no real DB needed. + +**Live mechanic API surface (4 routes):** +- `GET /api/v1/mechanic/healthz` — liveness +- `GET /api/v1/mechanic/me` — current user + permissions +- `GET /api/v1/mechanic/vehicles?q=&limit=` — list with search (1≤limit≤200) +- `GET /api/v1/mechanic/vehicles/{vehicle_id}` — detail with recent inspections ## Where things live @@ -65,46 +74,36 @@ Three places where the original plan (`docs/superpowers/plans/2026-05-16-premium When dispatching Phase C–F implementers, copy this override map into their context so they don't follow the stale subpackage / dept paths from the plan. +## Phase C testing-infrastructure decision (RESOLVED — Option A in force) + +Phase C runs against `FastAPI TestClient` (sync) + `app.dependency_overrides[require_mechanic]` and `app.dependency_overrides[get_db]` to inject fake user / fake session. Fake session is a `SimpleNamespace`-flavored `_FakeSession` class in `tests/test_mechanic_endpoints.py` that supports `.execute(stmt)` (returns `_FakeResult` seeded with rows) and `.get(model, pk)` (returns seeded object or `None`). + +No `conftest.py`, no `pytest-asyncio`, no real DB. SQL correctness is deliberately deferred to Phase J e2e. + +If Phase D/E/F needs a more complex query shape (e.g. `.scalars().one()` / `.first()`), extend `_FakeResult.scalars()` accordingly — currently it returns `self` and supports only `.all()`. See `test_mechanic_endpoints.py:_FakeResult` docstring. + ## Next task -### **Task C1 — `GET /me` endpoint** +### **Task D1 — inspections CRUD foundation (create + list)** -(From `docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md` Phase C.) +(From `docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md` Phase D.) -**Working dir:** `c:\NewProject\taxi-dashboard` (branch `feat/mechanic-mvp`, HEAD `f0d7e83`) +**Working dir:** `c:\NewProject\taxi-dashboard` (branch `feat/mechanic-mvp`, HEAD `90b8c66`) -**The endpoint** itself is trivial: -```python -@router.get("/me", response_model=MeOut) -async def me(user: User = Depends(require_mechanic)) -> MeOut: - perms = ["mechanic:inspect"] - if user.is_admin: - perms.insert(0, "admin") - return MeOut( - id=user.id, - name=user.full_name or user.username or f"user#{user.id}", - email=user.email, - permissions=perms, - ) -``` +⚠️ **Heads up** — Phase D introduces **write operations** + the **carry-over markers** logic, which is qualitatively different from Phase C's read-only endpoints: +- POST/PATCH endpoints need request-body validation (Pydantic schemas exist; verify they match the plan's body shapes) +- Carry-over: when a new inspection is created for a vehicle, copy unresolved markers from the previous inspection (`prev_inspection_id` self-reference). Needs careful design. +- Idempotency / race conditions: two mechanics opening the same vehicle simultaneously +- Real persistence with `_FakeSession`: tests need to verify the right `add()` / `flush()` / `refresh()` calls happened. The current `_FakeSession` only mocks read operations — needs extension for write paths (`add(obj)`, `flush()`, `refresh(obj)` to seed an `id`). -Add to `backend/app/api/mechanic.py` (the existing router from B1). Import `require_mechanic` from `app.api.mechanic_deps` and `MeOut` from `app.api.mechanic_schemas`. +**Recommendation for the next session:** before dispatching the D1 implementer, decide: +1. Whether the carry-over copy happens inside the create endpoint (simpler, atomic) or as a separate background step (e.g. Celery). +2. How `_FakeSession.add/flush/refresh` should behave for write-path tests (auto-assign id? track inserted objects?). +3. Whether Phase D is the right place to introduce a tiny real-DB conftest after all (re-evaluate Option C from earlier). -**⚠️ Open architecture decision before C1 (must resolve, not punt)** — see "Phase C testing-infrastructure decision" section below. The plan's tests for C1 assume `httpx.AsyncClient`, `pytest-asyncio`, `conftest.py` with `client` + `mechanic_user_token` fixtures, and a real test DB. **None of that exists.** Don't let the C1 implementer freelance — decide first, then dispatch. +These three decisions are NOT trivial and warrant a brainstorming pass at the start of the next session before any implementer is dispatched. -## Phase C testing-infrastructure decision (UNRESOLVED — answer this before Task C1) - -Current `backend/tests/` contains zero fixtures: no `conftest.py`, no async client, no JWT minting, no test DB. Phases A and B got by with pure-unit and metadata tests. Phase C is the first phase that needs **endpoint-level** tests, which forces a choice: - -| Option | Scope | Pros | Cons | -|---|---|---|---| -| **A. Dependency-override mocks** (recommended) | ~30 lines: override `get_current_user` / `require_mechanic` / `get_db` on the FastAPI app, fake `db.execute` results in each test. Sync `TestClient`. | Tiny, fast, no DB, matches the TDD London School preference in CLAUDE.md. Already proven in `test_mechanic_deps.py`. | Won't catch real-DB query bugs; defers integration testing to Phase J e2e. | -| **B. Real DB fixture infrastructure** | ~250–400 lines: `conftest.py` with async SQLite (aiosqlite) or testcontainers Postgres, `Base.metadata.create_all`, seeded user+department+M2M, JWT mint helper, `pytest-asyncio` + `httpx.AsyncClient`. | Catches real-query bugs, e2e-ish from day one. | Big tangent, slows every PR, async fixture lifecycle is fiddly. | -| **C. Hybrid** | Start with A; add a tiny "DB smoke" subset (Option B) only for endpoints with non-trivial query logic. | Best of both, incrementally. | Two paradigms to maintain. | - -**Recommendation: A**, with the understanding that Phase J adds end-to-end against a real running backend (the plan's existing Phase J e2e + smoke test tasks already cover the real-DB path). - -If A is chosen, the controller's prompt for the C1 implementer should explicitly tell them: "use FastAPI `TestClient` (sync) + `app.dependency_overrides[require_mechanic]` to inject a fake user; don't add a conftest, don't add a real DB, don't introduce `pytest-asyncio`." +Read the D1 task definition in the plan before deciding. Also check what other relevant Pydantic schemas exist that we built in B3 (`InspectionCreate`, `InspectionPatch`, `MarkerCreate`, `MarkerPatch`). ## How to resume in a new session @@ -115,21 +114,24 @@ In a new Claude Code chat in `c:\NewProject\`, paste this prompt: Прочитай PremiumDriverApp/docs/superpowers/STATE.md — там полное состояние и следующая задача. -Phase B полностью завершён (B1–B4, последний коммит f0d7e83). Следующая — -Task C1 (GET /me) из плана PremiumDriverApp/docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md, -НО сначала надо разрешить "Phase C testing-infrastructure decision" в STATE.md (Option A / B / C). +Phase C полностью завершён (C1–C3, последний коммит 90b8c66, 83/83 тестов зелёных). +Следующая фаза — Phase D (inspections CRUD с carry-over markers) из плана +PremiumDriverApp/docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md. -Используй superpowers:subagent-driven-development skill. Реши test-infra вопрос, потом -спавни первого implementer-subagent на C1 со специфической инструкцией про тестовый подход. +⚠️ Перед спавном D1 implementer-subagent сделай brainstorming pass по трём решениям +из секции "Next task" в STATE.md: (1) carry-over inline-vs-async, (2) _FakeSession +write-path extension, (3) пересмотр Option C для real-DB conftest в Phase D. + +Используй superpowers:brainstorming, потом superpowers:subagent-driven-development. ``` -That prompt is the **single entry point** for the next session — Claude will read STATE.md, ground itself, resolve the test-infrastructure fork, and continue dispatching subagents from Task C1 onward. +That prompt is the **single entry point** for the next session — Claude will read STATE.md, ground itself, brainstorm the three Phase D architecture decisions, then dispatch the D1 implementer. ## Remaining roadmap (Phase 1 MVP) - ~~**Phase B** (4 tasks)~~ ✅ done — module skeleton, models, schemas, auth deps -- **Phase C** (3 tasks): GET /me, GET /vehicles, GET /vehicles/{id} -- **Phase D** (3 tasks): inspections CRUD with carry-over markers logic +- ~~**Phase C** (3 tasks)~~ ✅ done — GET /me, GET /vehicles, GET /vehicles/{id} +- **Phase D** (3 tasks): inspections CRUD with carry-over markers logic ← **NEXT** - **Phase E** (5 tasks): MinIO storage layer + presigned upload/confirm + 302 read + celery thumbnail - **Phase F** (2 tasks): markers create/update/delete - **Phase G** (5 tasks): Vite + React + Tailwind + shadcn + PWA scaffold @@ -137,7 +139,7 @@ That prompt is the **single entry point** for the next session — Claude will r - **Phase I** (6 tasks): CameraCapture + PhotoSlot + VehicleScheme + InspectionEditor + InspectionReview - **Phase J** (4 tasks): e2e test + deploy backend + deploy frontend + smoke test -≈ 31 tasks left until MVP live on `mechanic.pptaxi.ru`. +≈ 28 tasks left until MVP live on `mechanic.pptaxi.ru`. ## Known external dependencies