Implement complete MCP server for parsing Primavera P6 XER files and exposing schedule data through MCP tools. All 4 user stories complete. Tools implemented: - load_xer: Parse XER files into SQLite database - list_activities: Query activities with pagination and filtering - get_activity: Get activity details by ID - list_relationships: Query activity dependencies - get_predecessors/get_successors: Query activity relationships - get_project_summary: Project overview with counts - list_milestones: Query milestone activities - get_critical_path: Query driving path activities Features: - Tab-delimited XER format parsing with pluggable table handlers - In-memory SQLite database for fast queries - Pagination with 100-item default limit - Multi-project file support with project selection - ISO8601 date formatting - NO_FILE_LOADED error handling for all query tools Test coverage: 81 tests (contract, integration, unit)
78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
"""Contract tests for get_predecessors 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 TestGetPredecessorsContract:
|
|
"""Contract tests verifying get_predecessors tool interface."""
|
|
|
|
async def test_get_predecessors_returns_list(self, sample_xer_single_project: Path) -> None:
|
|
"""get_predecessors returns predecessor activities."""
|
|
from xer_mcp.tools.get_predecessors import get_predecessors
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_single_project))
|
|
|
|
# A1010 (2002) has one predecessor: A1000 (2001)
|
|
result = await get_predecessors(activity_id="2002")
|
|
|
|
assert "activity_id" in result
|
|
assert result["activity_id"] == "2002"
|
|
assert "predecessors" in result
|
|
assert len(result["predecessors"]) == 1
|
|
assert result["predecessors"][0]["task_id"] == "2001"
|
|
|
|
async def test_get_predecessors_includes_relationship_details(
|
|
self, sample_xer_single_project: Path
|
|
) -> None:
|
|
"""get_predecessors includes relationship type and lag."""
|
|
from xer_mcp.tools.get_predecessors import get_predecessors
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_single_project))
|
|
|
|
result = await get_predecessors(activity_id="2002")
|
|
|
|
pred = result["predecessors"][0]
|
|
assert "relationship_type" in pred
|
|
assert "lag_hr_cnt" in pred
|
|
assert pred["relationship_type"] in ["FS", "SS", "FF", "SF"]
|
|
|
|
async def test_get_predecessors_empty_list_for_no_predecessors(
|
|
self, sample_xer_single_project: Path
|
|
) -> None:
|
|
"""get_predecessors returns empty list when no predecessors exist."""
|
|
from xer_mcp.tools.get_predecessors import get_predecessors
|
|
from xer_mcp.tools.load_xer import load_xer
|
|
|
|
await load_xer(file_path=str(sample_xer_single_project))
|
|
|
|
# A1000 (2001) has no predecessors
|
|
result = await get_predecessors(activity_id="2001")
|
|
|
|
assert "predecessors" in result
|
|
assert len(result["predecessors"]) == 0
|
|
|
|
async def test_get_predecessors_no_file_loaded_returns_error(self) -> None:
|
|
"""get_predecessors without loaded file returns NO_FILE_LOADED error."""
|
|
from xer_mcp.server import set_file_loaded
|
|
from xer_mcp.tools.get_predecessors import get_predecessors
|
|
|
|
set_file_loaded(False)
|
|
result = await get_predecessors(activity_id="2002")
|
|
|
|
assert "error" in result
|
|
assert result["error"]["code"] == "NO_FILE_LOADED"
|