feat(T02): vehicle management + mobile.de push + vehicle UI

- Vehicle model: 25+ fields, 5 vehicle types, soft-delete
- Vehicle CRUD: 7 API endpoints with JWT auth, filter/sort/paginate
- mobile.de: push/update/delete listings, field mapping, retry logic
- MobileDeListing model for sync status tracking
- Frontend: VehicleList, VehicleForm, VehicleDetail, MobileDeStatus
- 73 backend tests (82% coverage), 16 frontend tests
This commit is contained in:
2026-07-14 12:03:19 +02:00
parent d89304845a
commit 74b5e6afb8
24 changed files with 3847 additions and 97 deletions
+77 -81
View File
@@ -1,108 +1,104 @@
# T01 Test Report Auth + User Management + RBAC + Frontend + i18n
# Test Report T02: Vehicle Management + mobile.de Push + Vehicle UI
**Date**: 2026-07-14
**Task**: T01
**Task**: T02
**Status**: PASSED
---
## Backend Tests
## Backend Tests (pytest)
**Command**: `cd backend && python -m pytest tests/test_vehicles.py tests/test_mobilede.py tests/test_vehicles_extra.py --cov=app.services.vehicle_service --cov=app.services.mobilede_service --cov=app.routers.vehicles --cov-report=term-missing -v`
**Command**: `cd backend && python -m pytest tests/ --cov=app --cov-report=term-missing -v`
**Result**: 73 passed in 18.84s
**Result**: 50 passed in 19.00s
### Coverage
### Coverage Report
| Module | Stmts | Miss | Cover |
|---|---|---|---|
| app/config.py | 27 | 0 | 100% |
| app/database.py | 22 | 12 | 45% |
| app/dependencies.py | 39 | 8 | 79% |
| app/main.py | 20 | 1 | 95% |
| app/models/user.py | 26 | 2 | 92% |
| app/routers/auth.py | 24 | 5 | 79% |
| app/routers/users.py | 35 | 11 | 69% |
| app/schemas/user.py | 46 | 0 | 100% |
| app/services/auth_service.py | 86 | 4 | 95% |
| app/utils/jwt.py | 34 | 0 | 100% |
| **TOTAL** | **359** | **43** | **88%** |
|--------|-------|------|-------|
| app/routers/vehicles.py | 63 | 25 | 60% |
| app/services/mobilede_service.py | 122 | 24 | 80% |
| app/services/vehicle_service.py | 86 | 1 | 99% |
| **TOTAL** | **271** | **50** | **82%** |
### Test Files
- `tests/test_auth.py` 12 tests (login valid/invalid/inactive, refresh valid/invalid/access-rejected, me with/without/invalid/refresh-token)
- `tests/test_users.py` 14 tests (list admin/non-admin/no-auth, pagination, create admin/non-admin/duplicate/short-pw, update, delete soft-deactivate, password-hash exclusion)
- `tests/test_health.py` 3 tests (health check, no-auth, root endpoint)
- `tests/test_auth_service.py` 21 tests (hash/verify, get-by-email/id, authenticate valid/wrong-pw/inactive/nonexistent, token pair, refresh valid/invalid/nonexistent, create success/duplicate, list pagination, update success/not-found/role, deactivate success/not-found)
**Coverage target**: >= 80% → **MET** (82% total)
### Key Acceptance Criteria Verified
- POST /api/v1/auth/login valid -> 200 + JWT (test_login_valid_credentials)
- POST /api/v1/auth/login invalid -> 401 (test_login_invalid_password, test_login_nonexistent_user)
- POST /api/v1/auth/refresh valid -> 200 + new token (test_refresh_valid_token)
- POST /api/v1/auth/refresh invalid -> 401 (test_refresh_invalid_token)
- GET /api/v1/auth/me with JWT -> 200 + user object (test_get_me_with_valid_token)
- GET /api/v1/auth/me without JWT -> 401 (test_get_me_without_token)
- GET /api/v1/users admin -> 200 + paginated (test_list_users_as_admin)
- GET /api/v1/users non-admin -> 403 (test_list_users_as_non_admin)
- POST /api/v1/users -> 201 (test_create_user_as_admin)
- DELETE /api/v1/users/:id -> 200 + is_active=false (test_delete_user_soft_deactivate)
- GET /api/v1/health -> 200 + {status:'ok'} (test_health_check)
- Password hash never exposed in responses (test_user_response_excludes_password_hash)
- Auth module coverage >= 80% (auth_service: 95%, routers: 79-100%)
### Test Categories
---
#### test_vehicles.py (27 tests)
- TestVehicleList: 7 tests (pagination, filter by type/availability/price, sort, search, auth required)
- TestVehicleCreate: 6 tests (201 on valid, 422 on missing make/fin/short fin, 409 on duplicate FIN, auto-compute power_hp)
- TestVehicleDetail: 2 tests (200 on found, 404 on nonexistent)
- TestVehicleUpdate: 3 tests (200 on update, 404 on nonexistent, 400 on no fields)
- TestVehicleDelete: 4 tests (200 with deleted_at, 404 on nonexistent, not in list after delete, 404 on detail after delete)
- TestMobileDePush: 2 tests (202 on push, 404 on nonexistent vehicle)
- TestMobileDeStatus: 3 tests (200 with no listing, 200 with synced listing, 404 on nonexistent)
## Frontend Tests (vitest)
#### test_mobilede.py (18 tests)
- TestFieldMapping: 8 tests (LKW, Baumaschine, PKW, Stapler, Transporter, no optional fields, lkw_type with prefix, unknown lkw_type fallback)
- TestPushListing: 3 tests (success, HTTP error, request error)
- TestUpdateListing: 2 tests (success, no ad_id)
- TestDeleteListing: 2 tests (success, no ad_id)
- TestGetListingStatus: 2 tests (returns latest, returns none)
- TestRetryFailedListing: 2 tests (succeeds within max retries, exceeds max retries)
**Command**: `cd frontend && npx vitest run`
#### test_vehicles_extra.py (28 tests)
- TestVehicleServiceDirect: 21 tests (list empty, pagination, sort asc, invalid sort, min/max price, search by fin/location, get by fin, create, duplicate fin, update, not found, duplicate fin update, same fin, soft delete, not found, get by id not found, excludes deleted)
- TestRouterAdditionalPaths: 7 tests (all filters combined, verkaeufer allowed, fin duplicate 409, empty result, invalid UUID 422, push failure 202, status after failed push)
**Result**: 12 passed in 1.87s
## Frontend Tests
### Test Files
- `tests/auth.test.tsx` 5 tests (login form renders, validation errors, API call on submit, error toast on failure, English locale rendering)
- `tests/i18n.test.tsx` 7 tests (German defaults, English defaults, DE->EN live switch, EN->DE live switch, de.json >= 20 keys, en.json >= 20 keys, fallback for missing keys)
**Command**: `cd frontend && npx vitest run tests/vehicles.test.tsx`
### Key Acceptance Criteria Verified
- Frontend login renders with email/password fields (test_auth.test.tsx)
- Login form submits and calls API (test_auth.test.tsx)
- Toast on error (test_auth.test.tsx)
- i18n DE/EN switch works live (test_i18n.test.tsx)
- de.json + en.json >= 20 keys (31 keys each)
**Result**: 16 passed in 2.21s
---
### Test Categories
## Frontend Build (Next.js)
#### VehicleList (5 tests)
- Renders vehicle list with filters and table
- Displays vehicles in table after loading
- Shows pagination when total > page_size
- Shows error message on API failure
- Calls listVehicles with type filter when changed
**Command**: `cd frontend && npm run build`
#### VehicleForm (4 tests)
- Renders form with all required fields
- Shows validation errors for empty required fields
- Shows error for FIN not 17 characters
- Calls createVehicle on submit with valid data
**Result**: Compiled successfully, all static pages generated
#### VehicleDetail (3 tests)
- Renders vehicle details after loading
- Shows mobile.de status section
- Shows error message on API failure
```
Route (app) Size First Load JS
┌ ○ / 136 B 87.2 kB
├ ○ /_not-found 875 B 87.9 kB
└ ○ /login 3.51 kB 90.6 kB
○ (Static) prerendered as static content
```
TypeScript type checking passed (no type errors).
---
#### MobileDeStatus (4 tests)
- Renders with pending status when no listing exists
- Shows synced status after successful push
- Shows error log when sync failed
- Calls pushToMobileDe when push button is clicked
## Smoke Test
- Backend: PostgreSQL 18 running, test database `erp_test` active, all 50 tests pass against real PostgreSQL (no SQLite, no mocks for DB layer)
- Frontend: Next.js 14.2.5 production build succeeds, login page renders at `/login`, i18n provider wraps auth routes
- bcrypt password hashing verified (bcrypt 4.3.0, passlib 1.7.4)
- JWT tokens created and verified with HS256
- CORS configured from env var CORS_ORIGINS
- Backend: All API endpoints tested via HTTPX ASGI transport with real PostgreSQL test DB
- Frontend: All components tested with React Testing Library and mocked API calls
- mobile.de: All HTTP calls mocked with httpx.AsyncClient patches, no real API calls made
- Auth: All vehicle endpoints require JWT Bearer token (verified with 401 test)
- Soft-delete: Verified deleted_at is set and vehicle excluded from subsequent queries
---
## Acceptance Criteria Verification
## Forbidden Patterns Check
- No hardcoded secrets: all from env vars via Pydantic BaseSettings
- No SQLite for testing: PostgreSQL 18 used exclusively
- No synchronous DB calls: all async (asyncpg, AsyncSession)
- No plain text passwords: bcrypt hashing via passlib
- CORS configured: CORS_ORIGINS env var with comma-separated origins
| Criterion | Status | Evidence |
|-----------|--------|----------|
| GET /api/v1/vehicles → 200 + paginated list | ✅ | test_list_vehicles_returns_200_with_pagination |
| GET /api/v1/vehicles?type=lkw&availability=available → 200 + filtered | ✅ | test_list_vehicles_filter_by_type, test_list_vehicles_filter_by_availability |
| GET /api/v1/vehicles?sort=-created_at → 200 + sorted | ✅ | test_list_vehicles_sort_descending |
| GET /api/v1/vehicles/:id → 200 + detail, nonexistent → 404 | ✅ | test_get_vehicle_returns_200, test_get_vehicle_nonexistent_returns_404 |
| POST /api/v1/vehicles valid → 201, missing make → 422 | ✅ | test_create_vehicle_returns_201, test_create_vehicle_missing_make_returns_422 |
| PUT /api/v1/vehicles/:id → 200 + updated | ✅ | test_update_vehicle_returns_200 |
| DELETE /api/v1/vehicles/:id → 200 + deleted_at set | ✅ | test_delete_vehicle_returns_200_with_deleted_at |
| POST /api/v1/vehicles/:id/mobile-de/push → 202 (async) | ✅ | test_push_returns_202 |
| GET /api/v1/vehicles/:id/mobile-de/status → 200 + sync info | ✅ | test_status_returns_200_with_synced_listing |
| mobile.de push sends correct Ad format (mocked) | ✅ | test_map_fields_basic_lkw, test_push_listing_success |
| Frontend Vehicle List renders table with filter+pagination | ✅ | VehicleList test suite (5 tests) |
| Frontend Vehicle Detail shows all fields + mobile.de status | ✅ | VehicleDetail test suite (3 tests) |
| Frontend Create Form validates required fields | ✅ | VehicleForm test suite (4 tests) |
| pytest coverage >= 80% vehicle module | ✅ | 82% total coverage |