# app/modules/tenancy/tests/integration/test_merchant_routes.py """ Integration tests for merchant portal tenancy API routes. Tests the merchant portal endpoints at: /api/v1/merchants/account/* Authentication: Overrides get_merchant_for_current_user and get_current_merchant_api with mocks that return the test merchant/user. """ import uuid import pytest from app.api.deps import get_current_merchant_api, get_merchant_for_current_user from app.modules.tenancy.models import Merchant, Store, User from main import app from models.schema.auth import UserContext # ============================================================================ # Fixtures # ============================================================================ BASE = "/api/v1/merchants/account" @pytest.fixture def mt_owner(db): """Create a merchant owner user.""" from middleware.auth import AuthManager auth = AuthManager() uid = uuid.uuid4().hex[:8] user = User( email=f"mtowner_{uid}@test.com", username=f"mtowner_{uid}", hashed_password=auth.hash_password("mtpass123"), role="store", is_active=True, ) db.add(user) db.commit() db.refresh(user) return user @pytest.fixture def mt_merchant(db, mt_owner): """Create a merchant owned by mt_owner.""" merchant = Merchant( name="Merchant Portal Test", description="A test merchant for portal routes", owner_user_id=mt_owner.id, contact_email=mt_owner.email, contact_phone="+352 123 456", website="https://example.com", business_address="1 Rue Test, Luxembourg", tax_number="LU12345678", is_active=True, is_verified=True, ) db.add(merchant) db.commit() db.refresh(merchant) return merchant @pytest.fixture def mt_stores(db, mt_merchant): """Create stores for the merchant.""" stores = [] for i in range(3): uid = uuid.uuid4().hex[:8] store = Store( merchant_id=mt_merchant.id, store_code=f"MT_{uid.upper()}", subdomain=f"mtstore{uid}", name=f"MT Store {i}", is_active=i < 2, # Third store inactive is_verified=True, ) db.add(store) # noqa: PERF006 stores.append(store) db.commit() for s in stores: db.refresh(s) return stores @pytest.fixture def mt_auth(mt_owner, mt_merchant): """Override auth dependencies to return the test merchant/user.""" user_context = UserContext( id=mt_owner.id, email=mt_owner.email, username=mt_owner.username, role="store", is_active=True, ) def _override_merchant(): return mt_merchant def _override_user(): return user_context app.dependency_overrides[get_merchant_for_current_user] = _override_merchant app.dependency_overrides[get_current_merchant_api] = _override_user yield {"Authorization": "Bearer fake-token"} app.dependency_overrides.pop(get_merchant_for_current_user, None) app.dependency_overrides.pop(get_current_merchant_api, None) # ============================================================================ # Store Endpoints # ============================================================================ class TestMerchantStoresList: """Tests for GET /api/v1/merchants/account/stores.""" def test_list_stores_success(self, client, mt_auth, mt_stores): response = client.get(f"{BASE}/stores", headers=mt_auth) assert response.status_code == 200 data = response.json() assert "stores" in data assert "total" in data assert data["total"] == 3 def test_list_stores_response_shape(self, client, mt_auth, mt_stores): response = client.get(f"{BASE}/stores", headers=mt_auth) assert response.status_code == 200 store = response.json()["stores"][0] assert "id" in store assert "name" in store assert "store_code" in store assert "is_active" in store assert "subdomain" in store def test_list_stores_pagination(self, client, mt_auth, mt_stores): response = client.get( f"{BASE}/stores", params={"skip": 0, "limit": 2}, headers=mt_auth, ) assert response.status_code == 200 data = response.json() assert len(data["stores"]) == 2 assert data["total"] == 3 assert data["skip"] == 0 assert data["limit"] == 2 def test_list_stores_empty(self, client, mt_auth, mt_merchant): response = client.get(f"{BASE}/stores", headers=mt_auth) assert response.status_code == 200 data = response.json() assert data["stores"] == [] assert data["total"] == 0 # ============================================================================ # Profile Endpoints # ============================================================================ class TestMerchantProfile: """Tests for GET/PUT /api/v1/merchants/account/profile.""" def test_get_profile_success(self, client, mt_auth, mt_merchant): response = client.get(f"{BASE}/profile", headers=mt_auth) assert response.status_code == 200 data = response.json() assert data["id"] == mt_merchant.id assert data["name"] == "Merchant Portal Test" assert data["description"] == "A test merchant for portal routes" assert data["contact_email"] == mt_merchant.contact_email assert data["contact_phone"] == "+352 123 456" assert data["website"] == "https://example.com" assert data["business_address"] == "1 Rue Test, Luxembourg" assert data["tax_number"] == "LU12345678" assert data["is_verified"] is True def test_get_profile_all_fields_present(self, client, mt_auth, mt_merchant): response = client.get(f"{BASE}/profile", headers=mt_auth) assert response.status_code == 200 data = response.json() expected_fields = { "id", "name", "description", "contact_email", "contact_phone", "website", "business_address", "tax_number", "is_verified", } assert expected_fields.issubset(set(data.keys())) def test_update_profile_partial(self, client, mt_auth, mt_merchant): response = client.put( f"{BASE}/profile", json={"name": "Updated Merchant Name"}, headers=mt_auth, ) assert response.status_code == 200 data = response.json() assert data["name"] == "Updated Merchant Name" # Other fields should remain unchanged assert data["contact_phone"] == "+352 123 456" def test_update_profile_email_validation(self, client, mt_auth, mt_merchant): response = client.put( f"{BASE}/profile", json={"contact_email": "not-an-email"}, headers=mt_auth, ) assert response.status_code == 422 def test_update_profile_cannot_set_admin_fields(self, client, mt_auth, mt_merchant): """Admin-only fields (is_active, is_verified) are not accepted.""" response = client.put( f"{BASE}/profile", json={"is_active": False, "is_verified": False}, headers=mt_auth, ) # Should succeed but ignore unknown fields (Pydantic extra="ignore" by default) assert response.status_code == 200 data = response.json() # is_verified should remain True (not changed) assert data["is_verified"] is True def test_update_profile_name_too_short(self, client, mt_auth, mt_merchant): response = client.put( f"{BASE}/profile", json={"name": "A"}, headers=mt_auth, ) assert response.status_code == 422