# app/modules/loyalty/tests/integration/test_admin_pages.py """ Integration tests for admin loyalty page routes (HTML rendering). Tests the admin page routes at: /loyalty/programs /loyalty/analytics /loyalty/merchants/{merchant_id} /loyalty/merchants/{merchant_id}/program /loyalty/merchants/{merchant_id}/settings Authentication: Uses super_admin_headers fixture (real JWT login). """ import uuid import pytest from app.modules.tenancy.models import Merchant, User BASE = "/admin/loyalty" # ============================================================================ # Fixtures # ============================================================================ @pytest.fixture def admin_merchant(db): """Create a merchant for admin page tests.""" from middleware.auth import AuthManager auth = AuthManager() uid = uuid.uuid4().hex[:8] owner = User( email=f"pagemerchowner_{uid}@test.com", username=f"pagemerchowner_{uid}", hashed_password=auth.hash_password("testpass123"), role="merchant_owner", is_active=True, is_email_verified=True, ) db.add(owner) db.commit() db.refresh(owner) merchant = Merchant( name=f"Page Test Merchant {uid}", owner_user_id=owner.id, contact_email=owner.email, is_active=True, is_verified=True, ) db.add(merchant) db.commit() db.refresh(merchant) return merchant # ============================================================================ # Programs Dashboard Page # ============================================================================ @pytest.mark.integration @pytest.mark.loyalty class TestAdminProgramsPage: """Tests for GET /loyalty/programs.""" def test_programs_page_renders(self, client, super_admin_headers): """Programs dashboard returns HTML.""" response = client.get( f"{BASE}/programs", headers=super_admin_headers, ) assert response.status_code == 200 assert "text/html" in response.headers["content-type"] def test_programs_page_requires_auth(self, client): """Unauthenticated request is rejected.""" response = client.get(f"{BASE}/programs") assert response.status_code in [401, 403] # ============================================================================ # Analytics Page # ============================================================================ @pytest.mark.integration @pytest.mark.loyalty class TestAdminAnalyticsPage: """Tests for GET /loyalty/analytics.""" def test_analytics_page_renders(self, client, super_admin_headers): """Analytics page returns HTML.""" response = client.get( f"{BASE}/analytics", headers=super_admin_headers, ) assert response.status_code == 200 assert "text/html" in response.headers["content-type"] def test_analytics_page_requires_auth(self, client): """Unauthenticated request is rejected.""" response = client.get(f"{BASE}/analytics") assert response.status_code in [401, 403] # ============================================================================ # Merchant Detail Page # ============================================================================ @pytest.mark.integration @pytest.mark.loyalty class TestAdminMerchantDetailPage: """Tests for GET /loyalty/merchants/{merchant_id}.""" def test_merchant_detail_page_renders( self, client, super_admin_headers, admin_merchant ): """Merchant detail page returns HTML with valid merchant.""" response = client.get( f"{BASE}/merchants/{admin_merchant.id}", headers=super_admin_headers, ) assert response.status_code == 200 assert "text/html" in response.headers["content-type"] def test_merchant_detail_page_requires_auth(self, client, admin_merchant): """Unauthenticated request is rejected.""" response = client.get(f"{BASE}/merchants/{admin_merchant.id}") assert response.status_code in [401, 403] # ============================================================================ # Program Edit Page (NEW — the uncommitted route) # ============================================================================ @pytest.mark.integration @pytest.mark.loyalty class TestAdminProgramEditPage: """Tests for GET /loyalty/merchants/{merchant_id}/program.""" def test_program_edit_page_renders( self, client, super_admin_headers, admin_merchant ): """Program edit page returns HTML.""" response = client.get( f"{BASE}/merchants/{admin_merchant.id}/program", headers=super_admin_headers, ) assert response.status_code == 200 assert "text/html" in response.headers["content-type"] def test_program_edit_page_passes_merchant_id( self, client, super_admin_headers, admin_merchant ): """Page response contains the merchant_id for the Alpine component.""" response = client.get( f"{BASE}/merchants/{admin_merchant.id}/program", headers=super_admin_headers, ) assert response.status_code == 200 # Template should include merchant_id so JS can use it assert str(admin_merchant.id) in response.text def test_program_edit_page_requires_auth(self, client, admin_merchant): """Unauthenticated request is rejected.""" response = client.get( f"{BASE}/merchants/{admin_merchant.id}/program" ) assert response.status_code in [401, 403] def test_program_edit_page_without_existing_program( self, client, super_admin_headers, admin_merchant ): """Page renders even when merchant has no program yet (create mode).""" # admin_merchant has no program — page should still render response = client.get( f"{BASE}/merchants/{admin_merchant.id}/program", headers=super_admin_headers, ) assert response.status_code == 200 assert "text/html" in response.headers["content-type"] # ============================================================================ # Merchant Settings Page # ============================================================================ @pytest.mark.integration @pytest.mark.loyalty class TestAdminMerchantSettingsPage: """Tests for GET /loyalty/merchants/{merchant_id}/settings.""" def test_settings_page_renders( self, client, super_admin_headers, admin_merchant ): """Merchant settings page returns HTML.""" response = client.get( f"{BASE}/merchants/{admin_merchant.id}/settings", headers=super_admin_headers, ) assert response.status_code == 200 assert "text/html" in response.headers["content-type"] def test_settings_page_requires_auth(self, client, admin_merchant): """Unauthenticated request is rejected.""" response = client.get( f"{BASE}/merchants/{admin_merchant.id}/settings" ) assert response.status_code in [401, 403]