167 lines
5.8 KiB
Python
167 lines
5.8 KiB
Python
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from services.intelligence.agents.core_agent_framework import TaskProposal
|
|
from services import today_workflow_service as svc
|
|
|
|
|
|
class DummyActivity:
|
|
def __init__(self, *args, **kwargs):
|
|
pass
|
|
|
|
def start_run(self, *args, **kwargs):
|
|
return SimpleNamespace(id="run-1")
|
|
|
|
def log_event(self, *args, **kwargs):
|
|
return None
|
|
|
|
def finish_run(self, *args, **kwargs):
|
|
return None
|
|
|
|
|
|
class DummyMemoryService:
|
|
def __init__(self, user_id, db):
|
|
pass
|
|
|
|
async def filter_redundant_proposals(self, proposals):
|
|
return proposals
|
|
|
|
|
|
class DummyAgent:
|
|
def __init__(self, proposals):
|
|
self._proposals = proposals
|
|
|
|
async def propose_daily_tasks(self, grounding):
|
|
return self._proposals
|
|
|
|
|
|
def _mock_orchestrator_with_agents(content_proposals=None, strategy_proposals=None):
|
|
return SimpleNamespace(
|
|
agents={
|
|
"content": DummyAgent(content_proposals or []),
|
|
"strategy": DummyAgent(strategy_proposals or []),
|
|
"seo": None,
|
|
"social": None,
|
|
"competitor": None,
|
|
}
|
|
)
|
|
|
|
|
|
def _covered_pillars(result):
|
|
return {task["pillarId"] for task in result["tasks"]}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_agent_enhanced_plan_preserves_full_committee_coverage(monkeypatch):
|
|
proposals = [
|
|
TaskProposal("P", "desc", "plan", "high", 10, "content", "why", {}, "navigate", "/content-planning-dashboard"),
|
|
TaskProposal("G", "desc", "generate", "high", 10, "content", "why", {}, "navigate", "/blog-writer"),
|
|
TaskProposal("Pu", "desc", "publish", "high", 10, "content", "why", {}, "navigate", "/scheduler-dashboard"),
|
|
TaskProposal("A", "desc", "analyze", "high", 10, "content", "why", {}, "navigate", "/seo-dashboard"),
|
|
TaskProposal("E", "desc", "engage", "high", 10, "content", "why", {}, "navigate", "/linkedin-writer"),
|
|
TaskProposal("R", "desc", "remarket", "high", 10, "content", "why", {}, "navigate", "/facebook-writer"),
|
|
]
|
|
|
|
async def _get_orchestrator(user_id):
|
|
return _mock_orchestrator_with_agents(content_proposals=proposals)
|
|
|
|
monkeypatch.setattr(svc, "build_grounding_context", lambda db, user_id, date: {})
|
|
monkeypatch.setattr(svc.orchestration_service, "get_or_create_orchestrator", _get_orchestrator)
|
|
|
|
result = await svc.generate_agent_enhanced_plan(db=None, user_id="u1", date="2026-01-01")
|
|
|
|
assert _covered_pillars(result) == set(svc.PILLAR_IDS)
|
|
assert len(result["tasks"]) == len(proposals)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_agent_enhanced_plan_backfills_missing_committee_pillars(monkeypatch):
|
|
proposals = [
|
|
TaskProposal("P", "desc", "plan", "high", 10, "content", "why", {}, "navigate", "/content-planning-dashboard"),
|
|
TaskProposal("G", "desc", "generate", "high", 10, "content", "why", {}, "navigate", "/blog-writer"),
|
|
]
|
|
|
|
async def _get_orchestrator(user_id):
|
|
return _mock_orchestrator_with_agents(content_proposals=proposals)
|
|
|
|
monkeypatch.setattr(svc, "build_grounding_context", lambda db, user_id, date: {})
|
|
monkeypatch.setattr(svc.orchestration_service, "get_or_create_orchestrator", _get_orchestrator)
|
|
|
|
result = await svc.generate_agent_enhanced_plan(db=None, user_id="u1", date="2026-01-01")
|
|
|
|
assert _covered_pillars(result) == set(svc.PILLAR_IDS)
|
|
assert {"P", "G"}.issubset({task["title"] for task in result["tasks"]})
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_agent_enhanced_plan_full_fallback_path_still_covers_all_pillars(monkeypatch):
|
|
async def _get_orchestrator(user_id):
|
|
return _mock_orchestrator_with_agents()
|
|
|
|
monkeypatch.setattr(svc, "build_grounding_context", lambda db, user_id, date: {})
|
|
monkeypatch.setattr(svc.orchestration_service, "get_or_create_orchestrator", _get_orchestrator)
|
|
monkeypatch.setattr(svc, "AgentActivityService", DummyActivity)
|
|
|
|
def _raise_llm(*args, **kwargs):
|
|
raise RuntimeError("LLM down")
|
|
|
|
monkeypatch.setattr(svc, "llm_text_gen", _raise_llm)
|
|
|
|
result = await svc.generate_agent_enhanced_plan(db=None, user_id="u1", date="2026-01-01")
|
|
|
|
assert _covered_pillars(result) == set(svc.PILLAR_IDS)
|
|
assert len(result["tasks"]) >= len(svc.PILLAR_IDS)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_agent_enhanced_plan_strategy_plan_task_survives_dedupe_and_coverage(monkeypatch):
|
|
content_proposals = [
|
|
TaskProposal(
|
|
"Review Strategic Goals",
|
|
"desc",
|
|
"plan",
|
|
"medium",
|
|
10,
|
|
"ContentStrategyAgent",
|
|
"why",
|
|
{},
|
|
"navigate",
|
|
"/content-planning-dashboard",
|
|
),
|
|
]
|
|
strategy_proposals = [
|
|
TaskProposal(
|
|
"Review Strategic Goals",
|
|
"desc",
|
|
"plan",
|
|
"high",
|
|
10,
|
|
"StrategyArchitectAgent",
|
|
"why",
|
|
{},
|
|
"navigate",
|
|
"/content-planning-dashboard",
|
|
),
|
|
]
|
|
|
|
async def _get_orchestrator(user_id):
|
|
return _mock_orchestrator_with_agents(
|
|
content_proposals=content_proposals,
|
|
strategy_proposals=strategy_proposals,
|
|
)
|
|
|
|
monkeypatch.setattr(svc, "build_grounding_context", lambda db, user_id, date: {})
|
|
monkeypatch.setattr(svc.orchestration_service, "get_or_create_orchestrator", _get_orchestrator)
|
|
monkeypatch.setattr(svc, "TaskMemoryService", DummyMemoryService)
|
|
|
|
result = await svc.generate_agent_enhanced_plan(db=None, user_id="u1", date="2026-01-01")
|
|
|
|
assert _covered_pillars(result) == set(svc.PILLAR_IDS)
|
|
plan_tasks = [task for task in result["tasks"] if task["pillarId"] == "plan"]
|
|
assert any(
|
|
task["title"] == "Review Strategic Goals"
|
|
and task["metadata"].get("source_agent") == "StrategyArchitectAgent"
|
|
for task in plan_tasks
|
|
)
|