Files
tatlock/tests/integration/test_steward_tatlock_integration.py
jpmschweitzerandClaude 78066fab1b style: apply ruff's automatic fixes and formatter
Mechanical only, and separated from the judgment calls that follow so the
reviewable changes are not buried in a 98-file whitespace diff.

227 automatic fixes: 60 blank lines carrying whitespace, 60 unsorted import
blocks, 34 Optional[X] to X | None, 28 unused imports, 16 deprecated typing
imports, 12 datetime.timezone.utc to datetime.UTC, and assorted smaller
modernisations. Then `ruff format` over src and tests: 98 files reformatted,
35 already conforming.

No file among the unused-import findings defines __all__ or is an __init__.py,
so nothing here removes a re-export.

`make test`: 658 passed, unchanged from HEAD.

Two things observed while verifying, neither addressed here:

`pytest tests/` cannot collect — tests/e2e/test_orchestration_e2e.py uses an
`e2e` marker that is not registered, and the config is strict about markers.
This fails identically at HEAD, so it predates this change; `make test` passes
because it ignores tests/e2e, tests/integration and tests/contracts.

test_tatlock_tool_call_logging_calculator is flaky. It failed once in a full run
with these changes and passed on the next, passes in isolation with them, and
fails in isolation at HEAD. It is order- or timing-dependent, not a regression
from this commit — established by running the full suite both ways rather than
by reasoning about which change could have caused it.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 17:25:18 +02:00

264 lines
11 KiB
Python

