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)
112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
"""Unit tests for database query functions."""
|
|
|
|
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 TestActivityQueries:
|
|
"""Tests for activity query functions."""
|
|
|
|
def test_query_activities_with_pagination(self, sample_xer_single_project: Path) -> None:
|
|
"""Should return paginated activity results."""
|
|
from xer_mcp.db.loader import load_parsed_data
|
|
from xer_mcp.db.queries import query_activities
|
|
from xer_mcp.parser.xer_parser import XerParser
|
|
|
|
parser = XerParser()
|
|
parsed = parser.parse(sample_xer_single_project)
|
|
load_parsed_data(parsed, project_id="1001")
|
|
|
|
activities, total = query_activities(limit=2, offset=0)
|
|
|
|
assert len(activities) == 2
|
|
assert total == 5
|
|
|
|
def test_query_activities_filter_by_type(self, sample_xer_single_project: Path) -> None:
|
|
"""Should filter activities by task type."""
|
|
from xer_mcp.db.loader import load_parsed_data
|
|
from xer_mcp.db.queries import query_activities
|
|
from xer_mcp.parser.xer_parser import XerParser
|
|
|
|
parser = XerParser()
|
|
parsed = parser.parse(sample_xer_single_project)
|
|
load_parsed_data(parsed, project_id="1001")
|
|
|
|
activities, total = query_activities(activity_type="TT_Mile")
|
|
|
|
assert total == 2
|
|
for act in activities:
|
|
assert act["task_type"] == "TT_Mile"
|
|
|
|
def test_query_activities_filter_by_date_range(self, sample_xer_single_project: Path) -> None:
|
|
"""Should filter activities by date range."""
|
|
from xer_mcp.db.loader import load_parsed_data
|
|
from xer_mcp.db.queries import query_activities
|
|
from xer_mcp.parser.xer_parser import XerParser
|
|
|
|
parser = XerParser()
|
|
parsed = parser.parse(sample_xer_single_project)
|
|
load_parsed_data(parsed, project_id="1001")
|
|
|
|
# Filter to very narrow range
|
|
activities, total = query_activities(start_date="2026-01-01", end_date="2026-01-01")
|
|
|
|
# Only activities starting on 2026-01-01
|
|
for act in activities:
|
|
assert "2026-01-01" in act["target_start_date"]
|
|
|
|
def test_query_activities_filter_by_wbs(self, sample_xer_single_project: Path) -> None:
|
|
"""Should filter activities by WBS ID."""
|
|
from xer_mcp.db.loader import load_parsed_data
|
|
from xer_mcp.db.queries import query_activities
|
|
from xer_mcp.parser.xer_parser import XerParser
|
|
|
|
parser = XerParser()
|
|
parsed = parser.parse(sample_xer_single_project)
|
|
load_parsed_data(parsed, project_id="1001")
|
|
|
|
activities, total = query_activities(wbs_id="102")
|
|
|
|
# WBS 102 has 2 activities
|
|
assert total == 2
|
|
|
|
def test_get_activity_by_id(self, sample_xer_single_project: Path) -> None:
|
|
"""Should return single activity by ID."""
|
|
from xer_mcp.db.loader import load_parsed_data
|
|
from xer_mcp.db.queries import get_activity_by_id
|
|
from xer_mcp.parser.xer_parser import XerParser
|
|
|
|
parser = XerParser()
|
|
parsed = parser.parse(sample_xer_single_project)
|
|
load_parsed_data(parsed, project_id="1001")
|
|
|
|
activity = get_activity_by_id("2002")
|
|
|
|
assert activity is not None
|
|
assert activity["task_code"] == "A1010"
|
|
|
|
def test_get_activity_by_id_not_found(self, sample_xer_single_project: Path) -> None:
|
|
"""Should return None for non-existent activity."""
|
|
from xer_mcp.db.loader import load_parsed_data
|
|
from xer_mcp.db.queries import get_activity_by_id
|
|
from xer_mcp.parser.xer_parser import XerParser
|
|
|
|
parser = XerParser()
|
|
parsed = parser.parse(sample_xer_single_project)
|
|
load_parsed_data(parsed, project_id="1001")
|
|
|
|
activity = get_activity_by_id("nonexistent")
|
|
|
|
assert activity is None
|