diff --git a/tests/conftest.py b/tests/conftest.py index 4a70e041..e84c0741 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -128,5 +128,6 @@ pytest_plugins = [ "tests.fixtures.vendor_fixtures", "tests.fixtures.customer_fixtures", "tests.fixtures.marketplace_import_job_fixtures", + "tests.fixtures.message_fixtures", "tests.fixtures.testing_fixtures", ] diff --git a/tests/unit/services/test_admin_customer_service.py b/tests/unit/services/test_admin_customer_service.py new file mode 100644 index 00000000..0dd4ec76 --- /dev/null +++ b/tests/unit/services/test_admin_customer_service.py @@ -0,0 +1,280 @@ +# tests/unit/services/test_admin_customer_service.py +""" +Unit tests for AdminCustomerService. +""" + +from decimal import Decimal + +import pytest + +from app.exceptions.customer import CustomerNotFoundException +from app.services.admin_customer_service import AdminCustomerService +from models.database.customer import Customer + + +@pytest.fixture +def admin_customer_service(): + """Create AdminCustomerService instance.""" + return AdminCustomerService() + + +@pytest.fixture +def customer_with_orders(db, test_vendor, test_customer): + """Create a customer with order data.""" + test_customer.total_orders = 5 + test_customer.total_spent = Decimal("250.00") + db.commit() + db.refresh(test_customer) + return test_customer + + +@pytest.fixture +def multiple_customers(db, test_vendor): + """Create multiple customers for testing.""" + customers = [] + for i in range(5): + customer = Customer( + vendor_id=test_vendor.id, + email=f"customer{i}@example.com", + hashed_password="hashed_password_placeholder", + first_name=f"First{i}", + last_name=f"Last{i}", + customer_number=f"CUST-00{i}", + is_active=(i % 2 == 0), # Alternate active/inactive + total_orders=i, + total_spent=Decimal(str(i * 100)), + ) + db.add(customer) + customers.append(customer) + + db.commit() + for c in customers: + db.refresh(c) + + return customers + + +@pytest.mark.unit +class TestAdminCustomerServiceList: + """Tests for list_customers method.""" + + def test_list_customers_empty(self, db, admin_customer_service, test_vendor): + """Test listing customers when none exist.""" + customers, total = admin_customer_service.list_customers(db) + + assert customers == [] + assert total == 0 + + def test_list_customers_basic(self, db, admin_customer_service, test_customer): + """Test basic customer listing.""" + customers, total = admin_customer_service.list_customers(db) + + assert total == 1 + assert len(customers) == 1 + assert customers[0]["id"] == test_customer.id + assert customers[0]["email"] == test_customer.email + + def test_list_customers_with_vendor_info( + self, db, admin_customer_service, test_customer, test_vendor + ): + """Test that vendor info is included.""" + customers, total = admin_customer_service.list_customers(db) + + assert customers[0]["vendor_name"] == test_vendor.name + assert customers[0]["vendor_code"] == test_vendor.vendor_code + + def test_list_customers_filter_by_vendor( + self, db, admin_customer_service, multiple_customers, test_vendor + ): + """Test filtering by vendor ID.""" + customers, total = admin_customer_service.list_customers( + db, vendor_id=test_vendor.id + ) + + assert total == 5 + for customer in customers: + assert customer["vendor_id"] == test_vendor.id + + def test_list_customers_filter_by_active_status( + self, db, admin_customer_service, multiple_customers + ): + """Test filtering by active status.""" + # Get active customers (0, 2, 4 = 3 customers) + customers, total = admin_customer_service.list_customers(db, is_active=True) + assert total == 3 + + # Get inactive customers (1, 3 = 2 customers) + customers, total = admin_customer_service.list_customers(db, is_active=False) + assert total == 2 + + def test_list_customers_search_by_email( + self, db, admin_customer_service, multiple_customers + ): + """Test searching by email.""" + customers, total = admin_customer_service.list_customers( + db, search="customer2@" + ) + + assert total == 1 + assert customers[0]["email"] == "customer2@example.com" + + def test_list_customers_search_by_name( + self, db, admin_customer_service, multiple_customers + ): + """Test searching by name.""" + customers, total = admin_customer_service.list_customers(db, search="First3") + + assert total == 1 + assert customers[0]["first_name"] == "First3" + + def test_list_customers_search_by_customer_number( + self, db, admin_customer_service, multiple_customers + ): + """Test searching by customer number.""" + customers, total = admin_customer_service.list_customers(db, search="CUST-001") + + assert total == 1 + assert customers[0]["customer_number"] == "CUST-001" + + def test_list_customers_pagination( + self, db, admin_customer_service, multiple_customers + ): + """Test pagination.""" + # Get first page + customers, total = admin_customer_service.list_customers(db, skip=0, limit=2) + assert len(customers) == 2 + assert total == 5 + + # Get second page + customers, total = admin_customer_service.list_customers(db, skip=2, limit=2) + assert len(customers) == 2 + + # Get last page + customers, total = admin_customer_service.list_customers(db, skip=4, limit=2) + assert len(customers) == 1 + + +@pytest.mark.unit +class TestAdminCustomerServiceStats: + """Tests for get_customer_stats method.""" + + def test_get_customer_stats_empty(self, db, admin_customer_service, test_vendor): + """Test stats when no customers exist.""" + stats = admin_customer_service.get_customer_stats(db) + + assert stats["total"] == 0 + assert stats["active"] == 0 + assert stats["inactive"] == 0 + assert stats["with_orders"] == 0 + assert stats["total_spent"] == 0 + assert stats["total_orders"] == 0 + assert stats["avg_order_value"] == 0 + + def test_get_customer_stats_with_data( + self, db, admin_customer_service, multiple_customers + ): + """Test stats with customer data.""" + stats = admin_customer_service.get_customer_stats(db) + + assert stats["total"] == 5 + assert stats["active"] == 3 # 0, 2, 4 + assert stats["inactive"] == 2 # 1, 3 + # with_orders = customers with total_orders > 0 (1, 2, 3, 4 = 4 customers) + assert stats["with_orders"] == 4 + # total_spent = 0 + 100 + 200 + 300 + 400 = 1000 + assert stats["total_spent"] == 1000.0 + # total_orders = 0 + 1 + 2 + 3 + 4 = 10 + assert stats["total_orders"] == 10 + + def test_get_customer_stats_by_vendor( + self, db, admin_customer_service, test_customer, test_vendor + ): + """Test stats filtered by vendor.""" + stats = admin_customer_service.get_customer_stats(db, vendor_id=test_vendor.id) + + assert stats["total"] == 1 + + def test_get_customer_stats_avg_order_value( + self, db, admin_customer_service, customer_with_orders + ): + """Test average order value calculation.""" + stats = admin_customer_service.get_customer_stats(db) + + # total_spent = 250, total_orders = 5 + # avg = 250 / 5 = 50 + assert stats["avg_order_value"] == 50.0 + + +@pytest.mark.unit +class TestAdminCustomerServiceGetCustomer: + """Tests for get_customer method.""" + + def test_get_customer_success(self, db, admin_customer_service, test_customer): + """Test getting customer by ID.""" + customer = admin_customer_service.get_customer(db, test_customer.id) + + assert customer["id"] == test_customer.id + assert customer["email"] == test_customer.email + assert customer["first_name"] == test_customer.first_name + assert customer["last_name"] == test_customer.last_name + + def test_get_customer_with_vendor_info( + self, db, admin_customer_service, test_customer, test_vendor + ): + """Test vendor info in customer detail.""" + customer = admin_customer_service.get_customer(db, test_customer.id) + + assert customer["vendor_name"] == test_vendor.name + assert customer["vendor_code"] == test_vendor.vendor_code + + def test_get_customer_not_found(self, db, admin_customer_service): + """Test error when customer not found.""" + with pytest.raises(CustomerNotFoundException): + admin_customer_service.get_customer(db, 99999) + + +@pytest.mark.unit +class TestAdminCustomerServiceToggleStatus: + """Tests for toggle_customer_status method.""" + + def test_toggle_status_activate( + self, db, admin_customer_service, test_customer, test_admin + ): + """Test activating an inactive customer.""" + # Make customer inactive first + test_customer.is_active = False + db.commit() + + result = admin_customer_service.toggle_customer_status( + db, test_customer.id, test_admin.email + ) + db.commit() + + assert result["id"] == test_customer.id + assert result["is_active"] is True + assert "activated" in result["message"] + + def test_toggle_status_deactivate( + self, db, admin_customer_service, test_customer, test_admin + ): + """Test deactivating an active customer.""" + test_customer.is_active = True + db.commit() + + result = admin_customer_service.toggle_customer_status( + db, test_customer.id, test_admin.email + ) + db.commit() + + assert result["id"] == test_customer.id + assert result["is_active"] is False + assert "deactivated" in result["message"] + + def test_toggle_status_not_found( + self, db, admin_customer_service, test_admin + ): + """Test error when customer not found.""" + with pytest.raises(CustomerNotFoundException): + admin_customer_service.toggle_customer_status( + db, 99999, test_admin.email + )