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:
+77
-81
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user