| 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_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 |
|
|