283 lines
10 KiB
Python
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())
|