""" Launch Desk — Agent & Tool Tests ================================ Pytest tests that verify the agent has 4 tools registered and that each tool returns the expected structure. Because @function_tool wraps functions in a FunctionTool dataclass, we access the original callable via `tool.on_invoke_tool._get_wrapped_callable()`. """ import pytest from server.agent import agent from server.tools import ( extract_tasks, check_readiness, generate_checklist, draft_copy, ) # --------------------------------------------------------------------------- # Helper: get the original wrapped callable from a FunctionTool # --------------------------------------------------------------------------- def _get_callable(tool): """Extract the original Python function from a @function_tool wrapper.""" return tool.on_invoke_tool._get_wrapped_callable() # --------------------------------------------------------------------------- # Test: agent has 4 tools registered # --------------------------------------------------------------------------- def test_agent_has_four_tools(): """The Launch Planner agent should have exactly 4 tools registered.""" assert agent is not None assert agent.name == "Launch Planner" assert agent.tools is not None assert len(agent.tools) == 4, f"Expected 4 tools, got {len(agent.tools)}" def test_agent_has_expected_tool_names(): """The agent should have tools with the expected names.""" tool_names = {t.name for t in agent.tools} expected = {"extract_tasks", "check_readiness", "generate_checklist", "draft_copy"} assert tool_names == expected, f"Expected {expected}, got {tool_names}" # --------------------------------------------------------------------------- # Test: extract_tasks returns expected structure # --------------------------------------------------------------------------- def test_extract_tasks_returns_list_of_dicts(): """extract_tasks should return a list of dicts with task/priority/owner/deadline.""" fn = _get_callable(extract_tasks) brief = "We are launching a new API with backend, QA testing, and marketing campaign" result = fn(brief) assert isinstance(result, list) assert len(result) > 0 for task in result: assert isinstance(task, dict) assert "task" in task assert "priority" in task assert "owner" in task assert "deadline" in task def test_extract_tasks_handles_empty_brief(): """extract_tasks should handle an empty brief gracefully.""" fn = _get_callable(extract_tasks) result = fn("") assert isinstance(result, list) assert len(result) > 0 assert "task" in result[0] def test_extract_tasks_priority_values(): """All priorities should be high, medium, or low.""" fn = _get_callable(extract_tasks) result = fn("API, QA, docs, marketing, support, security, pricing, analytics, design, legal") valid_priorities = {"high", "medium", "low"} for task in result: assert task["priority"] in valid_priorities # --------------------------------------------------------------------------- # Test: check_readiness returns expected structure # --------------------------------------------------------------------------- def test_check_readiness_returns_dict(): """check_readiness should return a dict with ready/score/gaps.""" fn = _get_callable(check_readiness) tasks = "code freeze, QA testing, marketing campaign, documentation, support staffing" result = fn(tasks, "2026-10-01") assert isinstance(result, dict) assert "ready" in result assert "score" in result assert "gaps" in result assert isinstance(result["ready"], bool) assert isinstance(result["score"], int) assert isinstance(result["gaps"], list) def test_check_readiness_high_score_when_covered(): """check_readiness should give a high score when all rubric areas are covered.""" fn = _get_callable(check_readiness) tasks = "code freeze, QA testing, marketing campaign, documentation, support staffing" result = fn(tasks, "2026-10-01") assert result["score"] >= 80, f"Expected high score, got {result['score']}" def test_check_readiness_low_score_when_gaps(): """check_readiness should give a low score when rubric areas are missing.""" fn = _get_callable(check_readiness) tasks = "just some random text with no keywords" result = fn(tasks, "2026-10-01") assert result["score"] < 100 assert len(result["gaps"]) > 0 # --------------------------------------------------------------------------- # Test: generate_checklist returns expected structure # --------------------------------------------------------------------------- def test_generate_checklist_returns_list_of_dicts(): """generate_checklist should return a list of {owner, items} dicts.""" fn = _get_callable(generate_checklist) import json tasks_json = json.dumps([ {"task": "Freeze code", "owner": "Engineering", "priority": "high", "deadline": "T-14"}, {"task": "QA testing", "owner": "QA", "priority": "high", "deadline": "T-7"}, {"task": "Marketing campaign", "owner": "Marketing", "priority": "high", "deadline": "T-5"}, ]) result = fn(tasks_json) assert isinstance(result, list) assert len(result) > 0 for checklist in result: assert isinstance(checklist, dict) assert "owner" in checklist assert "items" in checklist assert isinstance(checklist["items"], list) assert len(checklist["items"]) > 0 for item in checklist["items"]: assert isinstance(item, str) def test_generate_checklist_includes_launch_lead(): """generate_checklist should include a Launch Lead checklist.""" fn = _get_callable(generate_checklist) result = fn("[]") owners = [c["owner"] for c in result] assert "Launch Lead" in owners def test_generate_checklist_groups_by_owner(): """generate_checklist should group tasks by owner.""" import json fn = _get_callable(generate_checklist) tasks_json = json.dumps([ {"task": "Task A", "owner": "Engineering"}, {"task": "Task B", "owner": "Engineering"}, {"task": "Task C", "owner": "Marketing"}, ]) result = fn(tasks_json) owners = {c["owner"] for c in result} assert "Engineering" in owners assert "Marketing" in owners # --------------------------------------------------------------------------- # Test: draft_copy returns expected structure # --------------------------------------------------------------------------- def test_draft_copy_returns_dict(): """draft_copy should return a dict with headline/body/cta.""" fn = _get_callable(draft_copy) result = fn("email", "LaunchDesk Pro", "enterprise DevOps teams") assert isinstance(result, dict) assert "headline" in result assert "body" in result assert "cta" in result assert isinstance(result["headline"], str) assert isinstance(result["body"], str) assert isinstance(result["cta"], str) def test_draft_copy_channel_specific(): """draft_copy should produce different copy for different channels.""" fn = _get_callable(draft_copy) email = fn("email", "ProductX", "developers") twitter = fn("twitter", "ProductX", "developers") assert email["headline"] != twitter["headline"], "Copy should differ by channel" def test_draft_copy_includes_product_and_audience(): """draft_copy should reference the product name and audience.""" fn = _get_callable(draft_copy) result = fn("email", "MyProduct", "data scientists") assert "MyProduct" in result["headline"] or "MyProduct" in result["body"] assert "data scientists" in result["headline"] or "data scientists" in result["body"] def test_draft_copy_handles_unknown_channel(): """draft_copy should handle an unknown channel gracefully.""" fn = _get_callable(draft_copy) result = fn("radio", "ProductX", "general audience") assert isinstance(result, dict) assert "headline" in result assert "body" in result assert "cta" in result