Files
odysseus/tests/test_contact_tool_partial_update.py
T

83 lines
3.8 KiB
Python

"""Tool follow-up updates must preserve fields the user did not change."""
import json
from types import SimpleNamespace
import httpx
import pytest
from fastapi import FastAPI
import routes.contacts_routes as contacts
from src.tools.contacts import do_manage_contact
OWNER = 'sft_contact_fixture'
@pytest.fixture
def contact_app(monkeypatch, tmp_path):
monkeypatch.setattr(contacts, 'DATA_DIR', tmp_path)
monkeypatch.setattr(contacts, 'LOCAL_CONTACTS_FILE', tmp_path / 'contacts.json')
monkeypatch.setattr(contacts, 'SETTINGS_FILE', tmp_path / 'settings.json')
monkeypatch.setattr(contacts, '_contact_cache', {
'contacts': [], 'by_owner': {}, 'fetched_at': None, 'failed_at': None,
})
app = FastAPI()
app.state.auth_manager = SimpleNamespace(is_configured=True, is_admin=lambda user: user == OWNER)
@app.middleware('http')
async def test_identity(request, call_next):
request.state.current_user = OWNER
return await call_next(request)
app.include_router(contacts.setup_contacts_routes())
return app
@pytest.mark.asyncio
@pytest.mark.parametrize('patch,expected', [
({'name': 'Casey Revised'}, {'name': 'Casey Revised'}),
({'email': 'revised@example.test'}, {'emails': ['revised@example.test']}),
({'phones': ['+1-202-555-0168']}, {'phones': ['+1-202-555-0168']}),
({'emails': []}, {'emails': []}),
({'phones': []}, {'phones': []}),
])
async def test_tool_patch_retains_unspecified_contact_fields(contact_app, patch, expected):
result = await do_manage_contact(json.dumps({
'action': 'add', 'name': 'Casey Example', 'email': 'casey@example.test',
'phones': ['+1-202-555-0142'], 'address': '42 Fixture Street',
}), owner=OWNER)
assert result['exit_code'] == 0
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=contact_app), base_url='http://test') as client:
original = (await client.get('/api/contacts/list')).json()['contacts'][0]
result = await do_manage_contact(json.dumps({
'action': 'update', 'uid': original['uid'], **patch,
}), owner=OWNER)
assert result['exit_code'] == 0
updated = (await client.get('/api/contacts/list')).json()['contacts'][0]
assert updated == {**original, **expected}
@pytest.mark.asyncio
async def test_contact_search_returns_phone_and_address_evidence(contact_app):
await do_manage_contact(json.dumps({
'action': 'add', 'name': 'Casey Example', 'email': 'casey@example.test',
'phones': ['+1-202-555-0142'], 'address': '42 Fixture Street',
}), owner=OWNER)
result = await do_manage_contact(json.dumps({'action': 'search', 'query': 'Casey'}), owner=OWNER)
assert result['exit_code'] == 0
assert '+1-202-555-0142' in result['output']
assert '42 Fixture Street' in result['output']
@pytest.mark.asyncio
@pytest.mark.parametrize('target', ['missing', 'other-owner'])
async def test_contact_tool_does_not_create_or_modify_an_unavailable_target(contact_app, target):
other_owner = 'sft_other_contact_fixture'
await do_manage_contact(json.dumps({'action': 'add', 'name': 'Other Contact',
'email': 'other@example.test'}), owner=other_owner)
before = await do_manage_contact('{"action":"list"}', owner=other_owner)
import re
uid = re.search(r'\[uid=([^\]]+)\]', before['output']).group(1) if target == 'other-owner' else 'missing'
result = await do_manage_contact(json.dumps({'action': 'update', 'uid': uid, 'name': 'Changed'}), owner=OWNER)
assert result['exit_code'] == 1
assert await do_manage_contact('{"action":"list"}', owner=other_owner) == before
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=contact_app), base_url='http://test') as client:
assert (await client.get('/api/contacts/list')).json()['contacts'] == []