Files
drivers_bot/tests/test_sto_booking.py
2026-05-15 05:17:54 +09:00

267 lines
9.4 KiB
Python

from datetime import UTC, date, datetime, time, timedelta
import pytest
def next_weekday(target: int) -> date:
today = date.today()
delta = (target - today.weekday()) % 7
if delta == 0:
delta = 7
return today + timedelta(days=delta)
async def create_verified_center(client, owner_headers, admin_headers, internal_headers, *, city: str = "Seoul") -> dict:
center = (
await client.post(
"/api/service-centers",
headers=owner_headers,
json={
"display_name": f"Booking Service {city}",
"country": "KR",
"city": city,
"specializations": ["oil_change", "diagnostics"],
},
)
).json()
await client.post(
"/api/users",
headers=internal_headers,
json={"telegram_id": 9001, "platform_role": "admin"},
)
response = await client.post(f"/api/admin/service-centers/{center['id']}/verify", headers=admin_headers)
assert response.status_code == 200
return response.json()
@pytest.mark.asyncio
async def test_sto_catalog_shows_only_approved_and_filters_city(
client, auth_headers, admin_auth_headers, internal_headers
) -> None:
approved = await create_verified_center(client, auth_headers, admin_auth_headers, internal_headers, city="Seoul")
await client.post(
"/api/service-centers",
headers=auth_headers,
json={"display_name": "Pending Booking Service", "country": "KR", "city": "Busan"},
)
response = await client.get("/api/sto/catalog?city=Seoul", headers=auth_headers)
assert response.status_code == 200
ids = [item["id"] for item in response.json()]
assert ids == [approved["id"]]
@pytest.mark.asyncio
async def test_available_slots_skip_weekend_lunch_and_holidays(
client, auth_headers, admin_auth_headers, internal_headers
) -> None:
center = await create_verified_center(client, auth_headers, admin_auth_headers, internal_headers)
monday = next_weekday(0)
saturday = next_weekday(5)
await client.post(
"/api/sto/settings/booking",
headers=auth_headers,
json={
"service_center_id": center["id"],
"working_days": [0],
"open_time": "09:00:00",
"close_time": "13:00:00",
"lunch_break_start": "11:00:00",
"lunch_break_end": "12:00:00",
"slot_duration_minutes": 60,
"max_parallel_bookings": 1,
"accepts_online_booking": True,
},
)
await client.post(
"/api/sto/settings/holidays",
headers=auth_headers,
json={"service_center_id": center["id"], "holiday_date": saturday.isoformat(), "reason": "Closed"},
)
monday_slots = (
await client.get(
f"/api/sto/{center['id']}/available-slots?date_from={monday}&date_to={monday}&duration_minutes=60",
headers=auth_headers,
)
).json()
saturday_slots = (
await client.get(
f"/api/sto/{center['id']}/available-slots?date_from={saturday}&date_to={saturday}&duration_minutes=60",
headers=auth_headers,
)
).json()
assert {datetime.fromisoformat(slot["start_at"]).time() for slot in monday_slots} == {time(9, 0), time(10, 0), time(12, 0)}
assert saturday_slots == []
@pytest.mark.asyncio
async def test_customer_booking_lifecycle_capacity_calendar_work_order_and_notifications(
client, auth_headers, other_auth_headers, admin_auth_headers, internal_headers
) -> None:
center = await create_verified_center(client, auth_headers, admin_auth_headers, internal_headers)
workday = next_weekday(0)
start_at = datetime.combine(workday, time(10, 0), tzinfo=UTC)
proposed_at = datetime.combine(workday, time(14, 0), tzinfo=UTC)
await client.post(
"/api/sto/settings/booking",
headers=auth_headers,
json={
"service_center_id": center["id"],
"working_days": [0, 1, 2, 3, 4],
"open_time": "09:00:00",
"close_time": "18:00:00",
"slot_duration_minutes": 60,
"max_parallel_bookings": 1,
"accepts_online_booking": True,
},
)
vehicle = (
await client.post(
"/api/my/vehicles",
headers=other_auth_headers,
json={"name": "Client car", "current_odometer": 45000, "oil_change_interval_km": 10000},
)
).json()
past_response = await client.post(
"/api/appointments",
headers=other_auth_headers,
json={
"service_center_id": center["id"],
"vehicle_id": vehicle["id"],
"service_type": "oil_change",
"service_name": "Oil change",
"requested_start_at": (datetime.now(UTC) - timedelta(days=1)).isoformat(),
"estimated_duration_minutes": 60,
},
)
assert past_response.status_code == 409
created = await client.post(
"/api/appointments",
headers=other_auth_headers,
json={
"service_center_id": center["id"],
"vehicle_id": vehicle["id"],
"service_type": "oil_change",
"service_name": "Oil change",
"requested_start_at": start_at.isoformat(),
"estimated_duration_minutes": 60,
"customer_comment": "Need synthetic oil",
},
)
assert created.status_code == 201
appointment = created.json()
assert appointment["status"] == "requested"
duplicate = await client.post(
"/api/appointments",
headers=other_auth_headers,
json={
"service_center_id": center["id"],
"vehicle_id": vehicle["id"],
"service_type": "diagnostics",
"service_name": "Diagnostics",
"requested_start_at": start_at.isoformat(),
"estimated_duration_minutes": 60,
},
)
assert duplicate.status_code == 409
proposed = await client.post(
f"/api/sto/appointments/{appointment['id']}/propose-time",
headers=auth_headers,
json={"proposed_start_at": proposed_at.isoformat(), "comment": "Better window"},
)
assert proposed.status_code == 200
assert proposed.json()["status"] == "proposed_new_time"
accepted = await client.post(
f"/api/appointments/{appointment['id']}/accept-proposed-time",
headers=other_auth_headers,
)
assert accepted.status_code == 200
assert accepted.json()["status"] == "confirmed"
calendar = await client.get(
f"/api/sto/calendar?service_center_id={center['id']}&date_from={workday.isoformat()}T00:00:00Z&date_to={workday.isoformat()}T23:59:59Z",
headers=auth_headers,
)
assert calendar.status_code == 200
assert calendar.json()[0]["id"] == appointment["id"]
work_order = await client.post(
f"/api/sto/appointments/{appointment['id']}/create-work-order",
headers=auth_headers,
json={"odometer": 45100},
)
assert work_order.status_code == 201
assert work_order.json()["vehicle_id"] == vehicle["id"]
my_appointments = await client.get("/api/appointments/my", headers=other_auth_headers)
assert my_appointments.json()[0]["linked_work_order_id"] == work_order.json()["id"]
@pytest.mark.asyncio
async def test_recommendations_can_be_generated_dismissed_and_linked_to_booking(
client, auth_headers, other_auth_headers, admin_auth_headers, internal_headers
) -> None:
center = await create_verified_center(client, auth_headers, admin_auth_headers, internal_headers)
workday = next_weekday(1)
start_at = datetime.combine(workday, time(10, 0), tzinfo=UTC)
vehicle = (
await client.post(
"/api/my/vehicles",
headers=other_auth_headers,
json={"name": "Recommendation car", "current_odometer": 90000, "oil_change_interval_km": 10000},
)
).json()
recommendations = await client.get(
f"/api/vehicles/{vehicle['id']}/maintenance-recommendations",
headers=other_auth_headers,
)
assert recommendations.status_code == 200
oil_recommendation = recommendations.json()[0]
assert oil_recommendation["recommendation_type"] == "oil_change"
appointment = (
await client.post(
"/api/appointments",
headers=other_auth_headers,
json={
"service_center_id": center["id"],
"vehicle_id": vehicle["id"],
"service_type": "oil_change",
"service_name": "Oil change",
"requested_start_at": start_at.isoformat(),
"estimated_duration_minutes": 60,
"source_recommendation_id": oil_recommendation["id"],
},
)
).json()
booked = await client.post(
f"/api/maintenance-recommendations/{oil_recommendation['id']}/book",
headers=other_auth_headers,
json={"appointment_id": appointment["id"]},
)
assert booked.status_code == 200
assert booked.json()["status"] == "booked"
custom = (
await client.post(
f"/api/vehicles/{vehicle['id']}/maintenance-recommendations",
headers=other_auth_headers,
json={"recommendation_type": "brakes", "title": "Check brakes", "priority": "low"},
)
).json()
dismissed = await client.post(
f"/api/maintenance-recommendations/{custom['id']}/dismiss",
headers=other_auth_headers,
)
assert dismissed.status_code == 200
assert dismissed.json()["status"] == "dismissed"