The list_milestones tool was only returning start milestones (TT_Mile) and missing all finish milestones. P6 uses two task types for milestones: - TT_Mile = Start Milestone - TT_FinMile = Finish Milestone Changes: - Update query_milestones() to include both TT_Mile and TT_FinMile - Derive milestone_type from task_type when not explicitly set: - TT_Mile -> 'start' - TT_FinMile -> 'finish' - Add unit tests for milestone_type derivation from task_type This fixes the E-J Electric schedule returning 5 milestones instead of 62.
404 lines
11 KiB
Python
404 lines
11 KiB
Python
"""Unit tests for XER table handlers."""
|
|
|
|
|
|
class TestProjectHandler:
|
|
"""Tests for PROJECT table handler."""
|
|
|
|
def test_parse_project_row(self) -> None:
|
|
"""Handler should parse PROJECT row correctly."""
|
|
from xer_mcp.parser.table_handlers.project import ProjectHandler
|
|
|
|
handler = ProjectHandler()
|
|
|
|
# Minimal PROJECT fields
|
|
fields = [
|
|
"proj_id",
|
|
"proj_short_name",
|
|
"plan_start_date",
|
|
"plan_end_date",
|
|
]
|
|
values = ["1001", "Test Project", "2026-01-01 00:00", "2026-06-30 00:00"]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["proj_id"] == "1001"
|
|
assert result["proj_short_name"] == "Test Project"
|
|
assert result["plan_start_date"] == "2026-01-01T00:00:00"
|
|
assert result["plan_end_date"] == "2026-06-30T00:00:00"
|
|
|
|
def test_table_name(self) -> None:
|
|
"""Handler should report correct table name."""
|
|
from xer_mcp.parser.table_handlers.project import ProjectHandler
|
|
|
|
handler = ProjectHandler()
|
|
assert handler.table_name == "PROJECT"
|
|
|
|
|
|
class TestTaskHandler:
|
|
"""Tests for TASK table handler."""
|
|
|
|
def test_parse_task_row(self) -> None:
|
|
"""Handler should parse TASK row correctly."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
|
|
fields = [
|
|
"task_id",
|
|
"proj_id",
|
|
"wbs_id",
|
|
"task_code",
|
|
"task_name",
|
|
"task_type",
|
|
"status_code",
|
|
"target_start_date",
|
|
"target_end_date",
|
|
"total_float_hr_cnt",
|
|
"driving_path_flag",
|
|
]
|
|
values = [
|
|
"2001",
|
|
"1001",
|
|
"100",
|
|
"A1000",
|
|
"Site Prep",
|
|
"TT_Task",
|
|
"TK_NotStart",
|
|
"2026-01-02 07:00",
|
|
"2026-01-08 15:00",
|
|
"0",
|
|
"Y",
|
|
]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["task_id"] == "2001"
|
|
assert result["task_code"] == "A1000"
|
|
assert result["task_type"] == "TT_Task"
|
|
assert result["driving_path_flag"] is True
|
|
assert result["total_float_hr_cnt"] == 0.0
|
|
|
|
def test_table_name(self) -> None:
|
|
"""Handler should report correct table name."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
assert handler.table_name == "TASK"
|
|
|
|
def test_parse_early_dates(self) -> None:
|
|
"""Handler should parse early_start_date and early_end_date from TASK row."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
|
|
fields = [
|
|
"task_id",
|
|
"proj_id",
|
|
"wbs_id",
|
|
"task_code",
|
|
"task_name",
|
|
"task_type",
|
|
"status_code",
|
|
"target_start_date",
|
|
"target_end_date",
|
|
"early_start_date",
|
|
"early_end_date",
|
|
"total_float_hr_cnt",
|
|
"driving_path_flag",
|
|
]
|
|
values = [
|
|
"2001",
|
|
"1001",
|
|
"100",
|
|
"A1000",
|
|
"Site Prep",
|
|
"TT_Task",
|
|
"TK_NotStart",
|
|
"2026-01-02 07:00",
|
|
"2026-01-08 15:00",
|
|
"2026-01-02 07:00",
|
|
"2026-01-08 15:00",
|
|
"0",
|
|
"Y",
|
|
]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["early_start_date"] == "2026-01-02T07:00:00"
|
|
assert result["early_end_date"] == "2026-01-08T15:00:00"
|
|
|
|
def test_parse_missing_early_dates(self) -> None:
|
|
"""Handler should handle missing early dates gracefully."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
|
|
fields = [
|
|
"task_id",
|
|
"proj_id",
|
|
"task_code",
|
|
"task_name",
|
|
"task_type",
|
|
]
|
|
values = [
|
|
"2001",
|
|
"1001",
|
|
"A1000",
|
|
"Site Prep",
|
|
"TT_Task",
|
|
]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["early_start_date"] is None
|
|
assert result["early_end_date"] is None
|
|
|
|
def test_parse_milestone_type_start(self) -> None:
|
|
"""Handler should parse milestone_type='start' for start milestones."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
|
|
fields = [
|
|
"task_id",
|
|
"proj_id",
|
|
"task_code",
|
|
"task_name",
|
|
"task_type",
|
|
"milestone_type",
|
|
]
|
|
values = [
|
|
"2001",
|
|
"1001",
|
|
"MS-START",
|
|
"Project Start",
|
|
"TT_Mile",
|
|
"MS_Start",
|
|
]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["task_type"] == "TT_Mile"
|
|
assert result["milestone_type"] == "start"
|
|
|
|
def test_parse_milestone_type_finish(self) -> None:
|
|
"""Handler should parse milestone_type='finish' for finish milestones."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
|
|
fields = [
|
|
"task_id",
|
|
"proj_id",
|
|
"task_code",
|
|
"task_name",
|
|
"task_type",
|
|
"milestone_type",
|
|
]
|
|
values = [
|
|
"2002",
|
|
"1001",
|
|
"MS-END",
|
|
"Project Complete",
|
|
"TT_Mile",
|
|
"MS_Finish",
|
|
]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["task_type"] == "TT_Mile"
|
|
assert result["milestone_type"] == "finish"
|
|
|
|
def test_parse_milestone_type_null_for_non_milestones(self) -> None:
|
|
"""Handler should return None for milestone_type on non-milestone activities."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
|
|
fields = [
|
|
"task_id",
|
|
"proj_id",
|
|
"task_code",
|
|
"task_name",
|
|
"task_type",
|
|
]
|
|
values = [
|
|
"2003",
|
|
"1001",
|
|
"A1000",
|
|
"Regular Task",
|
|
"TT_Task",
|
|
]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["task_type"] == "TT_Task"
|
|
assert result["milestone_type"] is None
|
|
|
|
def test_parse_milestone_type_derived_from_tt_mile(self) -> None:
|
|
"""Handler should derive milestone_type='start' from task_type TT_Mile."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
|
|
# No explicit milestone_type field - derive from task_type
|
|
fields = [
|
|
"task_id",
|
|
"proj_id",
|
|
"task_code",
|
|
"task_name",
|
|
"task_type",
|
|
]
|
|
values = [
|
|
"2001",
|
|
"1001",
|
|
"MS-START",
|
|
"Project Start",
|
|
"TT_Mile",
|
|
]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["task_type"] == "TT_Mile"
|
|
assert result["milestone_type"] == "start"
|
|
|
|
def test_parse_milestone_type_derived_from_tt_finmile(self) -> None:
|
|
"""Handler should derive milestone_type='finish' from task_type TT_FinMile."""
|
|
from xer_mcp.parser.table_handlers.task import TaskHandler
|
|
|
|
handler = TaskHandler()
|
|
|
|
# TT_FinMile is a finish milestone
|
|
fields = [
|
|
"task_id",
|
|
"proj_id",
|
|
"task_code",
|
|
"task_name",
|
|
"task_type",
|
|
]
|
|
values = [
|
|
"2002",
|
|
"1001",
|
|
"MS-END",
|
|
"Project Complete",
|
|
"TT_FinMile",
|
|
]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["task_type"] == "TT_FinMile"
|
|
assert result["milestone_type"] == "finish"
|
|
|
|
|
|
class TestTaskpredHandler:
|
|
"""Tests for TASKPRED table handler."""
|
|
|
|
def test_parse_relationship_row(self) -> None:
|
|
"""Handler should parse TASKPRED row correctly."""
|
|
from xer_mcp.parser.table_handlers.taskpred import TaskpredHandler
|
|
|
|
handler = TaskpredHandler()
|
|
|
|
fields = [
|
|
"task_pred_id",
|
|
"task_id",
|
|
"pred_task_id",
|
|
"proj_id",
|
|
"pred_proj_id",
|
|
"pred_type",
|
|
"lag_hr_cnt",
|
|
]
|
|
values = ["3001", "2002", "2001", "1001", "1001", "PR_FS", "8"]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["task_pred_id"] == "3001"
|
|
assert result["task_id"] == "2002"
|
|
assert result["pred_task_id"] == "2001"
|
|
assert result["pred_type"] == "PR_FS"
|
|
assert result["lag_hr_cnt"] == 8.0
|
|
|
|
def test_table_name(self) -> None:
|
|
"""Handler should report correct table name."""
|
|
from xer_mcp.parser.table_handlers.taskpred import TaskpredHandler
|
|
|
|
handler = TaskpredHandler()
|
|
assert handler.table_name == "TASKPRED"
|
|
|
|
|
|
class TestProjwbsHandler:
|
|
"""Tests for PROJWBS table handler."""
|
|
|
|
def test_parse_wbs_row(self) -> None:
|
|
"""Handler should parse PROJWBS row correctly."""
|
|
from xer_mcp.parser.table_handlers.projwbs import ProjwbsHandler
|
|
|
|
handler = ProjwbsHandler()
|
|
|
|
fields = [
|
|
"wbs_id",
|
|
"proj_id",
|
|
"parent_wbs_id",
|
|
"wbs_short_name",
|
|
"wbs_name",
|
|
]
|
|
values = ["100", "1001", "", "ROOT", "Project Root"]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["wbs_id"] == "100"
|
|
assert result["proj_id"] == "1001"
|
|
assert result["parent_wbs_id"] == ""
|
|
assert result["wbs_short_name"] == "ROOT"
|
|
|
|
def test_table_name(self) -> None:
|
|
"""Handler should report correct table name."""
|
|
from xer_mcp.parser.table_handlers.projwbs import ProjwbsHandler
|
|
|
|
handler = ProjwbsHandler()
|
|
assert handler.table_name == "PROJWBS"
|
|
|
|
|
|
class TestCalendarHandler:
|
|
"""Tests for CALENDAR table handler."""
|
|
|
|
def test_parse_calendar_row(self) -> None:
|
|
"""Handler should parse CALENDAR row correctly."""
|
|
from xer_mcp.parser.table_handlers.calendar import CalendarHandler
|
|
|
|
handler = CalendarHandler()
|
|
|
|
fields = [
|
|
"clndr_id",
|
|
"clndr_name",
|
|
"day_hr_cnt",
|
|
"week_hr_cnt",
|
|
]
|
|
values = ["1", "Standard 5 Day", "8", "40"]
|
|
|
|
result = handler.parse_row(fields, values)
|
|
|
|
assert result is not None
|
|
assert result["clndr_id"] == "1"
|
|
assert result["clndr_name"] == "Standard 5 Day"
|
|
assert result["day_hr_cnt"] == 8.0
|
|
assert result["week_hr_cnt"] == 40.0
|
|
|
|
def test_table_name(self) -> None:
|
|
"""Handler should report correct table name."""
|
|
from xer_mcp.parser.table_handlers.calendar import CalendarHandler
|
|
|
|
handler = CalendarHandler()
|
|
assert handler.table_name == "CALENDAR"
|