177 lines
5.7 KiB
Python
177 lines
5.7 KiB
Python
import sys
|
|
import types
|
|
|
|
import pytest
|
|
|
|
from services.intelligence.agents.core_agent_framework import TaskProposal
|
|
|
|
|
|
# Stub deep onboarding import chain before importing today_workflow_service.
|
|
module_names = [
|
|
"api",
|
|
"api.content_planning",
|
|
"api.content_planning.services",
|
|
"api.content_planning.services.content_strategy",
|
|
"api.content_planning.services.content_strategy.onboarding",
|
|
"api.content_planning.services.content_strategy.onboarding.data_integration",
|
|
]
|
|
for name in module_names:
|
|
if name not in sys.modules:
|
|
sys.modules[name] = types.ModuleType(name)
|
|
|
|
|
|
class _StubOnboardingDataIntegrationService:
|
|
def get_integrated_data_sync(self, user_id, db):
|
|
return {}
|
|
|
|
|
|
sys.modules[
|
|
"api.content_planning.services.content_strategy.onboarding.data_integration"
|
|
].OnboardingDataIntegrationService = _StubOnboardingDataIntegrationService
|
|
|
|
from services import today_workflow_service as workflow
|
|
|
|
|
|
class _NoopActivityService:
|
|
def __init__(self, db, user_id):
|
|
self.db = db
|
|
self.user_id = user_id
|
|
|
|
|
|
class _NoopMemoryService:
|
|
def __init__(self, user_id, db):
|
|
self.user_id = user_id
|
|
self.db = db
|
|
|
|
|
|
class _StaticAgent:
|
|
def __init__(self, proposals):
|
|
self._proposals = proposals
|
|
|
|
async def propose_daily_tasks(self, _grounding):
|
|
return self._proposals
|
|
|
|
|
|
class _FakeOrchestrationService:
|
|
def __init__(self, orchestrator):
|
|
self._orchestrator = orchestrator
|
|
|
|
async def get_or_create_orchestrator(self, _user_id):
|
|
return self._orchestrator
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_agent_enhanced_plan_includes_strategy_agent_proposals(monkeypatch):
|
|
strategy_proposal = TaskProposal(
|
|
title="Define weekly content themes",
|
|
description="Create this week's strategic themes for planned posts.",
|
|
pillar_id="plan",
|
|
priority="high",
|
|
estimated_time=20,
|
|
source_agent="StrategyArchitectAgent",
|
|
reasoning="Strategy-driven planning",
|
|
action_type="navigate",
|
|
action_url="/content-planning-dashboard",
|
|
)
|
|
|
|
orchestrator = type(
|
|
"Orchestrator",
|
|
(),
|
|
{"agents": {"strategy": _StaticAgent([strategy_proposal])}},
|
|
)()
|
|
|
|
monkeypatch.setattr(workflow, "build_grounding_context", lambda db, user_id, date: {"date": date})
|
|
monkeypatch.setattr(workflow, "AgentActivityService", _NoopActivityService)
|
|
monkeypatch.setattr(workflow, "TaskMemoryService", _NoopMemoryService)
|
|
monkeypatch.setattr(workflow, "orchestration_service", _FakeOrchestrationService(orchestrator))
|
|
|
|
# Mock DB session
|
|
class MockDB:
|
|
pass
|
|
|
|
result = await workflow.generate_agent_enhanced_plan(db=MockDB(), user_id="u1", date="2026-01-01")
|
|
|
|
# Assertions
|
|
# Note: result["tasks"] might contain fallback tasks if pillar coverage guardrail is active
|
|
# We just check if our strategy proposal is present
|
|
|
|
strategy_tasks = [t for t in result["tasks"] if t["metadata"].get("source_agent") == "StrategyArchitectAgent"]
|
|
|
|
assert len(strategy_tasks) >= 1
|
|
assert strategy_tasks[0]["pillarId"] == "plan"
|
|
assert strategy_tasks[0]["title"] == "Define weekly content themes"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_agent_enhanced_plan_dedupes_plan_tasks_with_priority_and_tiebreak(monkeypatch):
|
|
title = "Build next week content plan"
|
|
|
|
content_medium = TaskProposal(
|
|
title=title,
|
|
description="Draft a medium-priority weekly plan.",
|
|
pillar_id="plan",
|
|
priority="medium",
|
|
estimated_time=15,
|
|
source_agent="ZetaAgent",
|
|
reasoning="baseline",
|
|
action_type="navigate",
|
|
action_url="/",
|
|
context_data={}
|
|
)
|
|
strategy_high = TaskProposal(
|
|
title=title,
|
|
description="Draft a high-priority strategic plan.",
|
|
pillar_id="plan",
|
|
priority="high",
|
|
estimated_time=20,
|
|
source_agent="StrategyArchitectAgent",
|
|
reasoning="urgent update",
|
|
action_type="navigate",
|
|
action_url="/",
|
|
context_data={}
|
|
)
|
|
competitor_high = TaskProposal(
|
|
title=title,
|
|
description="Alternative high-priority plan.",
|
|
pillar_id="plan",
|
|
priority="high",
|
|
estimated_time=25,
|
|
source_agent="BetaAgent",
|
|
reasoning="same priority tie",
|
|
action_type="navigate",
|
|
action_url="/",
|
|
context_data={}
|
|
)
|
|
|
|
class MockOrchestrator:
|
|
agents = {
|
|
"content": _StaticAgent([content_medium]),
|
|
"strategy": _StaticAgent([strategy_high]),
|
|
"competitor": _StaticAgent([competitor_high]),
|
|
}
|
|
|
|
orchestrator = MockOrchestrator()
|
|
|
|
monkeypatch.setattr(workflow, "build_grounding_context", lambda db, user_id, date: {"date": date})
|
|
monkeypatch.setattr(workflow, "AgentActivityService", _NoopActivityService)
|
|
monkeypatch.setattr(workflow, "TaskMemoryService", _NoopMemoryService)
|
|
monkeypatch.setattr(workflow, "orchestration_service", _FakeOrchestrationService(orchestrator))
|
|
|
|
class MockDB:
|
|
pass
|
|
|
|
result = await workflow.generate_agent_enhanced_plan(db=MockDB(), user_id="u1", date="2026-01-01")
|
|
|
|
# Filter for our specific test title to ignore backfilled tasks
|
|
target_tasks = [t for t in result["tasks"] if t["title"] == title]
|
|
|
|
assert len(target_tasks) == 1
|
|
task = target_tasks[0]
|
|
assert task["title"] == title
|
|
assert task["priority"] == "high"
|
|
|
|
# The tie-breaking logic in today_workflow_service iterates through agent_tasks.
|
|
# The order depends on the dict iteration order of unique_map.
|
|
# We just want to ensure it picked one of the high priority ones, not the medium one.
|
|
assert task["metadata"]["source_agent"] in ["StrategyArchitectAgent", "BetaAgent"]
|