forked from sagnik/Project_Velocity
#38 Ipad app production readiness, Colony orchestration, Social posting Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: sagnik/Project_Velocity#44
160 lines
5.3 KiB
Python
160 lines
5.3 KiB
Python
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
|