============================= test session starts ==============================
platform linux -- Python 3.11.6, pytest-8.4.2, pluggy-1.6.0 -- /home/lighthouse/esp32-dev/.cache/downlink-investigation-0.5.1/server/.venv/bin/python
cachedir: .pytest_cache
rootdir: /home/lighthouse/esp32-dev/.cache/downlink-investigation-0.5.1
plugins: anyio-4.15.1
collecting ... collected 6 items

server/tests/test_agent_downlink.py::test_direct_answer_without_message_updates FAILED
server/tests/test_agent_downlink.py::test_tool_run_and_terminal_result_are_separate[False] FAILED
server/tests/test_agent_downlink.py::test_tool_run_and_terminal_result_are_separate[True] FAILED
server/tests/test_agent_downlink.py::test_final_bound_redaction_and_new_run_reset FAILED
server/tests/test_agent_downlink.py::test_unselected_session_never_pushes_activity PASSED
server/tests/test_agent_downlink.py::test_codex_authoritative_final_is_preserved FAILED

=================================== FAILURES ===================================
__________________ test_direct_answer_without_message_updates __________________

downlink = (<starlette.testclient.TestClient object at 0x7f5c1408d310>, <fake_device.FakeDevice object at 0x7f5c14018690>, <function downlink.<locals>.post at 0x7f5c140860c0>, <device_gateway.control.pi.PiAdapter object at 0x7f5c1408c150>)

    def test_direct_answer_without_message_updates(downlink):
        client, device, post, adapter = downlink
        response = client.post('/api/agents/pi/sessions/s/input', json={'text':'哈喽哈喽'})
        assert response.status_code == 202 and adapter.rpc.inputs == ['哈喽哈喽']
        post('agent_start')
>       assert device.wait('agent_state', state='RUNNING')['final_text'] == ''
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       KeyError: 'final_text'

server/tests/test_agent_downlink.py:63: KeyError
____________ test_tool_run_and_terminal_result_are_separate[False] _____________

downlink = (<starlette.testclient.TestClient object at 0x7f5c14008350>, <fake_device.FakeDevice object at 0x7f5c140728d0>, <function downlink.<locals>.post at 0x7f5c140849a0>, <device_gateway.control.pi.PiAdapter object at 0x7f5c140724d0>)
failed = False

    @pytest.mark.parametrize('failed', [False, True])
    def test_tool_run_and_terminal_result_are_separate(downlink, failed):
        _, device, post, _ = downlink
        post('agent_start'); device.wait('agent_state', state='RUNNING')
        post('activity', activity_type='tool', activity='Running\nsleep 3', tool_name='bash')
        assert device.wait('agent_activity')['activity'] == 'Running\nsleep 3'
        post('activity', activity_type='text', activity='正在检查。')
        assert device.wait('agent_activity')['activity'] == '正在检查。'
        final = '依赖不可用，尚未完成。' if failed else '检查完成，结果正常。'
        post('agent_end', failed=failed, final_text=final)
        end = device.wait('agent_state', state='FAILED' if failed else 'DONE')
>       assert end['final_text'] == final
               ^^^^^^^^^^^^^^^^^
E       KeyError: 'final_text'

server/tests/test_agent_downlink.py:84: KeyError
_____________ test_tool_run_and_terminal_result_are_separate[True] _____________

downlink = (<starlette.testclient.TestClient object at 0x7f5c12f36290>, <fake_device.FakeDevice object at 0x7f5c12f35e50>, <function downlink.<locals>.post at 0x7f5c14085800>, <device_gateway.control.pi.PiAdapter object at 0x7f5c12f35190>)
failed = True

    @pytest.mark.parametrize('failed', [False, True])
    def test_tool_run_and_terminal_result_are_separate(downlink, failed):
        _, device, post, _ = downlink
        post('agent_start'); device.wait('agent_state', state='RUNNING')
        post('activity', activity_type='tool', activity='Running\nsleep 3', tool_name='bash')
        assert device.wait('agent_activity')['activity'] == 'Running\nsleep 3'
        post('activity', activity_type='text', activity='正在检查。')
        assert device.wait('agent_activity')['activity'] == '正在检查。'
        final = '依赖不可用，尚未完成。' if failed else '检查完成，结果正常。'
        post('agent_end', failed=failed, final_text=final)
        end = device.wait('agent_state', state='FAILED' if failed else 'DONE')
>       assert end['final_text'] == final
               ^^^^^^^^^^^^^^^^^
E       KeyError: 'final_text'

server/tests/test_agent_downlink.py:84: KeyError
_________________ test_final_bound_redaction_and_new_run_reset _________________

