Add milestone_type field to milestone queries that indicates whether
a milestone is a start milestone ('start') or finish milestone ('finish').
Changes:
- Add milestone_type column to activities table schema
- Parse milestone_type from XER TASK table (MS_Start/MS_Finish)
- Include milestone_type in list_milestones response
- Update contract tests for milestone_type field
- Update specs, contracts, and documentation
The milestone_type is determined by:
1. Explicit milestone_type field in XER (MS_Start -> 'start', MS_Finish -> 'finish')
2. Derived from task_type (TT_Mile -> 'start', TT_FinMile -> 'finish')
125 lines
4.4 KiB
Python
125 lines
4.4 KiB
Python
"""Contract tests for list_milestones MCP tool."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from xer_mcp.db import db
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_db():
|
|
"""Initialize and clear database for each test."""
|
|
db.initialize()
|
|
yield
|
|
db.clear()
|
|
|
|
|
|
class TestListMilestonesContract:
|
|
"""Contract tests verifying list_milestones tool interface."""
|
|
|
|
async def test_list_milestones_returns_milestone_activities(
|
|
self, sample_xer_single_project: Path
|
|
) -> None:
|
|
"""list_milestones returns only milestone type activities."""
|
|
from xer_mcp.tools.list_milestones import list_milestones
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_single_project))
|
|
|
|
result = await list_milestones()
|
|
|
|
assert "milestones" in result
|
|
assert len(result["milestones"]) == 2
|
|
|
|
async def test_list_milestones_includes_expected_fields(
|
|
self, sample_xer_single_project: Path
|
|
) -> None:
|
|
"""list_milestones returns milestones with required fields."""
|
|
from xer_mcp.tools.list_milestones import list_milestones
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_single_project))
|
|
|
|
result = await list_milestones()
|
|
|
|
milestone = result["milestones"][0]
|
|
assert "task_id" in milestone
|
|
assert "task_code" in milestone
|
|
assert "task_name" in milestone
|
|
assert "target_start_date" in milestone
|
|
assert "target_end_date" in milestone
|
|
|
|
async def test_list_milestones_returns_correct_activities(
|
|
self, sample_xer_single_project: Path
|
|
) -> None:
|
|
"""list_milestones returns the expected milestone activities."""
|
|
from xer_mcp.tools.list_milestones import list_milestones
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_single_project))
|
|
|
|
result = await list_milestones()
|
|
|
|
milestone_names = [m["task_name"] for m in result["milestones"]]
|
|
assert "Project Start" in milestone_names
|
|
assert "Project Complete" in milestone_names
|
|
|
|
async def test_list_milestones_empty_when_no_milestones(self, sample_xer_empty: Path) -> None:
|
|
"""list_milestones returns empty list when no milestones exist."""
|
|
from xer_mcp.tools.list_milestones import list_milestones
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_empty))
|
|
|
|
result = await list_milestones()
|
|
|
|
assert "milestones" in result
|
|
assert len(result["milestones"]) == 0
|
|
|
|
async def test_list_milestones_no_file_loaded_returns_error(self) -> None:
|
|
"""list_milestones without loaded file returns NO_FILE_LOADED error."""
|
|
from xer_mcp.server import set_file_loaded
|
|
from xer_mcp.tools.list_milestones import list_milestones
|
|
|
|
set_file_loaded(False)
|
|
result = await list_milestones()
|
|
|
|
assert "error" in result
|
|
assert result["error"]["code"] == "NO_FILE_LOADED"
|
|
|
|
async def test_list_milestones_includes_milestone_type_field(
|
|
self, sample_xer_single_project: Path
|
|
) -> None:
|
|
"""list_milestones returns milestones with milestone_type field."""
|
|
from xer_mcp.tools.list_milestones import list_milestones
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_single_project))
|
|
|
|
result = await list_milestones()
|
|
|
|
# All milestones should have milestone_type field
|
|
for milestone in result["milestones"]:
|
|
assert "milestone_type" in milestone
|
|
|
|
async def test_list_milestones_returns_start_and_finish_types(
|
|
self, sample_xer_single_project: Path
|
|
) -> None:
|
|
"""list_milestones returns milestones with correct start/finish types."""
|
|
from xer_mcp.tools.list_milestones import list_milestones
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_single_project))
|
|
|
|
result = await list_milestones()
|
|
|
|
# Find milestones by name and verify their types
|
|
milestones_by_name = {m["task_name"]: m for m in result["milestones"]}
|
|
|
|
# "Project Start" should be a start milestone
|
|
assert milestones_by_name["Project Start"]["milestone_type"] == "start"
|
|
|
|
# "Project Complete" should be a finish milestone
|
|
assert milestones_by_name["Project Complete"]["milestone_type"] == "finish"
|