from __future__ import annotations import os os.environ.setdefault("VELOCITY_JWT_SECRET", "test-secret") from fastapi import FastAPI from fastapi.testclient import TestClient from backend.api.routes_catalyst import router from backend.auth.dependencies import UserPrincipal, get_current_user from backend.services.ad_network_service import BidAction, Platform def _build_client() -> TestClient: app = FastAPI() async def _broadcast_live_event(*_args, **_kwargs): return None app.state.broadcast_live_event = _broadcast_live_event app.include_router(router, prefix="/api/catalyst") return TestClient(app) def _build_authed_client() -> TestClient: app = FastAPI() app.state.db_pool = object() app.dependency_overrides[get_current_user] = lambda: UserPrincipal( user_id="sayan", role="ADMIN", tenant_id="tenant-root", ) app.include_router(router, prefix="/api/catalyst") return TestClient(app) def test_catalyst_campaigns_and_google_budget_routes(monkeypatch) -> None: client = _build_client() async def fake_list_campaigns(platform=None): return [ type( "Campaign", (), { "id": "google-camp-001", "name": "Search Investors", "platform": Platform.GOOGLE, "status": type("Status", (), {"value": "active"})(), "daily_budget": 5000, "spent": 0, "objective": "SEARCH", "bid_strategy": "TARGET_ROAS", }, )() ] async def fake_get_insights(**_kwargs): return [ { "campaign_id": "google-camp-001", "spend": 2400, "impressions": 120000, "clicks": 4200, "conversions": 38, } ] async def fake_update_budget(_payload): return {"status": "ok", "platform": "google", "mode": "simulated"} async def fake_update_bid_strategy(_payload): return BidAction( action_id="act-1", campaign_id="google-camp-001", platform=Platform.GOOGLE, old_strategy="TARGET_CPA", new_strategy="TARGET_ROAS", target_value=8.5, executed_at="2026-04-12T00:00:00Z", ) monkeypatch.setattr("backend.api.routes_catalyst.ad_network_service.list_campaigns", fake_list_campaigns) monkeypatch.setattr("backend.api.routes_catalyst.ad_network_service.get_insights", fake_get_insights) monkeypatch.setattr("backend.api.routes_catalyst.ad_network_service.update_budget", fake_update_budget) monkeypatch.setattr("backend.api.routes_catalyst.ad_network_service.update_bid_strategy", fake_update_bid_strategy) campaigns = client.get("/api/catalyst/campaigns") assert campaigns.status_code == 200 assert campaigns.json()["data"][0]["platform"] == "google" assert campaigns.json()["data"][0]["conversions"] == 38 budget = client.put( "/api/catalyst/budget", json={"campaign_id": "google-camp-001", "platform": "google", "daily_budget": 6500}, ) assert budget.status_code == 200 assert budget.json()["data"]["platform"] == "google" bid = client.put( "/api/catalyst/bid-strategy", json={ "campaign_id": "google-camp-001", "platform": "google", "strategy": "TARGET_ROAS", "target_value": 8.5, }, ) assert bid.status_code == 200 assert bid.json()["data"]["new_strategy"] == "TARGET_ROAS" def test_catalyst_social_publish_route_is_tenant_scoped(monkeypatch) -> None: async def fake_publish_content(*, pool, tenant_id, actor_id, payload): assert pool is not None assert tenant_id == "tenant-root" assert actor_id == "sayan" assert payload.platforms[0].value == "facebook" return { "request_id": "req-1", "total": 1, "published": 1, "scheduled": 0, "failed": 0, "posts": [{"post_id": "post-1", "platform": "facebook", "status": "published"}], } monkeypatch.setattr("backend.api.routes_catalyst.publish_content", fake_publish_content) response = _build_authed_client().post( "/api/catalyst/publish", json={ "platforms": ["facebook"], "post_type": "image", "caption": "New waterfront inventory is ready.", "media_url": "https://assets.example.com/post.jpg", }, ) assert response.status_code == 201 assert response.json()["data"]["published"] == 1 def test_catalyst_scheduled_posts_route_filters_scheduled_status(monkeypatch) -> None: async def fake_list_posts(*, pool, tenant_id, platform=None, status=None, limit=50): assert pool is not None assert tenant_id == "tenant-root" assert platform is None assert status.value == "scheduled" assert limit == 25 return [{"post_id": "post-2", "platform": "linkedin", "status": "scheduled"}] monkeypatch.setattr("backend.api.routes_catalyst.list_posts", fake_list_posts) response = _build_authed_client().get("/api/catalyst/scheduled?limit=25") assert response.status_code == 200 assert response.json()["meta"]["count"] == 1