import pytest from unittest.mock import AsyncMock, patch from fastapi import HTTPException from datetime import datetime, timezone, date, time from uuid import uuid4 from app.services.appointment import ( create_new_appointment, reschedule_appointment, cancel_appointment_service, get_appointment_details, get_appointments_by_customer_id, ) from app.models.appointment import Appointment, AppointmentStatus, PaymentMode, PaymentStatus from app.repositories.appointment import ( create_appointment, update_appointment, cancel_appointment, get_appointment_by_id, fetch_appointments_from_db, ) # Mock data MOCK_APPOINTMENT_ID = str(uuid4()) MOCK_CUSTOMER_ID = str(uuid4()) MOCK_MERCHANT_ID = str(uuid4()) MOCK_ORDER_ID = "order_123" # Test data TEST_APPOINTMENT = Appointment( appointment_id=MOCK_APPOINTMENT_ID, customer_id=MOCK_CUSTOMER_ID, merchant_id=MOCK_MERCHANT_ID, merchant_name="Test Merchant", city="Test City", merchant_address={"street": "123 Test St", "city": "Test City"}, location_id="LOC001", appointment_date="2023-12-25", appointment_time="10:00", associates=[{"associate_id": "STAFF1", "name": "John Doe"}], status=AppointmentStatus.PENDING, services=[{"service_id": "SERV1", "name": "Test Service", "price": 100.0, "duration": "30 minutes", "quantity": 1}], total_amount=100.0, payment_mode=PaymentMode.ONLINE, order_id=MOCK_ORDER_ID, ) # Mock functions @pytest.fixture def mock_create_appointment(): with patch("app.repositories.appointment.create_appointment", new_callable=AsyncMock) as mock: yield mock @pytest.fixture def mock_get_order_by_id(): with patch("app.repositories.payment.get_order_by_id", new_callable=AsyncMock) as mock: mock.return_value = {"status": "pending"} yield mock @pytest.fixture def mock_get_appointment_by_id(): with patch("app.repositories.appointment.get_appointment_by_id", new_callable=AsyncMock) as mock: yield mock @pytest.fixture def mock_update_appointment(): with patch("app.repositories.appointment.update_appointment", new_callable=AsyncMock) as mock: yield mock @pytest.fixture def mock_cancel_appointment(): with patch("app.repositories.appointment.cancel_appointment", new_callable=AsyncMock) as mock: yield mock @pytest.fixture def mock_fetch_appointments_from_db(): with patch("app.repositories.appointment.fetch_appointments_from_db", new_callable=AsyncMock) as mock: yield mock # Tests @pytest.mark.asyncio async def test_create_new_appointment_success(mock_create_appointment, mock_get_order_by_id): result = await create_new_appointment(TEST_APPOINTMENT) assert result["appointment_id"] == MOCK_APPOINTMENT_ID mock_create_appointment.assert_called_once() mock_get_order_by_id.assert_called_once_with(MOCK_ORDER_ID) @pytest.mark.asyncio async def test_create_new_appointment_invalid_order(mock_get_order_by_id): mock_get_order_by_id.return_value = None with pytest.raises(HTTPException) as exc_info: await create_new_appointment(TEST_APPOINTMENT) assert exc_info.value.status_code == 400 assert "Invalid Razorpay Order ID" in str(exc_info.value.detail) @pytest.mark.asyncio async def test_reschedule_appointment_success(mock_get_appointment_by_id, mock_update_appointment): mock_get_appointment_by_id.return_value = { "appointment_id": MOCK_APPOINTMENT_ID, "status": AppointmentStatus.CONFIRMED.value, "appointment_date": "2023-12-25", "appointment_time": "10:00:00", } result = await reschedule_appointment(MOCK_APPOINTMENT_ID, "2023-12-26", "11:00:00", MOCK_CUSTOMER_ID) assert result["message"] == "Appointment rescheduled successfully" mock_update_appointment.assert_called_once() @pytest.mark.asyncio async def test_reschedule_appointment_past_date(mock_get_appointment_by_id): mock_get_appointment_by_id.return_value = { "appointment_id": MOCK_APPOINTMENT_ID, "status": AppointmentStatus.CONFIRMED.value, "appointment_date": "2023-12-25", "appointment_time": "10:00:00", } with pytest.raises(HTTPException) as exc_info: await reschedule_appointment(MOCK_APPOINTMENT_ID, "2023-12-24", "11:00:00", MOCK_CUSTOMER_ID) assert exc_info.value.status_code == 400 assert "Cannot reschedule to a past time" in str(exc_info.value.detail) @pytest.mark.asyncio async def test_cancel_appointment_service_success(mock_get_appointment_by_id, mock_cancel_appointment): mock_get_appointment_by_id.return_value = { "appointment_id": MOCK_APPOINTMENT_ID, "status": AppointmentStatus.CONFIRMED.value, "appointment_date": "2023-12-25", "appointment_time": "10:00:00", } result = await cancel_appointment_service(MOCK_APPOINTMENT_ID, "change_of_plans", MOCK_CUSTOMER_ID) assert result["message"] == "Appointment canceled successfully" mock_cancel_appointment.assert_called_once() @pytest.mark.asyncio async def test_cancel_appointment_service_already_canceled(mock_get_appointment_by_id): mock_get_appointment_by_id.return_value = { "appointment_id": MOCK_APPOINTMENT_ID, "status": AppointmentStatus.CANCELED.value, "appointment_date": "2023-12-25", "appointment_time": "10:00:00", } with pytest.raises(HTTPException) as exc_info: await cancel_appointment_service(MOCK_APPOINTMENT_ID, "change_of_plans", MOCK_CUSTOMER_ID) assert exc_info.value.status_code == 400 assert "Appointment is already canceled" in str(exc_info.value.detail) @pytest.mark.asyncio async def test_get_appointment_details_success(mock_get_appointment_by_id): mock_get_appointment_by_id.return_value = { "appointment_id": MOCK_APPOINTMENT_ID, "status": AppointmentStatus.CONFIRMED.value, "appointment_date": "2023-12-25", "appointment_time": "10:00:00", } result = await get_appointment_details(MOCK_APPOINTMENT_ID) assert result["appointment_id"] == MOCK_APPOINTMENT_ID @pytest.mark.asyncio async def test_get_appointments_by_customer_id_success(mock_fetch_appointments_from_db): mock_fetch_appointments_from_db.return_value = ( [ { "appointment_id": MOCK_APPOINTMENT_ID, "merchant_id": MOCK_MERCHANT_ID, "merchant_name": "Test Merchant", "merchant_address": "123 Test St", "location_id": str(uuid4()), "appointment_date": date(2023, 12, 25), "appointment_time": "10:00:00", "status": "confirmed", "associates": [], "services": [], "notes": "", "total_amount": 100.0, "discount": 0.0, "payment_status": "Paid", "payment_id": "pay_123", "cleared_amount": 100.0, #"created_at": datetime(2023, 12, 1, 10, 0, 0), #"modified_at": datetime(2023, 12, 1, 10, 0, 0), } ], 1, ) result = await get_appointments_by_customer_id(MOCK_CUSTOMER_ID) assert result.customer_id == MOCK_CUSTOMER_ID assert len(result.appointments) == 1 assert result.appointments[0].appointment_id == MOCK_APPOINTMENT_ID