Files
odysseus/tests/test_failed_call_correction.py
T
Alexandre Teixeira 872888aa4a test(runtime): migrate Wave 3 legacy test suites to resource authority contracts
Migrate 28 legacy test failures to exercise behavior under valid sealed
RequestAuthority, native process reservations, sealed filesystem roots,
and external bridge contexts, or assert fail-closed unscoped behavior.
Preserves all design invariants without weakening production authority.
2026-10-02 18:54:09 +01:00

79 lines
3.8 KiB
Python

"""Real note storage and dispatcher, with only the model HTTP boundary scripted."""
import json
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from core import database
from src.clean_agent_preview import stream_preview
from src.tool_policy import ToolPolicy
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.turn_contract import resolve_full_inventory_contract
from src.tools.notes import do_manage_notes
@pytest.mark.asyncio
@pytest.mark.parametrize('repeats', [2, 3])
async def test_corrected_ids_execute_after_repeated_ambiguous_title_failures(tmp_path, monkeypatch, repeats):
import src.clean_agent_preview as preview
engine = create_engine(f'sqlite:///{tmp_path / "notes.db"}')
database.Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine)
monkeypatch.setattr(database, 'SessionLocal', factory)
with factory() as db:
for id, title in [('target-a', 'same-title'), ('target-b', 'same-title'), ('keep-c', 'keep-me')]:
db.add(database.Note(id=id, title=title, owner='fixture-owner', content='fixture'))
db.commit()
async def read(id):
return await do_manage_notes(json.dumps({'action': 'view', 'id': id}), owner='fixture-owner')
before = await read('keep-c')
def calls(*args):
return {'tool_calls': [{'index': i, 'id': f'call-{counter[0]}-{i}', 'function': {
'name': 'manage_notes', 'arguments': json.dumps(a)}} for i, a in enumerate(args)]}
counter = [0]
responses = iter([calls({'action': 'delete', 'title': 'same-title'}) for _ in range(repeats)] + [
calls({'action': 'delete', 'id': 'target-a'}, {'action': 'delete', 'id': 'target-b'}),
{'content': 'Deleted both notes.'},
])
requests = []
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):
counter[0] += 1
delta = next(responses)
for call in delta.get('tool_calls', []):
call['id'] = f'call-{counter[0]}-{call["index"]}'
yield 'data: ' + json.dumps({'choices': [{'delta': delta}]})
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(kwargs['json'])
return Response()
monkeypatch.setattr(preview.httpx, 'AsyncClient', Client)
schema = next(s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] == 'manage_notes')
policy = ToolPolicy()
contract = resolve_full_inventory_contract(schemas=[schema], policy=policy)
try:
raw = [chunk async for chunk in stream_preview(endpoint_url='http://model.test', model='test',
messages=[{'role': 'user', 'content': 'Delete both notes with the title same-title; keep keep-me.'}],
headers={}, turn_contract=contract, session_id='fixture-delete', owner='fixture-owner',
disabled_tools=set(), tool_policy=policy, max_rounds=8)]
events = [json.loads(chunk[6:]) for chunk in raw if '[DONE]' not in chunk]
assert (await read('target-a'))['exit_code'] == 0
assert (await read('target-b'))['exit_code'] == 0
assert await read('keep-c') == before
outputs = [e for e in events if e.get('type') == 'tool_output']
attempts = [e for e in outputs if e.get('execution_attempted')]
assert len(attempts) == 1
assert attempts[0]['blocked'] is True
assert 'missing or ambiguous' in attempts[0]['output']
assert any('No changes were made' in e.get('content', '') for e in events if e.get('type') == 'final_response')
finally:
engine.dispose()