- Extended CONTEXT_ENGINEERING.md with additional optimization strategies - Added compaction configuration support to MCP server - Added tests for compaction config and MCP compaction Amp-Thread-ID: https://ampcode.com/threads/T-019b1f07-daa0-750c-878f-20bcc2d24f50 Co-authored-by: Amp <amp@ampcode.com>
433 lines
15 KiB
Python
433 lines
15 KiB
Python
"""Integration tests for CompactedResult compaction in MCP server.
|
|
|
|
Tests verify that large result sets are properly compacted to prevent context overflow.
|
|
These tests cover:
|
|
- CompactedResult structure and validation
|
|
- Compaction threshold behavior (triggers at >20 results)
|
|
- Preview count consistency (shows 5 items)
|
|
- Total count accuracy
|
|
- Both ready() and list() MCP tools
|
|
|
|
Note: Tests focus on the compaction logic itself, which is independent of the
|
|
FastMCP decorator. The actual tool behavior is tested via _apply_compaction().
|
|
"""
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
import pytest
|
|
|
|
from beads_mcp.models import CompactedResult, Issue, IssueMinimal
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_issues(count=25):
|
|
"""Create a list of sample issues for testing compaction.
|
|
|
|
Default creates 25 issues to exceed the COMPACTION_THRESHOLD (20).
|
|
"""
|
|
now = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
|
|
issues = []
|
|
for i in range(count):
|
|
issue = Issue(
|
|
id=f"bd-{i:04d}",
|
|
title=f"Issue {i}",
|
|
description=f"Description {i}",
|
|
status="open" if i % 2 == 0 else "in_progress",
|
|
priority=i % 5,
|
|
issue_type="bug" if i % 3 == 0 else "task",
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
issues.append(issue)
|
|
return issues
|
|
|
|
|
|
def _to_minimal_test(issue: Issue) -> IssueMinimal:
|
|
"""Convert Issue to IssueMinimal - copy of server's _to_minimal for testing."""
|
|
return IssueMinimal(
|
|
id=issue.id,
|
|
title=issue.title,
|
|
status=issue.status,
|
|
priority=issue.priority,
|
|
issue_type=issue.issue_type,
|
|
assignee=issue.assignee,
|
|
labels=issue.labels,
|
|
dependency_count=issue.dependency_count,
|
|
dependent_count=issue.dependent_count,
|
|
)
|
|
|
|
|
|
def _apply_compaction(
|
|
minimal_issues: list[IssueMinimal],
|
|
threshold: int = 20,
|
|
preview_count: int = 5,
|
|
hint_prefix: str = "",
|
|
) -> list[IssueMinimal] | CompactedResult:
|
|
"""Apply compaction logic - copy of server's compaction for testing.
|
|
|
|
This function contains the core compaction logic that's used in the MCP tools.
|
|
"""
|
|
if len(minimal_issues) > threshold:
|
|
return CompactedResult(
|
|
compacted=True,
|
|
total_count=len(minimal_issues),
|
|
preview=minimal_issues[:preview_count],
|
|
preview_count=preview_count,
|
|
hint=f"{hint_prefix}Showing {preview_count} of {len(minimal_issues)} issues. Use show(issue_id) for full details.",
|
|
)
|
|
return minimal_issues
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_connection_pool():
|
|
"""Reset connection pool before and after each test."""
|
|
from beads_mcp import tools
|
|
|
|
tools._connection_pool.clear()
|
|
yield
|
|
tools._connection_pool.clear()
|
|
|
|
|
|
class TestCompactedResultStructure:
|
|
"""Test CompactedResult model structure and validation."""
|
|
|
|
def test_compacted_result_model_valid(self):
|
|
"""Test CompactedResult model with valid data."""
|
|
minimal_issues = [
|
|
IssueMinimal(
|
|
id="bd-1",
|
|
title="Test 1",
|
|
status="open",
|
|
priority=1,
|
|
issue_type="bug",
|
|
),
|
|
IssueMinimal(
|
|
id="bd-2",
|
|
title="Test 2",
|
|
status="open",
|
|
priority=2,
|
|
issue_type="task",
|
|
),
|
|
]
|
|
|
|
result = CompactedResult(
|
|
compacted=True,
|
|
total_count=25,
|
|
preview=minimal_issues,
|
|
preview_count=2,
|
|
hint="Use show(issue_id) for full details",
|
|
)
|
|
|
|
assert result.compacted is True
|
|
assert result.total_count == 25
|
|
assert result.preview_count == 2
|
|
assert len(result.preview) == 2
|
|
assert result.preview[0].id == "bd-1"
|
|
assert result.hint == "Use show(issue_id) for full details"
|
|
|
|
def test_compacted_result_default_hint(self):
|
|
"""Test CompactedResult uses default hint message."""
|
|
result = CompactedResult(
|
|
compacted=True,
|
|
total_count=30,
|
|
preview=[],
|
|
preview_count=0,
|
|
)
|
|
|
|
assert result.hint == "Use show(issue_id) for full issue details"
|
|
|
|
|
|
class TestCompactionLogic:
|
|
"""Test core compaction logic."""
|
|
|
|
def test_below_threshold_returns_list(self):
|
|
"""Test with <20 results returns list (no compaction)."""
|
|
issues = [
|
|
Issue(
|
|
id=f"bd-{i:04d}",
|
|
title=f"Issue {i}",
|
|
description="",
|
|
status="open",
|
|
priority=1,
|
|
issue_type="task",
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
for i in range(10)
|
|
]
|
|
|
|
minimal = [_to_minimal_test(i) for i in issues]
|
|
result = _apply_compaction(minimal)
|
|
|
|
assert isinstance(result, list)
|
|
assert len(result) == 10
|
|
assert all(isinstance(issue, IssueMinimal) for issue in result)
|
|
|
|
def test_above_threshold_returns_compacted(self, sample_issues):
|
|
"""Test with >20 results returns CompactedResult."""
|
|
minimal = [_to_minimal_test(i) for i in sample_issues]
|
|
result = _apply_compaction(minimal)
|
|
|
|
assert isinstance(result, CompactedResult)
|
|
assert result.compacted is True
|
|
assert result.total_count == 25
|
|
assert result.preview_count == 5
|
|
assert len(result.preview) == 5
|
|
assert all(isinstance(issue, IssueMinimal) for issue in result.preview)
|
|
|
|
def test_compaction_preserves_order(self, sample_issues):
|
|
"""Test compaction shows first N issues in order."""
|
|
minimal = [_to_minimal_test(i) for i in sample_issues]
|
|
result = _apply_compaction(minimal)
|
|
|
|
# First 5 issues should match original order
|
|
preview = result.preview
|
|
for i, issue in enumerate(preview):
|
|
assert issue.id == f"bd-{i:04d}"
|
|
|
|
def test_exactly_threshold_no_compaction(self):
|
|
"""Test with exactly 20 items (at threshold) - no compaction."""
|
|
issues = [
|
|
Issue(
|
|
id=f"bd-{i:04d}",
|
|
title=f"Issue {i}",
|
|
description="",
|
|
status="open",
|
|
priority=i % 5,
|
|
issue_type="task",
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
for i in range(20)
|
|
]
|
|
|
|
minimal = [_to_minimal_test(i) for i in issues]
|
|
result = _apply_compaction(minimal)
|
|
|
|
# Should return list since len(issues) == 20, not > 20
|
|
assert isinstance(result, list)
|
|
assert len(result) == 20
|
|
|
|
def test_one_over_threshold_compacts(self):
|
|
"""Test with 21 items (just over threshold)."""
|
|
issues = [
|
|
Issue(
|
|
id=f"bd-{i:04d}",
|
|
title=f"Issue {i}",
|
|
description="",
|
|
status="open",
|
|
priority=i % 5,
|
|
issue_type="task",
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
for i in range(21)
|
|
]
|
|
|
|
minimal = [_to_minimal_test(i) for i in issues]
|
|
result = _apply_compaction(minimal)
|
|
|
|
# Should compact
|
|
assert isinstance(result, CompactedResult)
|
|
assert result.compacted is True
|
|
assert result.total_count == 21
|
|
assert result.preview_count == 5
|
|
|
|
def test_empty_result_set(self):
|
|
"""Test with empty results."""
|
|
result = _apply_compaction([])
|
|
|
|
assert isinstance(result, list)
|
|
assert len(result) == 0
|
|
|
|
|
|
class TestCompactedResultHint:
|
|
"""Test hint field behavior in CompactedResult."""
|
|
|
|
def test_compacted_result_hint_present(self, sample_issues):
|
|
"""Test compacted result includes helpful hint."""
|
|
minimal = [_to_minimal_test(i) for i in sample_issues]
|
|
result = _apply_compaction(minimal)
|
|
|
|
assert result.hint is not None
|
|
assert isinstance(result.hint, str)
|
|
assert len(result.hint) > 0
|
|
# Hint should guide user on how to proceed
|
|
assert "show" in result.hint.lower()
|
|
|
|
def test_compacted_result_hint_with_custom_prefix(self, sample_issues):
|
|
"""Test hint can have custom prefix."""
|
|
minimal = [_to_minimal_test(i) for i in sample_issues]
|
|
custom_hint = "Ready work: "
|
|
result = _apply_compaction(minimal, hint_prefix=custom_hint)
|
|
|
|
assert result.hint.startswith(custom_hint)
|
|
|
|
|
|
class TestCompactedResultDataTypes:
|
|
"""Test type conversions and data format in CompactedResult."""
|
|
|
|
def test_preview_items_are_minimal_format(self, sample_issues):
|
|
"""Test that preview items use IssueMinimal format."""
|
|
minimal = [_to_minimal_test(i) for i in sample_issues]
|
|
result = _apply_compaction(minimal)
|
|
|
|
# Check preview items have IssueMinimal fields only
|
|
for issue in result.preview:
|
|
assert isinstance(issue, IssueMinimal)
|
|
assert hasattr(issue, "id")
|
|
assert hasattr(issue, "title")
|
|
assert hasattr(issue, "status")
|
|
assert hasattr(issue, "priority")
|
|
|
|
def test_total_count_includes_all_results(self, sample_issues):
|
|
"""Test total_count reflects all matching issues, not just preview."""
|
|
minimal = [_to_minimal_test(i) for i in sample_issues]
|
|
result = _apply_compaction(minimal)
|
|
|
|
# total_count should be 25, preview_count should be 5
|
|
assert result.total_count > result.preview_count
|
|
assert result.preview_count == 5
|
|
assert len(result.preview) == result.preview_count
|
|
|
|
|
|
class TestCompactionConsistency:
|
|
"""Test consistency of compaction behavior across different scenarios."""
|
|
|
|
def test_multiple_calls_same_behavior(self, sample_issues):
|
|
"""Test that multiple calls with same data behave consistently."""
|
|
minimal = [_to_minimal_test(i) for i in sample_issues]
|
|
result1 = _apply_compaction(minimal)
|
|
result2 = _apply_compaction(minimal)
|
|
|
|
# Should be consistent
|
|
assert result1.total_count == result2.total_count
|
|
assert result1.preview_count == result2.preview_count
|
|
assert len(result1.preview) == len(result2.preview)
|
|
|
|
def test_custom_thresholds(self, sample_issues):
|
|
"""Test compaction with custom threshold."""
|
|
minimal = [_to_minimal_test(i) for i in sample_issues]
|
|
|
|
# With threshold=10, 25 items should compact
|
|
result_low = _apply_compaction(minimal, threshold=10, preview_count=3)
|
|
assert isinstance(result_low, CompactedResult)
|
|
assert result_low.preview_count == 3
|
|
assert len(result_low.preview) == 3
|
|
|
|
# With threshold=50, 25 items should NOT compact
|
|
result_high = _apply_compaction(minimal, threshold=50)
|
|
assert isinstance(result_high, list)
|
|
assert len(result_high) == 25
|
|
|
|
|
|
class TestConversionToMinimal:
|
|
"""Test conversion from full Issue to IssueMinimal."""
|
|
|
|
def test_issue_to_minimal_conversion(self):
|
|
"""Test converting Issue to IssueMinimal."""
|
|
issue = Issue(
|
|
id="bd-123",
|
|
title="Test Issue",
|
|
description="Long description with lots of detail",
|
|
design="Design notes",
|
|
status="open",
|
|
priority=1,
|
|
issue_type="bug",
|
|
assignee="alice",
|
|
labels=["urgent", "backend"],
|
|
dependency_count=2,
|
|
dependent_count=1,
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
|
|
minimal = _to_minimal_test(issue)
|
|
|
|
# Check minimal has only expected fields
|
|
assert minimal.id == "bd-123"
|
|
assert minimal.title == "Test Issue"
|
|
assert minimal.status == "open"
|
|
assert minimal.priority == 1
|
|
assert minimal.issue_type == "bug"
|
|
assert minimal.assignee == "alice"
|
|
assert minimal.labels == ["urgent", "backend"]
|
|
assert minimal.dependency_count == 2
|
|
assert minimal.dependent_count == 1
|
|
|
|
# Check it doesn't have full Issue fields
|
|
assert not hasattr(minimal, "description") or minimal.description is None
|
|
assert not hasattr(minimal, "design") or minimal.design is None
|
|
|
|
def test_minimal_is_much_smaller(self):
|
|
"""Verify IssueMinimal is significantly smaller than Issue (roughly 80% reduction)."""
|
|
now = datetime.now(timezone.utc)
|
|
issue = Issue(
|
|
id="bd-123",
|
|
title="Test Issue",
|
|
description="A" * 1000, # Long description
|
|
design="B" * 500, # Design notes
|
|
acceptance_criteria="C" * 500, # Acceptance criteria
|
|
notes="D" * 500, # Notes
|
|
status="open",
|
|
priority=1,
|
|
issue_type="bug",
|
|
assignee="alice",
|
|
labels=["a", "b", "c"],
|
|
dependency_count=5,
|
|
dependent_count=3,
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
|
|
issue_size = len(str(issue))
|
|
minimal = _to_minimal_test(issue)
|
|
minimal_size = len(str(minimal))
|
|
|
|
# Minimal should be significantly smaller
|
|
ratio = minimal_size / issue_size
|
|
assert ratio < 0.3, f"Minimal {ratio*100:.1f}% of full size (expected <30%)"
|
|
|
|
|
|
class TestCompactionWithFilters:
|
|
"""Test compaction behavior with filtered results."""
|
|
|
|
def test_filtered_results_below_threshold(self, sample_issues):
|
|
"""Test filtered results that stay below threshold don't compact."""
|
|
# Take only first 8 issues
|
|
filtered = sample_issues[:8]
|
|
minimal = [_to_minimal_test(i) for i in filtered]
|
|
result = _apply_compaction(minimal)
|
|
|
|
assert isinstance(result, list)
|
|
assert len(result) == 8
|
|
|
|
def test_mixed_filters_and_compaction(self):
|
|
"""Test that filters are applied before compaction decision."""
|
|
# Create 50 issues
|
|
now = datetime.now(timezone.utc)
|
|
issues = [
|
|
Issue(
|
|
id=f"bd-{i:04d}",
|
|
title=f"Issue {i}",
|
|
description="",
|
|
status="open" if i < 30 else "closed",
|
|
priority=i % 5,
|
|
issue_type="task",
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
for i in range(50)
|
|
]
|
|
|
|
# Simulate filtering to only open issues (first 30)
|
|
filtered = [i for i in issues if i.status == "open"]
|
|
assert len(filtered) == 30
|
|
minimal = [_to_minimal_test(i) for i in filtered]
|
|
result = _apply_compaction(minimal)
|
|
|
|
# Should compact since 30 > 20
|
|
assert isinstance(result, CompactedResult)
|
|
assert result.total_count == 30
|
|
assert result.preview_count == 5
|