Files
service-finder/tests/active/test_service_book_api.py

283 lines
10 KiB
Python

#!/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())