Files
odysseus/tests/test_email_search_recovery.py
T
pewdiepie-archdaemon 2e8413a54a Preserve preview harness, editor, email and task improvements
Snapshot current maintainer-preview application changes and regression fixtures for integration into lab. Excludes local runtime data, evaluation outputs and source backups. Focused Python regression selection: 140 passed; full suite not certified.
2026-10-01 01:34:26 +00:00

86 lines
3.9 KiB
Python

import json
import pytest
from src.clean_agent_preview import email_search_recovery, email_search_result_empty, stream_preview
from src.tool_policy import ToolPolicy
from src.turn_contract import resolve_full_inventory_contract
@pytest.mark.parametrize('value', [
'No emails matched "drop off the keys address".',
{'stdout': 'No emails matched "keys".', 'exit_code': 0},
json.dumps({'output': json.dumps({'stdout': 'No emails matched "keys".', 'exit_code': 0})}),
])
def test_empty_results(value):
assert email_search_result_empty(value)
assert email_search_recovery(value, 0)
assert email_search_recovery(value, 1)
assert not email_search_recovery(value, 2)
@pytest.mark.parametrize('value', [
{'stdout': 'No emails matched "keys".', 'exit_code': 1},
{'stdout': 'No emails matched "keys".', 'stderr': 'Authentication failed'},
{'error': 'Timeout'}, '', None,
'UID 42: Key return address',
'Email body: No emails matched "keys".',
])
def test_nonempty_and_failed_results_do_not_trigger(value):
assert not email_search_result_empty(value)
@pytest.mark.asyncio
async def test_recovery_reaches_next_model_request(monkeypatch):
import src.clean_agent_preview as module
requests = []
executions = []
responses = iter([
{'tool_calls': [{'index': 0, 'id': 'c1', 'type': 'function', 'function': {
'name': 'mcp__email__search_emails', 'arguments': '{"query":"drop off the keys address"}'}}]},
{'tool_calls': [{'index': 0, 'id': 'c2', 'type': 'function', 'function': {
'name': 'mcp__email__search_emails', 'arguments': '{"query":"keys"}'}}]},
{'content': 'Found a possible message about returning keys.'},
])
class Response:
async def __aenter__(self): return self
async def __aexit__(self, *args): pass
def raise_for_status(self): pass
async def aiter_lines(self):
yield 'data: ' + json.dumps({'choices': [{'delta': next(responses)}]})
yield 'data: [DONE]'
class Client:
def __init__(self, **kwargs): pass
async def __aenter__(self): return self
async def __aexit__(self, *args): pass
def stream(self, *args, **kwargs):
requests.append(json.loads(json.dumps(kwargs['json'])))
return Response()
async def execute(block, **kwargs):
executions.append(block)
return block.tool_type, {'exit_code': 0, 'output': json.dumps({
'exit_code': 0, 'stdout': 'No emails matched "drop off the keys address".'
if len(executions) == 1 else 'UID 42: Key return address'})}
monkeypatch.setattr(module.httpx, 'AsyncClient', Client)
monkeypatch.setattr(module, 'execute_tool_block', execute)
import src.email_task_intent as intent_module
async def classify(*args, **kwargs):
return intent_module.EmailTaskIntent('read', ('email',), 'Find key return address')
monkeypatch.setattr(intent_module, 'classify_email_task', classify)
schema = {'type': 'function', 'function': {'name': 'mcp__email__search_emails',
'description': 'Search email', 'parameters': {'type': 'object',
'properties': {'query': {'type': 'string'}}, 'required': ['query']}}}
policy = ToolPolicy()
contract = resolve_full_inventory_contract(schemas=[schema], policy=policy)
chunks = [chunk async for chunk in stream_preview(
endpoint_url='http://test', model='Ajax', headers={}, turn_contract=contract,
messages=[{'role': 'user', 'content': 'Search my email, what address do I drop off the keys'}],
session_id='test-email-recovery', owner='fixture', disabled_tools=set(),
tool_policy=policy, max_rounds=3,
)]
assert len(executions) == 2, chunks
guidance = [m for m in requests[1]['messages'] if 'shorter targeted query' in str(m.get('content'))]
assert guidance
assert 'Read promising messages' in guidance[0]['content']
assert 'Preserve explicit account' in guidance[0]['content']