whFalsa / tests /test_tools.py
codeBOKER's picture
Allow drivers to publish trips in their own style; add reply_instruction for no-match results; improve privacy prompts
c925062
Raw
History Blame Contribute Delete
31.7 kB
import pytest
from app.tools.handlers import FalzhToolHandlers
from tests.conftest import FakeEmbeddings, FakeRepository, FakeWhatsApp
def make_handlers(
*,
repository: FakeRepository | None = None,
embeddings: FakeEmbeddings | None = None,
whatsapp: FakeWhatsApp | None = None,
customer: dict | None = None,
sender_phone: str = "967700000001",
) -> FalzhToolHandlers:
repo = repository or FakeRepository()
if customer is None:
cust = {"id": "cust-1", "remoteJid": sender_phone, "session_data": {}}
repo.customers_by_remote_jid[sender_phone] = cust
customer = cust
return FalzhToolHandlers(
repository=repo,
embeddings=embeddings or FakeEmbeddings(),
whatsapp=whatsapp or FakeWhatsApp(),
customer=customer,
remoteJid=sender_phone,
embedding_model="jina-embeddings-v5-text-small",
)
def trip(
*,
trip_id="trip-1",
departure="Aden",
destination="Mukalla",
departure_date="2026-12-01",
departure_time="morning",
available_seats=3,
status="active",
car_type="SUV",
driver_phone_number=None,
registered=True,
):
customers: dict = {"registered": registered}
if driver_phone_number is not None:
customers["phone_number"] = driver_phone_number
drivers = {"name": "Ali", "remoteJid": "967700000009", "customers": customers}
return {
"id": trip_id,
"departure": departure,
"destination": destination,
"departure_date": departure_date,
"departure_time": departure_time,
"available_seats": available_seats,
"total_seats": 4,
"price": "50.00",
"status": status,
"drivers": drivers,
"driver_cars": {"car_type": car_type},
}
@pytest.mark.asyncio
async def test_about_falzh_returns_retrieved_context():
repository = FakeRepository()
repository.info_search_results = [
{
"similarity": 0.91,
"chunk_text": "FALZH creates pending booking leads.",
"source": "prompts/falzh_info.md",
}
]
embeddings = FakeEmbeddings()
handlers = make_handlers(repository=repository, embeddings=embeddings)
result = await handlers.about_falzh({"query": "What is FALZH?", "language": "en"})
assert result.ok is True
assert result.data["answer_context"][0]["text"] == "FALZH creates pending booking leads."
assert embeddings.query_texts == ["What is FALZH?"]
@pytest.mark.asyncio
async def test_about_falzh_no_match_instructs_apology():
handlers = make_handlers()
result = await handlers.about_falzh({"query": "Do you support rewards?", "language": "en"})
assert result.ok is True
assert result.data["sources"] == []
assert "Do not guess" in result.data["reply_instruction"]
@pytest.mark.asyncio
async def test_search_trips_uses_supabase_vector_matches_and_validates_business_rules():
repository = FakeRepository()
repository.trip_vector_search_results = [
trip(trip_id="trip-1", available_seats=2),
trip(trip_id="trip-2", status="cancelled"),
trip(trip_id="trip-3", available_seats=0),
]
embeddings = FakeEmbeddings()
handlers = make_handlers(repository=repository, embeddings=embeddings)
result = await handlers.search_trips(
{"departure": "Aden", "destination": "Mukalla", "seats": 2, "vehicle_type": "SUV"}
)
assert result.ok is True
assert result.data["count"] == 1
assert result.data["matches"][0]["trip_id"] == "trip-1"
assert result.data["matches"][0]["departure_time_type"] == "morning"
assert embeddings.query_texts == ["Aden Mukalla 2 seats SUV"]
assert repository.vector_trip_search_calls[0]["departure"] == "Aden"
assert repository.vector_trip_search_calls[0]["destination"] == "Mukalla"
@pytest.mark.asyncio
async def test_search_trips_filters_requested_datetime_to_departure_bucket():
repository = FakeRepository()
repository.trip_vector_search_results = [
trip(trip_id="trip-1", departure_date="2026-12-02", departure_time="morning"),
trip(trip_id="trip-2", departure_date="2026-12-02", departure_time="night"),
]
handlers = make_handlers(repository=repository)
result = await handlers.search_trips(
{
"departure": "Aden",
"destination": "Mukalla",
"travel_datetime": "2026-12-02T19:30:00+03:00",
}
)
assert result.ok is True
assert result.data["count"] == 1
assert result.data["matches"][0]["trip_id"] == "trip-2"
assert repository.vector_trip_search_calls[0]["departure_time"] == "night"
assert repository.vector_trip_search_calls[0]["requested_time"].hour == 19
@pytest.mark.asyncio
async def test_search_trips_reports_no_matches():
handlers = make_handlers()
result = await handlers.search_trips({"departure": "Aden", "destination": "Sana'a"})
assert result.ok is True
assert result.data["matches"] == []
assert "No active" in result.data["note"]
assert "Do not promise to notify" in result.data["reply_instruction"]
assert "already a general search" in result.data["reply_instruction"]
@pytest.mark.asyncio
async def test_search_trips_no_timed_match_instructs_no_narrow_retry():
handlers = make_handlers()
result = await handlers.search_trips(
{
"departure": "Aden",
"destination": "Sana'a",
"travel_date": "2026-12-01",
"travel_time": "morning",
}
)
assert result.ok is True
assert result.data["matches"] == []
assert "narrower/specific time" in result.data["reply_instruction"]
@pytest.mark.asyncio
async def test_search_trips_requires_at_least_departure_or_destination():
handlers = make_handlers()
result = await handlers.search_trips({"seats": 2})
assert result.ok is False
assert "departure" in (result.error or "")
assert "driver name" in (result.error or "")
@pytest.mark.asyncio
async def test_search_trips_with_departure_only():
repository = FakeRepository()
repository.trip_vector_search_results = [
trip(trip_id="trip-1", departure="Aden", destination="Mukalla"),
trip(trip_id="trip-2", departure="Aden", destination="Taiz"),
]
embeddings = FakeEmbeddings()
handlers = make_handlers(repository=repository, embeddings=embeddings)
result = await handlers.search_trips({"departure": "Aden"})
assert result.ok is True
assert result.data["count"] == 2
assert result.data["matches"][0]["trip_id"] == "trip-1"
assert result.data["matches"][1]["trip_id"] == "trip-2"
assert repository.vector_trip_search_calls[0]["departure"] == "Aden"
assert repository.vector_trip_search_calls[0]["destination"] is None
@pytest.mark.asyncio
async def test_search_trips_with_destination_only():
repository = FakeRepository()
repository.trip_vector_search_results = [
trip(trip_id="trip-1", departure="Aden", destination="Mukalla"),
trip(trip_id="trip-2", departure="Taiz", destination="Mukalla"),
]
embeddings = FakeEmbeddings()
handlers = make_handlers(repository=repository, embeddings=embeddings)
result = await handlers.search_trips({"destination": "Mukalla"})
assert result.ok is True
assert result.data["count"] == 2
assert repository.vector_trip_search_calls[0]["departure"] is None
assert repository.vector_trip_search_calls[0]["destination"] == "Mukalla"
@pytest.mark.asyncio
async def test_search_trips_defaults_seats_to_one():
repository = FakeRepository()
repository.trip_vector_search_results = [
trip(trip_id="trip-1", available_seats=1),
]
handlers = make_handlers(repository=repository)
result = await handlers.search_trips({"departure": "Aden", "destination": "Mukalla"})
assert result.ok is True
assert result.data["count"] == 1
assert repository.vector_trip_search_calls[0]["seats"] == 1
@pytest.mark.asyncio
async def test_search_trips_includes_alternate_alert_when_first_result_is_over_one_hour():
repository = FakeRepository()
repository.trip_vector_search_results = [
{
**trip(trip_id="trip-1", departure_time="morning"),
"time_difference_minutes": 90,
}
]
handlers = make_handlers(repository=repository)
result = await handlers.search_trips(
{
"departure": "Aden",
"destination": "Mukalla",
"travel_time": "morning",
"travel_time_exact": "04:30",
"vector_query_text": "Aden to Mukalla tomorrow morning at 04:30",
}
)
assert result.ok is True
assert result.data["count"] == 1
assert "more than 60 minutes" in result.data["alternate_alert"]
assert result.data["matches"][0]["time_difference_minutes"] == 90
@pytest.mark.asyncio
async def test_select_trip_notifies_driver():
repository = FakeRepository()
repository.trips_by_id["trip-1"] = trip()
whatsapp = FakeWhatsApp()
handlers = make_handlers(
repository=repository,
whatsapp=whatsapp,
customer={"id": "cust-1", "remoteJid": "967700000001", "name": "Mona"},
)
result = await handlers.select_trip(
{"trip_id": "trip-1", "requested_seats": 2, "notes": "Window seat"}
)
assert result.ok is True
assert result.data["driver_notification_status"] == "sent"
assert repository.trip_selections[0]["requested_seats"] == 2
assert whatsapp.sent[0][0] == "967700000009"
@pytest.mark.asyncio
async def test_select_trip_uses_driver_phone_number():
repository = FakeRepository()
repository.trips_by_id["trip-1"] = trip(driver_phone_number="967700000099")
whatsapp = FakeWhatsApp()
handlers = make_handlers(
repository=repository,
whatsapp=whatsapp,
customer={"id": "cust-1", "remoteJid": "967700000001", "name": "Mona"},
)
result = await handlers.select_trip(
{"trip_id": "trip-1", "requested_seats": 1}
)
assert result.ok is True
assert result.data["driver_notification_status"] == "sent"
assert whatsapp.sent[0][0] == "967700000099"
assert result.data["driver_phone"] == "967700000099"
@pytest.mark.asyncio
async def test_select_trip_keeps_pending_when_driver_notification_fails():
repository = FakeRepository()
repository.trips_by_id["trip-1"] = trip()
handlers = make_handlers(
repository=repository,
whatsapp=FakeWhatsApp(fail=True),
)
result = await handlers.select_trip({"trip_id": "trip-1", "requested_seats": 1})
assert result.ok is True
assert result.data["status"] == "pending"
assert result.data["driver_notification_status"] == "failed"
assert repository.notification_updates[0]["status"] == "failed"
@pytest.mark.asyncio
async def test_select_trip_rejects_insufficient_seats():
repository = FakeRepository()
repository.trips_by_id["trip-1"] = trip(available_seats=1)
handlers = make_handlers(repository=repository)
result = await handlers.select_trip({"trip_id": "trip-1", "requested_seats": 2})
assert result.ok is False
assert result.error == "Not enough available seats"
assert repository.trip_selections == []
@pytest.mark.asyncio
async def test_create_driver_account_uses_sender_phone():
repository = FakeRepository()
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.create_driver_account({"name": "Ali Driver"})
assert result.ok is True
assert repository.created_drivers[0]["customers"]["remoteJid"] == "967700000010"
assert result.data["driver_id"] == "driver-1"
assert "own format/style" in result.data["reply_instruction"]
assert "phone numbers" in result.data["reply_instruction"]
@pytest.mark.asyncio
async def test_create_driver_account_rejects_duplicate():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.create_driver_account({"name": "Ali Driver"})
assert result.ok is False
assert "already exists" in (result.error or "")
assert repository.created_drivers == []
@pytest.mark.asyncio
async def test_add_trip_by_driver_requires_account():
handlers = make_handlers(sender_phone="967700000010")
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-06-10",
"departure_time": "morning",
}
)
assert result.ok is False
assert result.data.get("action") == "create_driver_account"
assert "create_driver_account" in (result.error or "")
@pytest.mark.asyncio
async def test_add_driver_car_accepts_name_only():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.add_driver_car({"name": "سيارة"})
assert result.ok is True
assert result.data["name"] == "سيارة"
assert result.data["plate_number"] is None
assert result.data["seat_count"] is None
assert repository.driver_cars_by_driver["driver-1"][0]["car_type"] == "سيارة"
@pytest.mark.asyncio
async def test_check_driver_info_returns_account_summary():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
"status": "active",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "plate_number": "1234", "seat_count": 4}
]
repository.trips_by_id["trip-1"] = {
"id": "trip-1",
"driver_id": "driver-1",
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-12-01",
"departure_time": "morning",
"available_seats": 2,
"total_seats": 4,
"price": "80.00",
"status": "active",
"driver_cars": {"car_type": "SUV"},
"drivers": {"name": "Ali"},
}
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.check_driver_info({})
assert result.ok is True
assert result.data["driver_id"] == "driver-1"
assert result.data["vehicle_count"] == 1
assert result.data["active_trip_count"] == 1
assert result.data["vehicles"][0]["name"] == "SUV"
assert result.data["active_trips"][0]["trip_id"] == "trip-1"
@pytest.mark.asyncio
async def test_check_driver_trips_returns_upcoming_trips():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.trips_by_id["trip-1"] = {
"id": "trip-1",
"driver_id": "driver-1",
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-12-01",
"departure_time": "morning",
"available_seats": 2,
"total_seats": 4,
"price": "80.00",
"status": "active",
"driver_cars": {"car_type": "SUV"},
"drivers": {"name": "Ali"},
}
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.check_driver_trips({})
assert result.ok is True
assert result.data["count"] == 1
assert result.suppress_llm_reply is True
@pytest.mark.asyncio
async def test_check_driver_trips_requests_driver_registration_if_unregistered():
handlers = make_handlers(sender_phone="967700000010")
result = await handlers.check_driver_trips({})
assert result.ok is False
assert result.data["action"] == "create_driver_account"
assert "create_driver_account" in (result.error or "")
@pytest.mark.asyncio
async def test_check_driver_info_requests_driver_registration_if_unregistered():
handlers = make_handlers(sender_phone="967700000010")
result = await handlers.check_driver_info({})
assert result.ok is False
assert result.data["action"] == "create_driver_account"
assert "create_driver_account" in (result.error or "")
@pytest.mark.asyncio
async def test_add_trip_by_driver_uses_latest_trip_defaults():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "seat_count": 4},
]
repository.latest_trips_by_driver["driver-1"] = {
"car_id": "car-1",
"available_seats": 3,
"total_seats": 4,
"price": "75.00",
}
embeddings = FakeEmbeddings()
handlers = make_handlers(
repository=repository,
embeddings=embeddings,
sender_phone="967700000010",
)
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "صنعاء",
"departure_date": "2026-06-10",
"departure_time": "noon",
}
)
assert result.ok is True
assert result.data["trip_id"] == "trip-1"
assert repository.created_trips[0]["car_id"] == "car-1"
assert repository.created_trips[0]["available_seats"] == 3
assert repository.created_trips[0]["price"] == 75.0
assert repository.trip_embeddings[0]["trip_id"] == "trip-1"
assert embeddings.passage_texts == [["Trip trip-1: عدن to صنعاء on 2026-06-10 during noon. Available seats: 3 of 4. Vehicle: SUV. Driver: Ali. Price: 75.0. Status: active."]]
@pytest.mark.asyncio
async def test_add_trip_by_driver_resolves_vehicle_type_by_name():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "باص", "plate_number": "1234", "seat_count": 14},
{"id": "car-2", "car_type": "سيارة", "plate_number": "5678", "seat_count": 4},
]
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-06-10",
"departure_time": "morning",
"vehicle_type": "باص",
"available_seats": 10,
"total_seats": 14,
"price": 120,
}
)
assert result.ok is True
assert repository.created_trips[0]["car_id"] == "car-1"
@pytest.mark.asyncio
async def test_add_trip_by_driver_indexes_new_trip():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "seat_count": 4},
]
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-06-10",
"departure_time": "morning",
"vehicle_type": "SUV",
"available_seats": 2,
"total_seats": 4,
"price": 50,
}
)
assert result.ok is True
assert result.data["indexed"] is True
assert len(repository.trip_embeddings) == 1
@pytest.mark.asyncio
async def test_add_trip_by_driver_creates_trip_without_optional_fields():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "seat_count": 4},
]
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "صنعاء",
"departure_date": "2026-06-10",
"departure_time": "noon",
}
)
assert result.ok is True
assert repository.created_trips[0]["car_id"] == "car-1"
assert repository.created_trips[0]["total_seats"] == 4
assert repository.created_trips[0]["available_seats"] is None
assert repository.created_trips[0]["price"] == 0
@pytest.mark.asyncio
async def test_add_trip_by_driver_stores_driver_message_with_emojis():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "seat_count": 4},
]
current_message = {"message": "باص من عدن الى المكلا 😊😊😊"}
handlers = make_handlers(repository=repository, sender_phone="967700000010")
handlers.current_message = current_message
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-06-10",
"departure_time": "morning",
"vehicle_type": "SUV",
"available_seats": 2,
"total_seats": 4,
"price": 50,
}
)
assert result.ok is True
created_trip = repository.created_trips[-1]
assert created_trip["use_driver_message"] is True
assert created_trip["driver_message"] == "باص من عدن الى المكلا 😊😊😊"
@pytest.mark.asyncio
async def test_add_trip_by_driver_uses_llm_cleaned_message():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "seat_count": 4},
]
raw_msg = "🚐بص فهد مريح 🚐. نازل الجمعة ٧ صباحا 👈تريم المكلا👉 بن بكر 📞770026665 💐بن بكر💐"
cleaned_msg = "🚐بص فهد مريح 🚐. نازل الجمعة ٧ صباحا 👈تريم المكلا👉 💐"
current_message = {"message": raw_msg}
handlers = make_handlers(repository=repository, sender_phone="967700000010")
handlers.current_message = current_message
result = await handlers.add_trip_by_driver(
{
"departure": "تريم",
"destination": "المكلا",
"departure_date": "2026-06-10",
"departure_time": "morning",
"vehicle_type": "SUV",
"available_seats": 2,
"total_seats": 4,
"price": 50,
"driver_message": cleaned_msg,
}
)
assert result.ok is True
created_trip = repository.created_trips[-1]
assert created_trip["use_driver_message"] is True
assert created_trip["driver_message"] == cleaned_msg
@pytest.mark.asyncio
async def test_add_trip_by_driver_uses_cleaned_message_without_many_emojis():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "seat_count": 4},
]
raw_msg = "رحلة يومية من عدن إلى المكلا للتواصل 770026665"
cleaned_msg = "رحلة يومية من عدن إلى المكلا"
handlers = make_handlers(repository=repository, sender_phone="967700000010")
handlers.current_message = {"message": raw_msg}
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-06-10",
"departure_time": "morning",
"vehicle_type": "SUV",
"available_seats": 2,
"total_seats": 4,
"price": 50,
"driver_message": cleaned_msg,
}
)
assert result.ok is True
created_trip = repository.created_trips[-1]
assert created_trip["use_driver_message"] is True
assert created_trip["driver_message"] == cleaned_msg
@pytest.mark.asyncio
async def test_add_trip_by_driver_no_driver_message_with_few_emojis():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "seat_count": 4},
]
current_message = {"message": "باص من عدن الى المكلا 😊"}
handlers = make_handlers(repository=repository, sender_phone="967700000010")
handlers.current_message = current_message
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-06-10",
"departure_time": "morning",
"vehicle_type": "SUV",
"available_seats": 2,
"total_seats": 4,
"price": 50,
}
)
assert result.ok is True
created_trip = repository.created_trips[-1]
assert created_trip["use_driver_message"] is False
assert created_trip["driver_message"] is None
@pytest.mark.asyncio
async def test_add_trip_by_driver_no_current_message():
repository = FakeRepository()
repository.drivers_by_remote_jid["967700000010"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000010",
}
repository.driver_cars_by_driver["driver-1"] = [
{"id": "car-1", "car_type": "SUV", "seat_count": 4},
]
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.add_trip_by_driver(
{
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-06-10",
"departure_time": "morning",
"vehicle_type": "SUV",
"available_seats": 2,
"total_seats": 4,
"price": 50,
}
)
assert result.ok is True
created_trip = repository.created_trips[-1]
assert created_trip["use_driver_message"] is False
assert created_trip["driver_message"] is None
@pytest.mark.asyncio
async def test_switch_to_driver_requires_existing_driver_account():
repository = FakeRepository()
customer = {"id": "cust-1", "remoteJid": "967700000001"}
handlers = make_handlers(repository=repository, customer=customer)
result = await handlers.switch_to_driver({})
assert result.ok is False
assert result.data["action"] == "create_driver_account"
@pytest.mark.asyncio
async def test_switch_to_driver_updates_customer_mode():
repository = FakeRepository()
customer = await repository.upsert_customer(remote_jid="967700000001")
repository.drivers_by_remote_jid["967700000001"] = {
"id": "driver-1",
"name": "Ali",
"remoteJid": "967700000001",
}
handlers = make_handlers(repository=repository, customer=customer)
result = await handlers.switch_to_driver({})
assert result.ok is True
assert result.data["user_mode"] == "driver"
assert customer["user_mode"] == "driver"
@pytest.mark.asyncio
async def test_switch_to_passenger_updates_mode_and_optional_name():
repository = FakeRepository()
customer = await repository.upsert_customer(remote_jid="967700000001")
handlers = make_handlers(repository=repository, customer=customer)
result = await handlers.switch_to_passenger({"name": "Sara"})
assert result.ok is True
assert result.data["user_mode"] == "passenger"
assert customer["user_mode"] == "passenger"
assert customer["name"] == "Sara"
@pytest.mark.asyncio
async def test_switch_to_passenger_without_name():
repository = FakeRepository()
customer = await repository.upsert_customer(remote_jid="967700000001", name="Existing")
handlers = make_handlers(repository=repository, customer=customer)
result = await handlers.switch_to_passenger({})
assert result.ok is True
assert customer["user_mode"] == "passenger"
assert customer["name"] == "Existing"
def _driver_setup(repository: FakeRepository, *, phone: str = "967700000010") -> None:
repository.drivers_by_remote_jid[phone] = {
"id": "driver-1",
"name": "Ali",
"phone_number": phone,
}
repository.trips_by_id["trip-1"] = {
"id": "trip-1",
"driver_id": "driver-1",
"departure": "عدن",
"destination": "المكلا",
"departure_date": "2026-12-01",
"departure_time": "morning",
"available_seats": 2,
"total_seats": 4,
"price": "80.00",
"status": "active",
"driver_cars": {"car_type": "SUV"},
"drivers": {"name": "Ali"},
}
@pytest.mark.asyncio
async def test_initiate_trip_action_sends_trip_cards():
repository = FakeRepository()
whatsapp = FakeWhatsApp()
_driver_setup(repository)
handlers = make_handlers(
repository=repository,
whatsapp=whatsapp,
sender_phone="967700000010",
)
result = await handlers.initiate_trip_action({"action_type": "DELETE"})
assert result.ok is True
assert result.data["count"] == 1
assert result.suppress_llm_reply is True
assert len(whatsapp.sent) == 2 # card + prompt
card_text = whatsapp.sent[0][1]
assert "عدن" in card_text
assert "المكلا" in card_text
@pytest.mark.asyncio
async def test_initiate_trip_action_returns_no_trips_message():
repository = FakeRepository()
_driver_setup(repository)
repository.trips_by_id.clear()
handlers = make_handlers(repository=repository, sender_phone="967700000010")
result = await handlers.initiate_trip_action({"action_type": "MODIFY"})
assert result.ok is True
assert result.data["count"] == 0
assert "No trips found" in result.data["message"]
@pytest.mark.asyncio
async def test_update_trip_field_uses_active_session_and_clears_it():
repository = FakeRepository()
customer = await repository.upsert_customer(phone_number="967700000010")
await repository.set_customer_session_field(
customer_id=customer["id"],
key="active_edit_trip_id",
value="trip-1",
)
_driver_setup(repository)
handlers = make_handlers(
repository=repository,
customer=customer,
sender_phone="967700000010",
)
result = await handlers.update_trip_field(
{"field": "pickup_time", "value": "15:00"},
)
assert result.ok is True
assert repository.trips_by_id["trip-1"]["departure_time"] == "noon"
assert await repository.get_customer_session(customer["id"]) == {}
assert len(repository.trip_embeddings) == 1