forked from sagnik/Project_Velocity
feat: Ipad app production readiness, Colony orchestration, Social posting (#44)
#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
This commit is contained in:
@@ -108,6 +108,30 @@ def test_canonical_crm_import_upload_requires_authentication() -> None:
|
||||
assert response.json()["detail"] == "Missing or malformed Authorization header."
|
||||
|
||||
|
||||
def test_canonical_crm_vocabularies_require_authentication() -> None:
|
||||
client = _build_app(authenticated=False)
|
||||
|
||||
response = client.get("/api/crm/vocabularies")
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response.json()["detail"] == "Missing or malformed Authorization header."
|
||||
|
||||
|
||||
def test_canonical_crm_vocabularies_are_backend_owned() -> None:
|
||||
client = _build_app(authenticated=True)
|
||||
|
||||
response = client.get("/api/crm/vocabularies")
|
||||
|
||||
assert response.status_code == 200
|
||||
payload = response.json()["data"]
|
||||
assert payload["lead_statuses"][0]["value"] == routes_crm_imports.CANONICAL_LEAD_STAGES[0]
|
||||
assert payload["opportunity_stages"][0]["value"] == routes_crm_imports.CANONICAL_OPPORTUNITY_STAGES[0]
|
||||
assert {policy["value"] for policy in payload["import_duplicate_policies"]} == set(
|
||||
routes_crm_imports.IMPORT_DUPLICATE_POLICIES
|
||||
)
|
||||
assert payload["dream_weaver_room_types"][0]["icon"]
|
||||
|
||||
|
||||
def test_canonical_crm_contacts_can_be_read_when_authenticated(monkeypatch) -> None:
|
||||
client = _build_app(authenticated=True)
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
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
|
||||
|
||||
|
||||
@@ -18,6 +23,18 @@ def _build_client() -> TestClient:
|
||||
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()
|
||||
|
||||
@@ -92,3 +109,51 @@ def test_catalyst_campaigns_and_google_budget_routes(monkeypatch) -> None:
|
||||
)
|
||||
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
|
||||
|
||||
99
backend/tests/test_colony_routes.py
Normal file
99
backend/tests/test_colony_routes.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
os.environ.setdefault("VELOCITY_JWT_SECRET", "test-secret")
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from backend.api import routes_colony
|
||||
from backend.auth.dependencies import UserPrincipal, get_current_user
|
||||
|
||||
|
||||
def _mission_row(mission: dict[str, Any], *, status: str = "pending") -> dict[str, Any]:
|
||||
now = datetime(2026, 5, 3, tzinfo=timezone.utc)
|
||||
return {
|
||||
**mission,
|
||||
"status": status,
|
||||
"review_status": None,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
"completed_at": None,
|
||||
}
|
||||
|
||||
|
||||
def _build_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(routes_colony.router, prefix="/api/colony")
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def test_colony_create_mission_persists_and_dispatches(monkeypatch) -> None:
|
||||
stored: dict[str, Any] = {}
|
||||
events: list[str] = []
|
||||
|
||||
class FakeRepo:
|
||||
def __init__(self, _pool) -> None:
|
||||
pass
|
||||
|
||||
async def create_mission(self, mission: dict[str, Any]) -> dict[str, Any]:
|
||||
stored.update(mission)
|
||||
return _mission_row(mission)
|
||||
|
||||
async def update_status(self, mission_id: str, tenant_id: str, status: str, **_kwargs) -> dict[str, Any]:
|
||||
assert mission_id == stored["mission_id"]
|
||||
assert tenant_id == "tenant-root"
|
||||
return _mission_row(stored, status=status)
|
||||
|
||||
async def log_event(self, **kwargs) -> None:
|
||||
events.append(kwargs["event_type"])
|
||||
|
||||
class FakeGateway:
|
||||
async def dispatch_mission(self, mission: dict[str, Any]) -> dict[str, Any]:
|
||||
assert mission["tenant_id"] == "tenant-root"
|
||||
assert mission["actor_id"] == "sayan"
|
||||
return {"accepted": True, "remote_mission_id": mission["mission_id"]}
|
||||
|
||||
monkeypatch.setattr(routes_colony, "ColonyRepository", FakeRepo)
|
||||
monkeypatch.setattr(routes_colony, "ColonyGateway", FakeGateway)
|
||||
|
||||
response = _build_client().post(
|
||||
"/api/colony/missions",
|
||||
json={
|
||||
"mission_type": "oracle_advisory",
|
||||
"user_goal": "Compare Palm Jumeirah leads and recommend the next broker action.",
|
||||
"context_refs": {"lead_id": "lead-1"},
|
||||
"requested_outputs": ["summary", "writeback_proposals"],
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
body = response.json()["data"]
|
||||
assert body["tenant_id"] == "tenant-root"
|
||||
assert body["actor_id"] == "sayan"
|
||||
assert body["status"] == "queued"
|
||||
assert body["dispatch"]["accepted"] is True
|
||||
assert events == ["mission_created", "mission_dispatched"]
|
||||
|
||||
|
||||
def test_colony_create_mission_requires_configured_orchestrator(monkeypatch) -> None:
|
||||
monkeypatch.delenv("COLONY_SERVICE_URL", raising=False)
|
||||
response = _build_client().post(
|
||||
"/api/colony/missions",
|
||||
json={
|
||||
"mission_type": "catalyst_strategy_brief",
|
||||
"user_goal": "Prepare a launch brief.",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 503
|
||||
assert "COLONY_SERVICE_URL" in response.json()["detail"]
|
||||
Reference in New Issue
Block a user