#!/usr/bin/env python3 """ Service Book API E2E Test Tests GET and POST /assets/{asset_id}/events endpoints. """ import asyncio import httpx from sqlalchemy import select, delete from app.db.session import AsyncSessionLocal from app.models.identity import User, Person from app.models.marketplace.organization import Organization, OrganizationMember from app.models.vehicle.asset import Asset, AssetEvent from app.models.vehicle.vehicle_definitions import VehicleModelDefinition from app.core.security import create_tokens import uuid async def setup_test_data(): async with AsyncSessionLocal() as db: # Clean previous test data await db.execute(delete(AssetEvent).where(AssetEvent.description == "TEST Service Book Event")) await db.execute(delete(Asset).where(Asset.license_plate == "SERVBK-01")) await db.execute(delete(VehicleModelDefinition).where( VehicleModelDefinition.make == "TestMake", VehicleModelDefinition.marketing_name == "TestModel" )) await db.execute(delete(OrganizationMember)) await db.execute(delete(Organization).where(Organization.name == "Test Service Book Org")) test_users = await db.execute(select(User).where(User.email == "test_servicebook@test.com")) for u in test_users.scalars().all(): await db.execute(delete(User).where(User.id == u.id)) await db.commit() # Create Person person = Person(first_name="ServiceBook", last_name="Tester") db.add(person) await db.flush() # Create User user = User( email="test_servicebook@test.com", hashed_password="pw", is_active=True, person_id=person.id, subscription_plan="personal", preferred_language="en", region_code="HU", preferred_currency="EUR", scope_level="personal" ) db.add(user) await db.flush() # Create Organization org = Organization(name="Test Service Book Org", tax_number="999999") db.add(org) await db.flush() member = OrganizationMember(organization_id=org.id, user_id=user.id, role="owner") db.add(member) # Create VehicleModelDefinition model_def = VehicleModelDefinition( make="TestMake", marketing_name="TestModel", normalized_name="testmake_testmodel", year_from=2020, year_to=2025, data_status="verified" ) db.add(model_def) await db.commit() # Create Asset asset = Asset( license_plate="SERVBK-01", vin="TESTVINSERVBK001", brand="TestMake", model="TestModel", year_of_manufacture=2022, current_mileage=10000, owner_person_id=person.id, owner_org_id=org.id, status="active" ) db.add(asset) await db.commit() await db.refresh(asset) # Generate token token, _ = create_tokens(data={"sub": str(user.id)}) return token, asset.id async def run_tests(): print("=" * 60) print("๐Ÿงช SERVICE BOOK API E2E TEST") print("=" * 60) print("\n--- SETUP ---") token, asset_id = await setup_test_data() print(f"โœ… Test data created. Asset ID: {asset_id}") headers = {"Authorization": f"Bearer {token}"} async with httpx.AsyncClient(base_url="http://127.0.0.1:8000") as client: # TEST 1: GET events (empty list) print("\n--- TEST 1: GET /assets/{asset_id}/events (empty) ---") r1 = await client.get(f"/api/v1/assets/{asset_id}/events", headers=headers) print(f" Status: {r1.status_code}") if r1.status_code == 200: data = r1.json() print(f" Events count: {len(data)}") assert len(data) == 0, "Expected empty events list" print(" โœ… PASS: Empty events list returned") else: print(f" โŒ FAIL: {r1.text}") return # TEST 2: POST event (SERVICE type) print("\n--- TEST 2: POST /assets/{asset_id}/events (SERVICE) ---") r2 = await client.post( f"/api/v1/assets/{asset_id}/events", headers=headers, json={ "event_type": "SERVICE", "description": "TEST Service Book Event - Oil Change", "odometer_reading": 15000, "event_date": "2026-06-15T10:00:00Z" } ) print(f" Status: {r2.status_code}") if r2.status_code == 201: data = r2.json() print(f" Event ID: {data.get('id')}") print(f" Event type: {data.get('event_type')}") print(f" Odometer: {data.get('odometer_reading')}") assert data["event_type"] == "SERVICE" assert data["odometer_reading"] == 15000 print(" โœ… PASS: Service event created") else: print(f" โŒ FAIL: {r2.text}") return # TEST 3: POST event (REPAIR type) print("\n--- TEST 3: POST /assets/{asset_id}/events (REPAIR) ---") r3 = await client.post( f"/api/v1/assets/{asset_id}/events", headers=headers, json={ "event_type": "REPAIR", "description": "TEST Service Book Event - Brake replacement", "odometer_reading": 15500, } ) print(f" Status: {r3.status_code}") if r3.status_code == 201: data = r3.json() print(f" Event ID: {data.get('id')}") print(f" Event type: {data.get('event_type')}") assert data["event_type"] == "REPAIR" print(" โœ… PASS: Repair event created") else: print(f" โŒ FAIL: {r3.text}") return # TEST 4: GET events (should have 2 events, sorted by date desc) print("\n--- TEST 4: GET /assets/{asset_id}/events (2 events) ---") r4 = await client.get(f"/api/v1/assets/{asset_id}/events", headers=headers) print(f" Status: {r4.status_code}") if r4.status_code == 200: data = r4.json() print(f" Events count: {len(data)}") assert len(data) == 2, f"Expected 2 events, got {len(data)}" # Should be sorted by event_date descending print(f" First event type: {data[0]['event_type']}") print(f" Second event type: {data[1]['event_type']}") print(" โœ… PASS: Both events returned, sorted by date") else: print(f" โŒ FAIL: {r4.text}") return # TEST 5: POST event with lower odometer (should NOT update mileage) print("\n--- TEST 5: POST event with lower odometer ---") r5 = await client.post( f"/api/v1/assets/{asset_id}/events", headers=headers, json={ "event_type": "INSPECTION", "description": "TEST Service Book Event - Annual inspection", "odometer_reading": 14000, # Lower than current 15500 } ) print(f" Status: {r5.status_code}") if r5.status_code == 201: data = r5.json() print(f" Event type: {data.get('event_type')}") print(f" Odometer: {data.get('odometer_reading')}") print(" โœ… PASS: Inspection event created (odometer not updated)") else: print(f" โŒ FAIL: {r5.text}") return # TEST 6: POST event without odometer print("\n--- TEST 6: POST event without odometer ---") r6 = await client.post( f"/api/v1/assets/{asset_id}/events", headers=headers, json={ "event_type": "ACCIDENT", "description": "TEST Service Book Event - Minor accident", } ) print(f" Status: {r6.status_code}") if r6.status_code == 201: data = r6.json() print(f" Event type: {data.get('event_type')}") print(f" Odometer: {data.get('odometer_reading')}") assert data["odometer_reading"] is None print(" โœ… PASS: Accident event created without odometer") else: print(f" โŒ FAIL: {r6.text}") return # TEST 7: GET events (should have 4 events) print("\n--- TEST 7: GET /assets/{asset_id}/events (4 events) ---") r7 = await client.get(f"/api/v1/assets/{asset_id}/events", headers=headers) print(f" Status: {r7.status_code}") if r7.status_code == 200: data = r7.json() print(f" Events count: {len(data)}") assert len(data) == 4, f"Expected 4 events, got {len(data)}" print(" โœ… PASS: All 4 events returned") else: print(f" โŒ FAIL: {r7.text}") return # TEST 8: GET events with skip/limit print("\n--- TEST 8: GET events with skip=0&limit=2 ---") r8 = await client.get( f"/api/v1/assets/{asset_id}/events?skip=0&limit=2", headers=headers ) print(f" Status: {r8.status_code}") if r8.status_code == 200: data = r8.json() print(f" Events count: {len(data)}") assert len(data) == 2, f"Expected 2 events, got {len(data)}" print(" โœ… PASS: Pagination works correctly") else: print(f" โŒ FAIL: {r8.text}") return # TEST 9: Unauthorized access print("\n--- TEST 9: Unauthorized access (no token) ---") r9 = await client.get(f"/api/v1/assets/{asset_id}/events") print(f" Status: {r9.status_code}") if r9.status_code == 401: print(" โœ… PASS: Unauthorized access rejected") else: print(f" โŒ FAIL: Expected 401, got {r9.status_code}") return # TEST 10: Invalid asset ID print("\n--- TEST 10: Invalid asset ID ---") fake_id = uuid.uuid4() r10 = await client.get(f"/api/v1/assets/{fake_id}/events", headers=headers) print(f" Status: {r10.status_code}") if r10.status_code == 404: print(" โœ… PASS: Invalid asset ID returns 404") else: print(f" โŒ FAIL: Expected 404, got {r10.status_code}") return print("\n" + "=" * 60) print("๐ŸŽ‰ ALL TESTS PASSED!") print("=" * 60) if __name__ == "__main__": asyncio.run(run_tests())