downlink = (<starlette.testclient.TestClient object at 0x7f5c12f36c90>, <fake_device.FakeDevice object at 0x7f5c14f19dd0>, <function downlink.<locals>.post at 0x7f5c12ecbec0>, <device_gateway.control.pi.PiAdapter object at 0x7f5c12f34250>)

    def test_final_bound_redaction_and_new_run_reset(downlink):
        _, device, post, _ = downlink
        post('agent_start'); device.wait('agent_state', state='RUNNING')
        final = '结果🙂 Authorization: Bearer private-credential\napi_key=private-key\n' + '中文🙂'*3000
        post('agent_end', final_text=final)
        end = device.wait('agent_state', state='DONE')
>       assert 0 < len(end['final_text'].encode()) <= 2048
                       ^^^^^^^^^^^^^^^^^
E       KeyError: 'final_text'

server/tests/test_agent_downlink.py:100: KeyError
_________________ test_codex_authoritative_final_is_preserved __________________

tmp_path = PosixPath('/tmp/pytest-of-lighthouse/pytest-925/test_codex_authoritative_final0')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f5c12f35450>

    def test_codex_authoritative_final_is_preserved(tmp_path, monkeypatch):
        async def check():
            projects = ProjectRegistry(tmp_path/'projects.json')
            projects.add(Project(id='p', name='Test', path=str(tmp_path)))
            adapter = CodexAdapter(projects)
            async def call(method, params):
                assert method == 'thread/read'
                return {'thread':dict(id='s', cwd=str(tmp_path), updatedAt=0, status={'type':'idle'})}
            async def observe(sid): pass
            adapter.call = call
            adapter.observe_session = observe
            monkeypatch.setattr(gateway, 'adapters', {'codex':adapter})
            await adapter.event(dict(method='turn/started', params={'threadId':'s', 'turn':{'id':'t'}}))
            await adapter.event(dict(method='item/completed', params={'threadId':'s', 'turnId':'t',
                'item':dict(id='final', type='agentMessage', phase='final_answer', text='Codex 结果。token=private-key')}))
            await adapter.event(dict(method='turn/completed', params={'threadId':'s', 'turn':{'id':'t', 'status':'completed'}}))
            payload = await gateway.agent_target_payload('agent:codex:s')
            assert payload['state'] == 'DONE' and payload['agent'] == 'codex'
            assert payload['final_text'].startswith('Codex 结果。') and 'private-key' not in payload['final_text']
>       asyncio.run(check())

server/tests/test_agent_downlink.py:141: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib64/python3.11/asyncio/runners.py:190: in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
/usr/lib64/python3.11/asyncio/runners.py:118: in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib64/python3.11/asyncio/base_events.py:653: in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    async def check():
        projects = ProjectRegistry(tmp_path/'projects.json')
        projects.add(Project(id='p', name='Test', path=str(tmp_path)))
        adapter = CodexAdapter(projects)
        async def call(method, params):
            assert method == 'thread/read'
            return {'thread':dict(id='s', cwd=str(tmp_path), updatedAt=0, status={'type':'idle'})}
        async def observe(sid): pass
        adapter.call = call
        adapter.observe_session = observe
        monkeypatch.setattr(gateway, 'adapters', {'codex':adapter})
        await adapter.event(dict(method='turn/started', params={'threadId':'s', 'turn':{'id':'t'}}))
        await adapter.event(dict(method='item/completed', params={'threadId':'s', 'turnId':'t',
            'item':dict(id='final', type='agentMessage', phase='final_answer', text='Codex 结果。token=private-key')}))
        await adapter.event(dict(method='turn/completed', params={'threadId':'s', 'turn':{'id':'t', 'status':'completed'}}))
        payload = await gateway.agent_target_payload('agent:codex:s')
        assert payload['state'] == 'DONE' and payload['agent'] == 'codex'
>       assert payload['final_text'].startswith('Codex 结果。') and 'private-key' not in payload['final_text']
               ^^^^^^^^^^^^^^^^^^^^^
E       KeyError: 'final_text'

server/tests/test_agent_downlink.py:140: KeyError
=============================== warnings summary ===============================
server/.venv/lib64/python3.11/site-packages/starlette/testclient.py:51
  /home/lighthouse/esp32-dev/.cache/downlink-investigation-0.5.1/server/.venv/lib64/python3.11/site-packages/starlette/testclient.py:51: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED server/tests/test_agent_downlink.py::test_direct_answer_without_message_updates - KeyError: 'final_text'
FAILED server/tests/test_agent_downlink.py::test_tool_run_and_terminal_result_are_separate[False] - KeyError: 'final_text'
FAILED server/tests/test_agent_downlink.py::test_tool_run_and_terminal_result_are_separate[True] - KeyError: 'final_text'
FAILED server/tests/test_agent_downlink.py::test_final_bound_redaction_and_new_run_reset - KeyError: 'final_text'
FAILED server/tests/test_agent_downlink.py::test_codex_authoritative_final_is_preserved - KeyError: 'final_text'
==================== 5 failed, 1 passed, 1 warning in 0.63s ====================
