Files
xer-mcp/tests/contract/test_load_xer.py
Bill Ballou ccc8296418 feat: implement XER MCP Server with 9 schedule query tools
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)
2026-01-06 21:27:35 -05:00

108 lines
4.0 KiB
Python

"""Contract tests for load_xer 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 TestLoadXerContract:
"""Contract tests verifying load_xer tool interface matches spec."""
async def test_load_single_project_returns_success(
self, sample_xer_single_project: Path
) -> None:
"""load_xer with single-project file returns success and project info."""
from xer_mcp.tools.load_xer import load_xer
result = await load_xer(file_path=str(sample_xer_single_project))
assert result["success"] is True
assert "project" in result
assert result["project"]["proj_id"] == "1001"
assert result["project"]["proj_short_name"] == "Test Project"
assert "activity_count" in result
assert result["activity_count"] == 5
assert "relationship_count" in result
assert result["relationship_count"] == 5
async def test_load_multi_project_without_selection_returns_list(
self, sample_xer_multi_project: Path
) -> None:
"""load_xer with multi-project file without project_id returns available projects."""
from xer_mcp.tools.load_xer import load_xer
result = await load_xer(file_path=str(sample_xer_multi_project))
assert result["success"] is False
assert "available_projects" in result
assert len(result["available_projects"]) == 2
assert "message" in result
assert "project_id" in result["message"].lower()
async def test_load_multi_project_with_selection_returns_success(
self, sample_xer_multi_project: Path
) -> None:
"""load_xer with multi-project file and project_id returns selected project."""
from xer_mcp.tools.load_xer import load_xer
result = await load_xer(file_path=str(sample_xer_multi_project), project_id="1001")
assert result["success"] is True
assert result["project"]["proj_id"] == "1001"
assert result["project"]["proj_short_name"] == "Project Alpha"
async def test_load_nonexistent_file_returns_error(self, nonexistent_xer_path: Path) -> None:
"""load_xer with missing file returns FILE_NOT_FOUND error."""
from xer_mcp.tools.load_xer import load_xer
result = await load_xer(file_path=str(nonexistent_xer_path))
assert result["success"] is False
assert "error" in result
assert result["error"]["code"] == "FILE_NOT_FOUND"
async def test_load_invalid_file_returns_error(self, invalid_xer_file: Path) -> None:
"""load_xer with invalid file returns PARSE_ERROR error."""
from xer_mcp.tools.load_xer import load_xer
result = await load_xer(file_path=str(invalid_xer_file))
assert result["success"] is False
assert "error" in result
assert result["error"]["code"] == "PARSE_ERROR"
async def test_load_replaces_previous_file(
self, sample_xer_single_project: Path, sample_xer_empty: Path
) -> None:
"""Loading a new file replaces the previous file's data."""
from xer_mcp.tools.load_xer import load_xer
# Load first file
result1 = await load_xer(file_path=str(sample_xer_single_project))
assert result1["activity_count"] == 5
# Load second file (empty)
result2 = await load_xer(file_path=str(sample_xer_empty))
assert result2["activity_count"] == 0
async def test_load_returns_plan_dates(self, sample_xer_single_project: Path) -> None:
"""load_xer returns project plan start and end dates."""
from xer_mcp.tools.load_xer import load_xer
result = await load_xer(file_path=str(sample_xer_single_project))
assert "plan_start_date" in result["project"]
assert "plan_end_date" in result["project"]
# Dates should be ISO8601 format
assert "T" in result["project"]["plan_start_date"]