"""
Integration tests for Steward → Tatlock flow.
Tests the complete Phase 2 request pipeline:
1. Steward analyzes request and recommends capabilities
2. Tool tracker monitors tool usage
3. Tatlock runs with scoped tools
4. Response includes both Steward reasoning and Tatlock output
"""
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from src.core.startup import initialize_application
from src.responses.schemas import ResponseRequest
from src.responses.service import create_response_with_steward
@pytest.fixture(scope="module", autouse=True)
def setup_household_registry():
"""Initialize household registry before running tests."""
initialize_application()
class TestStewardTatlockIntegration:
"""Test full Steward → Tatlock integration flow."""
@pytest.mark.asyncio
async def test_simple_math_request(self):
"""Test math request flows through Steward → Tatlock correctly."""
# Create a simple math request
request = ResponseRequest(
model="tatlock",
input=[{"role": "user", "content": "What's 2 + 2?"}],
)
# Mock the Steward analysis
with patch("src.core.preprocessing.analyze_request") as mock_steward:
with patch("src.agents.tatlock.TatlockAgent.run_with_scoped_tools") as mock_tatlock:
from src.agents.steward.schemas import ConversationContext, StewardRecommendation
# Mock Steward recommendation
mock_steward.return_value = StewardRecommendation(
recommended_capabilities=["tatlock_core"],
reasoning="Math calculation requires tatlock_core",
estimated_complexity="simple",
conversation_context=ConversationContext(has_previous_context=False),
)
# Mock Tatlock response
mock_tatlock.return_value = "Certainly, sir. 2 + 2 equals 4."
# Execute the flow
response = await create_response_with_steward(request)
# Verify Steward was called
assert mock_steward.called
# Note: preprocess_request injects temporal context
steward_call_arg = mock_steward.call_args[0][0]
assert steward_call_arg.startswith(
"What's 2 + 2?"
), f"Expected request to start with original message, got: {steward_call_arg}"
# Verify Tatlock was called with scoped tools
assert mock_tatlock.called
# Verify response structure
assert response.status == "completed"
assert len(response.output) == 2 # Reasoning + Message
# Check Steward reasoning output
reasoning_item = response.output[0]
assert reasoning_item.type == "reasoning"
assert "Math calculation" in reasoning_item.summary[1]
# Check Tatlock message output
message_item = response.output[1]
assert message_item.type == "message"
assert message_item.role == "assistant"
assert "4" in message_item.content[0].text
@pytest.mark.asyncio
async def test_request_with_conversation_history(self):
"""Test that conversation history flows through to Steward."""
request = ResponseRequest(
model="tatlock",
input=[
{"role": "user", "content": "What's 5 times 3?"},
{"role": "assistant", "content": "That equals 15, sir."},
{"role": "user", "content": "And divided by 3?"},
],
)
with patch("src.core.preprocessing.analyze_request") as mock_steward:
with patch("src.agents.tatlock.TatlockAgent.run_with_scoped_tools") as mock_tatlock:
from src.agents.steward.schemas import ConversationContext, StewardRecommendation
mock_steward.return_value = StewardRecommendation(
recommended_capabilities=["tatlock_core"],
reasoning="Follow-up calculation",
estimated_complexity="simple",
conversation_context=ConversationContext(
has_previous_context=True,
relevant_turns=[0],
context_summary="Previous calculation in turn 0",
),
)
mock_tatlock.return_value = "15 divided by 3 equals 5, sir."
response = await create_response_with_steward(request)
# Verify Steward received conversation history
call_kwargs = mock_steward.call_args[1]
assert "conversation_history" in call_kwargs
assert len(call_kwargs["conversation_history"]) == 2 # First Q&A pair
# Verify Tatlock received history
tatlock_kwargs = mock_tatlock.call_args[1]
assert "message_history" in tatlock_kwargs
# Verify response completed
assert response.status == "completed"
@pytest.mark.asyncio
async def test_no_capabilities_needed(self):
"""Test simple conversational request that needs no tools."""
request = ResponseRequest(
model="tatlock",
input=[{"role": "user", "content": "Hello!"}],
)
with patch("src.core.preprocessing.analyze_request") as mock_steward:
with patch("src.agents.tatlock.TatlockAgent.run_with_scoped_tools") as mock_tatlock:
from src.agents.steward.schemas import ConversationContext, StewardRecommendation
mock_steward.return_value = StewardRecommendation(
recommended_capabilities=[], # No tools needed
reasoning="Simple greeting, no tools required",
estimated_complexity="simple",
conversation_context=ConversationContext(has_previous_context=False),
)
mock_tatlock.return_value = "Good day, sir. How may I assist you?"
response = await create_response_with_steward(request)
# Verify empty scoped tools were passed
tatlock_kwargs = mock_tatlock.call_args[1]
assert "scoped_tools" in tatlock_kwargs
assert tatlock_kwargs["scoped_tools"] == [] # No tools
assert response.status == "completed"
@pytest.mark.asyncio
async def test_tool_tracker_integration(self):
"""Test that tool tracker is passed to Tatlock and finalized."""
request = ResponseRequest(
model="tatlock",
input=[{"role": "user", "content": "Calculate sqrt(16)"}],
)
with patch("src.core.preprocessing.analyze_request") as mock_steward:
with patch("src.agents.tatlock.TatlockAgent.run_with_scoped_tools") as mock_tatlock:
with patch("src.core.tool_tracking.ToolCallTracker.finalize") as mock_finalize:
from src.agents.steward.schemas import (
ConversationContext,
StewardRecommendation,
)
mock_steward.return_value = StewardRecommendation(
recommended_capabilities=["tatlock_core"],
reasoning="Calculator needed",
estimated_complexity="simple",
conversation_context=ConversationContext(has_previous_context=False),
)
mock_tatlock.return_value = "The square root of 16 is 4, sir."
response = await create_response_with_steward(request)
# Verify tool tracker was finalized
assert mock_finalize.called
assert response.status == "completed"
@pytest.mark.asyncio
async def test_missing_capabilities_warning(self):
"""Test that missing capabilities are included in Steward's reasoning."""
request = ResponseRequest(
model="tatlock",
input=[{"role": "user", "content": "Generate an image of a sunset"}],
)
with patch("src.core.preprocessing.analyze_request") as mock_steward:
with patch("src.agents.tatlock.TatlockAgent.run_with_scoped_tools") as mock_tatlock:
from src.agents.steward.schemas import ConversationContext, StewardRecommendation
mock_steward.return_value = StewardRecommendation(
recommended_capabilities=[],
reasoning="Image generation not available",
estimated_complexity="simple",
conversation_context=ConversationContext(has_previous_context=False),
missing_capabilities="Image generation capability would be needed",
)
mock_tatlock.return_value = (
"I'm afraid I don't have image generation capabilities, sir."
)
response = await create_response_with_steward(request)
# Verify Steward's reasoning mentions missing capabilities
reasoning_item = response.output[0]
assert "not available" in reasoning_item.summary[1].lower()
assert response.status == "completed"
@pytest.mark.asyncio
async def test_conversation_id_propagation(self):
"""Test that conversation ID flows through entire pipeline."""
request = ResponseRequest(
model="tatlock",
input=[{"role": "user", "content": "Test request"}],
metadata={"conversation_id": "test_conv_123"},
)
with patch("src.core.preprocessing.analyze_request") as mock_steward:
with patch("src.agents.tatlock.TatlockAgent.run_with_scoped_tools") as mock_tatlock:
with patch("src.responses.service.ToolCallTracker") as mock_tracker_class:
from src.agents.steward.schemas import (
ConversationContext,
StewardRecommendation,
)
mock_steward.return_value = StewardRecommendation(
recommended_capabilities=["tatlock_core"],
reasoning="Test",
estimated_complexity="simple",
conversation_context=ConversationContext(has_previous_context=False),
)
mock_tatlock.return_value = "Test response"
mock_tracker = MagicMock()
mock_tracker.get_summary = MagicMock(return_value={})
mock_tracker.finalize = AsyncMock()
mock_tracker_class.return_value = mock_tracker
response = await create_response_with_steward(request)
# Verify conversation ID was passed to Steward
steward_kwargs = mock_steward.call_args[1]
assert steward_kwargs.get("conversation_id") == "test_conv_123"
# Verify conversation ID was passed to tracker
assert mock_tracker_class.called
tracker_call_args = mock_tracker_class.call_args
if tracker_call_args and len(tracker_call_args) > 1:
tracker_init_kwargs = tracker_call_args[1]
assert tracker_init_kwargs.get("conversation_id") == "test_conv_123"
assert response.status == "completed"