"""Tests for plan tools."""
import pytest
from pathfinder_mcp.state import Phase
from pathfinder_mcp.tools.plan import save_plan, start_plan, validate_plan_structure
class TestValidatePlanStructure:
"""Tests for plan validation."""
def test_valid_plan_passes(self, sample_plan_content: str) -> None:
"""Valid plan passes validation."""
is_valid, errors = validate_plan_structure(sample_plan_content)
assert is_valid
assert len(errors) == 0
def test_missing_frontmatter_fails(self) -> None:
"""Plan without frontmatter fails."""
content = "# Plan\n\n## Architecture Overview\n"
is_valid, errors = validate_plan_structure(content)
assert not is_valid
assert any("frontmatter" in e.lower() for e in errors)
def test_missing_sections_fails(self) -> None:
"""Plan without required sections fails."""
content = """---
name: Test
overview: Test
todos: []
---
# Plan
## Phase 1: Test
"""
is_valid, errors = validate_plan_structure(content)
assert not is_valid
# Should be missing Architecture Overview, Core Components, etc.
assert len(errors) > 0
def test_missing_yaml_fields_fails(self) -> None:
"""Plan with incomplete YAML fails."""
content = """---
name: Test
---
## Architecture Overview
## Core Components
## Phase 1: Setup
## Implementation Details
## Success Criteria
"""
is_valid, errors = validate_plan_structure(content)
assert not is_valid
assert any("overview" in e.lower() for e in errors)
class TestStartPlan:
"""Tests for start_plan tool."""
@pytest.mark.asyncio
async def test_transitions_to_plan_phase(
self,
session_manager,
artifact_writer,
context_monitor,
active_sessions,
sample_session_id,
research_state,
) -> None:
"""start_plan transitions from RESEARCH to PLAN."""
session_manager.create_session(sample_session_id)
active_sessions[sample_session_id] = research_state
artifact_writer.write_research(sample_session_id, "Findings", task="Task")
result = await start_plan(
session_id=sample_session_id,
ctx=None, # Skip elicit
session_manager=session_manager,
artifact_writer=artifact_writer,
context_monitor=context_monitor,
sessions=active_sessions,
)
assert result["phase"] == "plan"
assert active_sessions[sample_session_id].current_phase == Phase.PLAN
@pytest.mark.asyncio
async def test_creates_plan_template(
self,
session_manager,
artifact_writer,
context_monitor,
active_sessions,
sample_session_id,
research_state,
) -> None:
"""start_plan creates plan.md template."""
session_manager.create_session(sample_session_id)
active_sessions[sample_session_id] = research_state
artifact_writer.write_research(sample_session_id, "Findings", task="Task")
await start_plan(
session_id=sample_session_id,
ctx=None,
session_manager=session_manager,
artifact_writer=artifact_writer,
context_monitor=context_monitor,
sessions=active_sessions,
)
content = artifact_writer.read_artifact(sample_session_id, "plan.md")
assert content is not None
assert "---" in content # YAML frontmatter
@pytest.mark.asyncio
async def test_requires_research_artifact(
self,
session_manager,
artifact_writer,
context_monitor,
active_sessions,
sample_session_id,
research_state,
) -> None:
"""start_plan fails without research.md."""
session_manager.create_session(sample_session_id)
active_sessions[sample_session_id] = research_state
# Don't create research artifact
result = await start_plan(
session_id=sample_session_id,
ctx=None,
session_manager=session_manager,
artifact_writer=artifact_writer,
context_monitor=context_monitor,
sessions=active_sessions,
)
assert result["code"] == "MISSING_RESEARCH"
@pytest.mark.asyncio
async def test_fails_from_wrong_phase(
self,
session_manager,
artifact_writer,
context_monitor,
active_sessions,
sample_session_id,
plan_state,
) -> None:
"""start_plan fails when not in RESEARCH phase."""
session_manager.create_session(sample_session_id)
active_sessions[sample_session_id] = plan_state
result = await start_plan(
session_id=sample_session_id,
ctx=None,
session_manager=session_manager,
artifact_writer=artifact_writer,
context_monitor=context_monitor,
sessions=active_sessions,
)
assert result["code"] == "INVALID_PHASE"
class TestSavePlan:
"""Tests for save_plan tool."""
def test_saves_valid_plan(
self,
session_manager,
artifact_writer,
context_monitor,
active_sessions,
sample_session_id,
plan_state,
sample_plan_content,
) -> None:
"""save_plan saves valid plan content."""
session_manager.create_session(sample_session_id)
active_sessions[sample_session_id] = plan_state
result = save_plan(
session_id=sample_session_id,
plan_content=sample_plan_content,
session_manager=session_manager,
artifact_writer=artifact_writer,
context_monitor=context_monitor,
sessions=active_sessions,
)
assert result["validated"] is True
content = artifact_writer.read_artifact(sample_session_id, "plan.md")
assert "Test Implementation Plan" in content
def test_rejects_invalid_plan(
self,
session_manager,
artifact_writer,
context_monitor,
active_sessions,
sample_session_id,
plan_state,
) -> None:
"""save_plan rejects invalid plan format."""
session_manager.create_session(sample_session_id)
active_sessions[sample_session_id] = plan_state
result = save_plan(
session_id=sample_session_id,
plan_content="# Invalid plan\n\nNo structure",
session_manager=session_manager,
artifact_writer=artifact_writer,
context_monitor=context_monitor,
sessions=active_sessions,
)
assert result["code"] == "INVALID_PLAN_FORMAT"
assert "validation_errors" in result
def test_fails_from_wrong_phase(
self,
session_manager,
artifact_writer,
context_monitor,
active_sessions,
sample_session_id,
research_state,
sample_plan_content,
) -> None:
"""save_plan fails when not in PLAN phase."""
session_manager.create_session(sample_session_id)
active_sessions[sample_session_id] = research_state
result = save_plan(
session_id=sample_session_id,
plan_content=sample_plan_content,
session_manager=session_manager,
artifact_writer=artifact_writer,
context_monitor=context_monitor,
sessions=active_sessions,
)
assert result["code"] == "INVALID_PHASE"