"""Tests for competitors API.""" import uuid import pytest import pytest_asyncio from httpx import AsyncClient, ASGITransport from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession from app.database import Base from app.main import app from app.models.user import User from app.models.brand import Brand from app.models.competitor import Competitor from app.api.deps import get_current_user, get_db from app.services.auth import create_access_token from tests.fixtures.auth import _to_uuid @pytest_asyncio.fixture async def async_engine(): """Create async engine for testing with SQLite.""" from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.pool import StaticPool engine = create_async_engine( "sqlite+aiosqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine await engine.dispose() @pytest_asyncio.fixture async def async_session(async_engine): """Create async session for testing.""" async_session_maker = async_sessionmaker( async_engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, autocommit=False, ) async with async_session_maker() as session: yield session @pytest_asyncio.fixture async def test_user(async_session): """Create a test user.""" user = User( id=str(uuid.uuid4()), email="test@example.com", password="hashed_password", firstName="Test User", plan="free", max_queries=5, isActive=True, emailVerified=True, ) async_session.add(user) await async_session.commit() await async_session.refresh(user) return user @pytest_asyncio.fixture async def test_brand(async_session, test_user): """Create a test brand.""" brand = Brand( id=uuid.uuid4(), user_id=_to_uuid(test_user.id), name="Test Brand", aliases=["TestBrand", "TB"], website="https://testbrand.com", industry="technology", platforms=["wenxin", "kimi"], frequency="weekly", status="active", ) async_session.add(brand) await async_session.commit() await async_session.refresh(brand) return brand @pytest_asyncio.fixture async def test_competitor(async_session, test_brand): """Create a test competitor.""" competitor = Competitor( id=uuid.uuid4(), brand_id=test_brand.id, name="Test Competitor", aliases=["CompetitorA", "CA"], ) async_session.add(competitor) await async_session.commit() await async_session.refresh(competitor) return competitor @pytest_asyncio.fixture async def async_client(async_session, test_user): """Create async client for API testing.""" async def override_get_db(): yield async_session async def override_get_current_user(): return test_user app.dependency_overrides[get_db] = override_get_db app.dependency_overrides[get_current_user] = override_get_current_user transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: yield client app.dependency_overrides.clear() @pytest.fixture def auth_headers(test_user): """Create authentication headers.""" token = create_access_token(data={"sub": str(test_user.id)}) return {"Authorization": f"Bearer {token}"} class TestCompetitorsAPI: """Test cases for competitors API.""" @pytest.mark.asyncio async def test_get_competitors_empty(self, async_client, test_brand): """Test getting empty competitor list.""" response = await async_client.get( f"/api/v1/brands/{test_brand.id}/competitors/" ) assert response.status_code == 200 data = response.json() assert data["items"] == [] assert data["total"] == 0 @pytest.mark.asyncio async def test_create_competitor(self, async_client, test_brand): """Test creating a new competitor.""" competitor_data = { "name": "New Competitor", "aliases": ["CompetitorB", "CB"], } response = await async_client.post( f"/api/v1/brands/{test_brand.id}/competitors/", json=competitor_data ) assert response.status_code == 201 data = response.json() assert data["name"] == "New Competitor" assert data["aliases"] == ["CompetitorB", "CB"] assert data["brand_id"] == str(test_brand.id) @pytest.mark.asyncio async def test_delete_competitor(self, async_client, test_brand, test_competitor): """Test deleting a competitor.""" response = await async_client.delete( f"/api/v1/brands/{test_brand.id}/competitors/{test_competitor.id}/" ) assert response.status_code == 204 # Verify competitor is deleted response = await async_client.get( f"/api/v1/brands/{test_brand.id}/competitors/" ) assert response.status_code == 200 data = response.json() assert data["total"] == 0 @pytest.mark.asyncio async def test_list_competitors(self, async_client, async_session, test_brand): """Test listing multiple competitors.""" # Create multiple competitors for i in range(3): competitor = Competitor( brand_id=test_brand.id, name=f"Competitor {i}", aliases=[f"C{i}"], ) async_session.add(competitor) await async_session.commit() response = await async_client.get( f"/api/v1/brands/{test_brand.id}/competitors/" ) assert response.status_code == 200 data = response.json() assert len(data["items"]) == 3 assert data["total"] == 3 @pytest.mark.asyncio async def test_competitor_brand_not_found(self, async_client): """Test accessing competitors of non-existent brand.""" non_existent_brand_id = uuid.uuid4() response = await async_client.get( f"/api/v1/brands/{non_existent_brand_id}/competitors/" ) assert response.status_code == 404