........................................................................ [ 0%] ........................................................................ [ 1%] ........................................................................ [ 2%] ........................................................................ [ 3%] ........................................................................ [ 4%] ........................................................................ [ 5%] ........................................................................ [ 6%] ........................................................................ [ 7%] ........................................................................ [ 7%] ........................................................................ [ 8%] ........................................................................ [ 9%] ........................................................................ [ 10%] ........................................................................ [ 11%] ........................................................................ [ 12%] ........................................................................ [ 13%] ........................................................................ [ 14%] ........................................................................ [ 14%] ........................................................................ [ 15%] ........................................................................ [ 16%] ........................................................................ [ 17%] ....................................................FFFF.FF.FF..FF.FF... [ 18%] ........................................................................ [ 19%] ........................................................................ [ 20%] ........................................................................ [ 21%] ........................................................................ [ 21%] ........................................................................ [ 22%] ........................................................................ [ 23%] ........................................................................ [ 24%] ........................................................................ [ 25%] .............................................ssssssssssssssssssssss..... [ 26%] ..sssssssssssssssssssssss............................................... [ 27%] ........................................................................ [ 28%] ........................................................................ [ 28%] ........................................................................ [ 29%] ........................................................................ [ 30%] ........................................................................ [ 31%] ........................................................................ [ 32%] ........................................................................ [ 33%] ........................................................................ [ 34%] ........................................................................ [ 35%] ........................................................................ [ 36%] ........................................................................ [ 36%] ........................................................................ [ 37%] ........................................................................ [ 38%] ...................FFF.FFFFFF........................................... [ 39%] ........................................................................ [ 40%] ........................................................................ [ 41%] ......................................................................s. [ 42%] ..s..................................................................... [ 43%] ........................................................................ [ 43%] ........................................................................ [ 44%] ........................................................................ [ 45%] ........................................................................ [ 46%] ........................................................................ [ 47%] ........................................................................ [ 48%] ........................................................................ [ 49%] ........................................................................ [ 50%] ........................................................................ [ 50%] ..................ss..........................s......................... [ 51%] ........................................................................ [ 52%] ...........sssssss...................................................... [ 53%] ........................................................................ [ 54%] ........................................................................ [ 55%] ........................................................................ [ 56%] ........................................................................ [ 57%] ........................................................................ [ 57%] ........................................................................ [ 58%] ........................................................................ [ 59%] ........................................................................ [ 60%] ........................................................................ [ 61%] ........................................................................ [ 62%] ........................................................................ [ 63%] ........................................................................ [ 64%] ........................................................................ [ 65%] ........................................................................ [ 65%] ........................................................................ [ 66%] ........................................................................ [ 67%] ........................................................................ [ 68%] ........................................................................ [ 69%] ........................................................................ [ 70%] ........................................................................ [ 71%] ........................................................................ [ 72%] ..................F..................................................... [ 72%] ........................................................................ [ 73%] ........................................................................ [ 74%] ........................................................................ [ 75%] ........................................................................ [ 76%] ........................................................................ [ 77%] ........................................................................ [ 78%] ........................................................................ [ 79%] ........................................................................ [ 79%] ........................................................................ [ 80%] ........................................................................ [ 81%] ........................................................................ [ 82%] ........................................................................ [ 83%] ........................................................................ [ 84%] ........................................................................ [ 85%] ........................................................................ [ 86%] ........................................................................ [ 86%] ........................................................................ [ 87%] ........................................................................ [ 88%] ........................................................................ [ 89%] ........................................................................ [ 90%] ........................................................................ [ 91%] ........................................................................ [ 92%] ........................................................................ [ 93%] ........................................................................ [ 94%] ........................................................................ [ 94%] ........................................................................ [ 95%] ........................................................................ [ 96%] ........................................................................ [ 97%] ........................................................................ [ 98%] ........................................................................ [ 99%] ........................................................... [100%] =================================== FAILURES =================================== ____________ TestIncomingDocumentHandling.test_pdf_document_cached _____________ self = adapter = @pytest.mark.asyncio async def test_pdf_document_cached(self, adapter): """A PDF attachment should be downloaded, cached, typed as DOCUMENT.""" pdf_bytes = b"%PDF-1.4 fake content" with _mock_aiohttp_download(pdf_bytes): msg = make_message([make_attachment(filename="report.pdf", content_type="application/pdf")]) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] assert event.message_type == MessageType.DOCUMENT > assert len(event.media_urls) == 1 E AssertionError: assert 0 == 1 E + where 0 = len([]) E + where [] = MessageEvent(text='(The user sent a message with no text content)', message_type=, s...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 105138, tzinfo=datetime.timezone.utc)).media_urls tests/gateway/test_discord_document_handling.py:173: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document report.pdf: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file ____________ TestIncomingDocumentHandling.test_txt_content_injected ____________ self = adapter = @pytest.mark.asyncio async def test_txt_content_injected(self, adapter): """.txt file under 100KB should have its content injected into event.text.""" file_content = b"Hello from a text file" with _mock_aiohttp_download(file_content): msg = make_message( attachments=[make_attachment(filename="notes.txt", content_type="text/plain")], content="summarize this", ) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert "[Content of notes.txt]:" in event.text E AssertionError: assert '[Content of notes.txt]:' in 'summarize this' E + where 'summarize this' = MessageEvent(text='summarize this', message_type=, source=SessionSource(platform= adapter = @pytest.mark.asyncio async def test_md_content_injected(self, adapter): """.md file under 100KB should have its content injected.""" file_content = b"# Title\nSome markdown content" with _mock_aiohttp_download(file_content): msg = make_message( attachments=[make_attachment(filename="readme.md", content_type="text/markdown")], content="", ) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert "[Content of readme.md]:" in event.text E AssertionError: assert '[Content of readme.md]:' in '(The user sent a message with no text content)' E + where '(The user sent a message with no text content)' = MessageEvent(text='(The user sent a message with no text content)', message_type=, s...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 179102, tzinfo=datetime.timezone.utc)).text tests/gateway/test_discord_document_handling.py:210: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document readme.md: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file ____________ TestIncomingDocumentHandling.test_log_content_injected ____________ self = adapter = @pytest.mark.asyncio async def test_log_content_injected(self, adapter): """.log file under 100KB should be treated as text/plain and injected.""" file_content = b"BLE trace line 1\nBLE trace line 2" with _mock_aiohttp_download(file_content): msg = make_message( attachments=[make_attachment(filename="btsnoop_hci.log", content_type="text/plain")], content="please inspect this", ) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert "[Content of btsnoop_hci.log]:" in event.text E AssertionError: assert '[Content of btsnoop_hci.log]:' in 'please inspect this' E + where 'please inspect this' = MessageEvent(text='please inspect this', message_type=, source=SessionSource(platfor...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 187467, tzinfo=datetime.timezone.utc)).text tests/gateway/test_discord_document_handling.py:226: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document btsnoop_hci.log: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file _____ TestIncomingDocumentHandling.test_mid_sized_zip_under_32mb_is_cached _____ self = adapter = @pytest.mark.asyncio async def test_mid_sized_zip_under_32mb_is_cached(self, adapter): """A 25MB .zip should be accepted now that Discord documents allow up to 32MB.""" msg = make_message([ make_attachment( filename="bugreport.zip", content_type="application/zip", size=25 * 1024 * 1024, ) ]) with _mock_aiohttp_download(b"PK\x03\x04test"): await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert len(event.media_urls) == 1 E AssertionError: assert 0 == 1 E + where 0 = len([]) E + where [] = MessageEvent(text='(The user sent a message with no text content)', message_type=, s...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 199854, tzinfo=datetime.timezone.utc)).media_urls tests/gateway/test_discord_document_handling.py:262: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document bugreport.zip: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file ____________ TestIncomingDocumentHandling.test_zip_document_cached _____________ self = adapter = @pytest.mark.asyncio async def test_zip_document_cached(self, adapter): """A .zip file should be cached as a supported document.""" msg = make_message([ make_attachment(filename="archive.zip", content_type="application/zip") ]) with _mock_aiohttp_download(b"PK\x03\x04test"): await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert len(event.media_urls) == 1 E AssertionError: assert 0 == 1 E + where 0 = len([]) E + where [] = MessageEvent(text='(The user sent a message with no text content)', message_type=, s...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 208121, tzinfo=datetime.timezone.utc)).media_urls tests/gateway/test_discord_document_handling.py:276: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document archive.zip: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file _______ TestIncomingDocumentHandling.test_large_txt_cached_not_injected ________ self = adapter = @pytest.mark.asyncio async def test_large_txt_cached_not_injected(self, adapter): """.txt over 100KB should be cached but NOT injected into event.text.""" large_content = b"x" * (200 * 1024) with _mock_aiohttp_download(large_content): msg = make_message( attachments=[make_attachment(filename="big.txt", content_type="text/plain", size=len(large_content))], content="", ) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert len(event.media_urls) == 1 E AssertionError: assert 0 == 1 E + where 0 = len([]) E + where [] = MessageEvent(text='(The user sent a message with no text content)', message_type=, s...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 222938, tzinfo=datetime.timezone.utc)).media_urls tests/gateway/test_discord_document_handling.py:316: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document big.txt: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file _____ TestIncomingDocumentHandling.test_multiple_text_files_both_injected ______ self = adapter = @pytest.mark.asyncio async def test_multiple_text_files_both_injected(self, adapter): """Two text file attachments should both be injected into event.text in order.""" content1 = b"First file content" content2 = b"Second file content" call_count = 0 responses = [content1, content2] def make_session(_responses): idx = 0 class FakeSession: async def __aenter__(self): return self async def __aexit__(self, *_): pass def get(self, url, **kwargs): nonlocal idx data = _responses[idx % len(_responses)] idx += 1 resp = AsyncMock() resp.status = 200 resp.read = AsyncMock(return_value=data) resp.__aenter__ = AsyncMock(return_value=resp) resp.__aexit__ = AsyncMock(return_value=False) return resp return FakeSession() with patch("aiohttp.ClientSession", return_value=make_session([content1, content2])): msg = make_message( attachments=[ make_attachment(filename="file1.txt", content_type="text/plain"), make_attachment(filename="file2.txt", content_type="text/plain"), ], content="", ) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert "[Content of file1.txt]:" in event.text E AssertionError: assert '[Content of file1.txt]:' in '(The user sent a message with no text content)' E + where '(The user sent a message with no text content)' = MessageEvent(text='(The user sent a message with no text content)', message_type=, s...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 230815, tzinfo=datetime.timezone.utc)).text tests/gateway/test_discord_document_handling.py:364: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document file1.txt: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document file2.txt: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file _________ TestAllowAnyAttachment.test_unknown_type_cached_when_flag_on _________ self = adapter = @pytest.mark.asyncio async def test_unknown_type_cached_when_flag_on(self, adapter): """Flag on: unknown extension is cached as application/octet-stream.""" adapter.config.extra["allow_any_attachment"] = True with _mock_aiohttp_download(b"\x00\x01\x02 binary payload"): msg = make_message([ make_attachment(filename="weird.xyz", content_type="application/x-custom") ]) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert len(event.media_urls) == 1 E AssertionError: assert 0 == 1 E + where 0 = len([]) E + where [] = MessageEvent(text='(The user sent a message with no text content)', message_type=, s...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 249308, tzinfo=datetime.timezone.utc)).media_urls tests/gateway/test_discord_document_handling.py:428: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document weird.xyz: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file _ TestAllowAnyAttachment.test_unknown_type_no_content_type_becomes_octet_stream _ self = adapter = @pytest.mark.asyncio async def test_unknown_type_no_content_type_becomes_octet_stream(self, adapter): """Flag on + no content_type from discord: MIME falls back to octet-stream.""" adapter.config.extra["allow_any_attachment"] = True with _mock_aiohttp_download(b"raw bytes"): msg = make_message([ make_attachment(filename="mystery.bin", content_type=None) ]) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] assert event.message_type == MessageType.DOCUMENT > assert event.media_types == ["application/octet-stream"] E AssertionError: assert [] == ['application/octet-stream'] E E Right contains one more item: 'application/octet-stream' E Use -v to get more diff tests/gateway/test_discord_document_handling.py:450: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document mystery.bin: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file ____ TestAllowAnyAttachment.test_max_attachment_bytes_zero_means_unlimited _____ self = adapter = @pytest.mark.asyncio async def test_max_attachment_bytes_zero_means_unlimited(self, adapter): """max_attachment_bytes=0 disables the size cap entirely.""" adapter.config.extra["allow_any_attachment"] = True adapter.config.extra["max_attachment_bytes"] = 0 # 64 MiB — would normally exceed the historical 32 MiB hardcoded cap. with _mock_aiohttp_download(b"x" * 16): msg = make_message([ make_attachment( filename="huge.xyz", content_type="application/x-custom", size=64 * 1024 * 1024, ) ]) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert len(event.media_urls) == 1 E AssertionError: assert 0 == 1 E + where 0 = len([]) E + where [] = MessageEvent(text='(The user sent a message with no text content)', message_type=, s...ontext=None, internal=False, timestamp=datetime.datetime(2026, 8, 15, 5, 41, 26, 270427, tzinfo=datetime.timezone.utc)).media_urls tests/gateway/test_discord_document_handling.py:488: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: cdn.discordapp.com WARNING plugins.platforms.discord.adapter:adapter.py:5446 [Discord] Failed to cache document huge.xyz: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file Traceback (most recent call last): File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5401, in _handle_message raw_bytes = await self._cache_discord_document(att, ext) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py", line 5131, in _cache_discord_document raise ValueError( ValueError: Blocked unsafe attachment URL (SSRF protection): https://cdn.discordapp.com/attachments/fake/file ______ TestAllowAnyAttachment.test_allowlisted_doc_unchanged_when_flag_on ______ self = adapter = @pytest.mark.asyncio async def test_allowlisted_doc_unchanged_when_flag_on(self, adapter): """Flag on must not change handling of types already in SUPPORTED_DOCUMENT_TYPES. A .txt should still get its content inlined (the historical behavior), and the MIME should still be the canonical text/plain — not whatever discord guessed. """ adapter.config.extra["allow_any_attachment"] = True file_content = b"still a text file" with _mock_aiohttp_download(file_content): msg = make_message( attachments=[make_attachment(filename="notes.txt", content_type="text/plain")], content="check this", ) await adapter._handle_message(msg) event = adapter.handle_message.call_args[0][0] > assert "[Content of notes.txt]:" in event.text E AssertionError: assert '[Content of notes.txt]:' in 'check this' E + where 'check this' = MessageEvent(text='check this', message_type=, source=SessionSource(platform= monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701457d811f0> @pytest.mark.asyncio async def test_external_media_download_rejects_oversized_content_length(self, monkeypatch): import aiohttp class _Content: async def iter_chunked(self, _size): yield b"x" class _Response: url = "https://example.com/image.png" headers = {"Content-Length": "11"} content_type = "image/png" content = _Content() async def __aenter__(self): return self async def __aexit__(self, *_args): return None def raise_for_status(self): return None class _Session: async def __aenter__(self): return self async def __aexit__(self, *_args): return None def get(self, *_args, **_kwargs): return _Response() self.adapter._max_media_bytes = 10 monkeypatch.setattr(aiohttp, "ClientSession", lambda **_kwargs: _Session()) with pytest.raises(ValueError, match="exceeds Matrix limit"): > await self.adapter._download_external_media_with_cap( "https://example.com/image.png" ) tests/gateway/test_matrix.py:3276: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = url = 'https://example.com/image.png' async def _download_external_media_with_cap(self, url: str) -> tuple[bytes, str, str]: """Download external media while enforcing redirect safety and size caps.""" from tools.url_safety import is_safe_url if not is_safe_url(url): > raise ValueError("blocked unsafe media URL") E ValueError: blocked unsafe media URL gateway/platforms/matrix.py:1723: ValueError During handling of the above exception, another exception occurred: self = monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701457d811f0> @pytest.mark.asyncio async def test_external_media_download_rejects_oversized_content_length(self, monkeypatch): import aiohttp class _Content: async def iter_chunked(self, _size): yield b"x" class _Response: url = "https://example.com/image.png" headers = {"Content-Length": "11"} content_type = "image/png" content = _Content() async def __aenter__(self): return self async def __aexit__(self, *_args): return None def raise_for_status(self): return None class _Session: async def __aenter__(self): return self async def __aexit__(self, *_args): return None def get(self, *_args, **_kwargs): return _Response() self.adapter._max_media_bytes = 10 monkeypatch.setattr(aiohttp, "ClientSession", lambda **_kwargs: _Session()) > with pytest.raises(ValueError, match="exceeds Matrix limit"): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E AssertionError: Regex pattern did not match. E Expected regex: 'exceeds Matrix limit' E Actual message: 'blocked unsafe media URL' tests/gateway/test_matrix.py:3275: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com _ TestMatrixImageOnlyMediaNormalization.test_external_media_download_rejects_oversized_stream _ self = monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701457e09460> @pytest.mark.asyncio async def test_external_media_download_rejects_oversized_stream(self, monkeypatch): import aiohttp class _Content: async def iter_chunked(self, _size): yield b"12345" yield b"67890" yield b"!" class _Response: url = "https://example.com/image.png" headers = {} content_type = "image/png" content = _Content() async def __aenter__(self): return self async def __aexit__(self, *_args): return None def raise_for_status(self): return None class _Session: async def __aenter__(self): return self async def __aexit__(self, *_args): return None def get(self, *_args, **_kwargs): return _Response() self.adapter._max_media_bytes = 10 monkeypatch.setattr(aiohttp, "ClientSession", lambda **_kwargs: _Session()) with pytest.raises(ValueError, match="exceeds Matrix limit"): > await self.adapter._download_external_media_with_cap( "https://example.com/image.png" ) tests/gateway/test_matrix.py:3319: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = url = 'https://example.com/image.png' async def _download_external_media_with_cap(self, url: str) -> tuple[bytes, str, str]: """Download external media while enforcing redirect safety and size caps.""" from tools.url_safety import is_safe_url if not is_safe_url(url): > raise ValueError("blocked unsafe media URL") E ValueError: blocked unsafe media URL gateway/platforms/matrix.py:1723: ValueError During handling of the above exception, another exception occurred: self = monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701457e09460> @pytest.mark.asyncio async def test_external_media_download_rejects_oversized_stream(self, monkeypatch): import aiohttp class _Content: async def iter_chunked(self, _size): yield b"12345" yield b"67890" yield b"!" class _Response: url = "https://example.com/image.png" headers = {} content_type = "image/png" content = _Content() async def __aenter__(self): return self async def __aexit__(self, *_args): return None def raise_for_status(self): return None class _Session: async def __aenter__(self): return self async def __aexit__(self, *_args): return None def get(self, *_args, **_kwargs): return _Response() self.adapter._max_media_bytes = 10 monkeypatch.setattr(aiohttp, "ClientSession", lambda **_kwargs: _Session()) > with pytest.raises(ValueError, match="exceeds Matrix limit"): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E AssertionError: Regex pattern did not match. E Expected regex: 'exceeds Matrix limit' E Actual message: 'blocked unsafe media URL' tests/gateway/test_matrix.py:3318: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com _ TestMatrixImageOnlyMediaNormalization.test_external_media_download_rejects_unsafe_redirect _ self = monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701457d732f0> @pytest.mark.asyncio async def test_external_media_download_rejects_unsafe_redirect(self, monkeypatch): import aiohttp class _Content: async def iter_chunked(self, _size): yield b"ok" class _Response: url = "http://127.0.0.1/private.png" headers = {} content_type = "image/png" content = _Content() async def __aenter__(self): return self async def __aexit__(self, *_args): return None def raise_for_status(self): return None class _Session: async def __aenter__(self): return self async def __aexit__(self, *_args): return None def get(self, *_args, **_kwargs): return _Response() monkeypatch.setattr(aiohttp, "ClientSession", lambda **_kwargs: _Session()) with pytest.raises(ValueError, match="unsafe redirect"): > await self.adapter._download_external_media_with_cap( "https://example.com/image.png" ) tests/gateway/test_matrix.py:3359: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = url = 'https://example.com/image.png' async def _download_external_media_with_cap(self, url: str) -> tuple[bytes, str, str]: """Download external media while enforcing redirect safety and size caps.""" from tools.url_safety import is_safe_url if not is_safe_url(url): > raise ValueError("blocked unsafe media URL") E ValueError: blocked unsafe media URL gateway/platforms/matrix.py:1723: ValueError During handling of the above exception, another exception occurred: self = monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701457d732f0> @pytest.mark.asyncio async def test_external_media_download_rejects_unsafe_redirect(self, monkeypatch): import aiohttp class _Content: async def iter_chunked(self, _size): yield b"ok" class _Response: url = "http://127.0.0.1/private.png" headers = {} content_type = "image/png" content = _Content() async def __aenter__(self): return self async def __aexit__(self, *_args): return None def raise_for_status(self): return None class _Session: async def __aenter__(self): return self async def __aexit__(self, *_args): return None def get(self, *_args, **_kwargs): return _Response() monkeypatch.setattr(aiohttp, "ClientSession", lambda **_kwargs: _Session()) > with pytest.raises(ValueError, match="unsafe redirect"): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E AssertionError: Regex pattern did not match. E Expected regex: 'unsafe redirect' E Actual message: 'blocked unsafe media URL' tests/gateway/test_matrix.py:3358: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com _ TestMatrixImageOnlyMediaNormalization.test_external_media_download_rejects_non_image_content_type _ self = monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701457cf7050> @pytest.mark.asyncio async def test_external_media_download_rejects_non_image_content_type(self, monkeypatch): import aiohttp class _Content: async def iter_chunked(self, _size): yield b"" class _Response: url = "https://example.com/image.png" headers = {} content_type = "text/html" content = _Content() async def __aenter__(self): return self async def __aexit__(self, *_args): return None def raise_for_status(self): return None class _Session: async def __aenter__(self): return self async def __aexit__(self, *_args): return None def get(self, *_args, **_kwargs): return _Response() monkeypatch.setattr(aiohttp, "ClientSession", lambda **_kwargs: _Session()) with pytest.raises(ValueError, match="not an image"): > await self.adapter._download_external_media_with_cap( "https://example.com/image.png" ) tests/gateway/test_matrix.py:3406: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = url = 'https://example.com/image.png' async def _download_external_media_with_cap(self, url: str) -> tuple[bytes, str, str]: """Download external media while enforcing redirect safety and size caps.""" from tools.url_safety import is_safe_url if not is_safe_url(url): > raise ValueError("blocked unsafe media URL") E ValueError: blocked unsafe media URL gateway/platforms/matrix.py:1723: ValueError During handling of the above exception, another exception occurred: self = monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701457cf7050> @pytest.mark.asyncio async def test_external_media_download_rejects_non_image_content_type(self, monkeypatch): import aiohttp class _Content: async def iter_chunked(self, _size): yield b"" class _Response: url = "https://example.com/image.png" headers = {} content_type = "text/html" content = _Content() async def __aenter__(self): return self async def __aexit__(self, *_args): return None def raise_for_status(self): return None class _Session: async def __aenter__(self): return self async def __aexit__(self, *_args): return None def get(self, *_args, **_kwargs): return _Response() monkeypatch.setattr(aiohttp, "ClientSession", lambda **_kwargs: _Session()) > with pytest.raises(ValueError, match="not an image"): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E AssertionError: Regex pattern did not match. E Expected regex: 'not an image' E Actual message: 'blocked unsafe media URL' tests/gateway/test_matrix.py:3405: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com _ TestMatrixImageOnlyMediaNormalization.test_send_image_failure_log_redacts_signed_url _ self = caplog = <_pytest.logging.LogCaptureFixture object at 0x701457c2c110> @pytest.mark.asyncio async def test_send_image_failure_log_redacts_signed_url(self, caplog): from gateway.platforms.base import SendResult signed_url = "https://example.com/image.png?signature=secret-token#frag" self.adapter._download_external_media_with_cap = AsyncMock( side_effect=ValueError("download failed") ) self.adapter.send = AsyncMock(return_value=SendResult(success=True)) await self.adapter.send_image("!room:example.org", signed_url) > assert "https://example.com/image.png" in caplog.text E AssertionError: assert 'https://example.com/image.png' in 'WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com\nWARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection)\n' E + where 'WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com\nWARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection)\n' = <_pytest.logging.LogCaptureFixture object at 0x701457c2c110>.text tests/gateway/test_matrix.py:3422: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com WARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection) _ TestMatrixImageOnlyMediaNormalization.test_send_image_failure_response_does_not_expose_signed_url_query _ self = @pytest.mark.asyncio async def test_send_image_failure_response_does_not_expose_signed_url_query(self): from gateway.platforms.base import SendResult signed_url = "https://example.com/image.png?signature=secret-token" self.adapter._download_external_media_with_cap = AsyncMock( side_effect=ValueError("download failed") ) self.adapter.send = AsyncMock(return_value=SendResult(success=True)) await self.adapter.send_image("!room:example.org", signed_url) > sent_text = self.adapter.send.await_args.args[1] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E IndexError: tuple index out of range tests/gateway/test_matrix.py:3438: IndexError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com WARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection) _ TestMatrixImageOnlyMediaNormalization.test_send_image_failure_response_does_not_expose_signed_url_fragment _ self = @pytest.mark.asyncio async def test_send_image_failure_response_does_not_expose_signed_url_fragment(self): from gateway.platforms.base import SendResult signed_url = "https://example.com/image.png#fragment-secret" self.adapter._download_external_media_with_cap = AsyncMock( side_effect=ValueError("download failed") ) self.adapter.send = AsyncMock(return_value=SendResult(success=True)) await self.adapter.send_image("!room:example.org", signed_url) > sent_text = self.adapter.send.await_args.args[1] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E IndexError: tuple index out of range tests/gateway/test_matrix.py:3456: IndexError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com WARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection) _ TestMatrixImageOnlyMediaNormalization.test_send_image_failure_response_preserves_caption _ self = @pytest.mark.asyncio async def test_send_image_failure_response_preserves_caption(self): from gateway.platforms.base import SendResult signed_url = "https://example.com/image.png?signature=secret-token#fragment" self.adapter._download_external_media_with_cap = AsyncMock( side_effect=ValueError("download failed") ) self.adapter.send = AsyncMock(return_value=SendResult(success=True)) await self.adapter.send_image( "!room:example.org", signed_url, caption="Here is the image", ) > sent_text = self.adapter.send.await_args.args[1] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E IndexError: tuple index out of range tests/gateway/test_matrix.py:3478: IndexError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com WARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection) _ TestMatrixImageOnlyMediaNormalization.test_send_image_failure_log_still_redacts_signed_url _ self = caplog = <_pytest.logging.LogCaptureFixture object at 0x701457cfae10> @pytest.mark.asyncio async def test_send_image_failure_log_still_redacts_signed_url(self, caplog): from gateway.platforms.base import SendResult signed_url = "https://example.com/image.png?signature=secret-token#fragment" self.adapter._download_external_media_with_cap = AsyncMock( side_effect=ValueError("download failed") ) self.adapter.send = AsyncMock(return_value=SendResult(success=True)) await self.adapter.send_image("!room:example.org", signed_url) > assert "https://example.com/image.png" in caplog.text E AssertionError: assert 'https://example.com/image.png' in 'WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com\nWARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection)\n' E + where 'WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com\nWARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection)\n' = <_pytest.logging.LogCaptureFixture object at 0x701457cfae10>.text tests/gateway/test_matrix.py:3497: AssertionError ------------------------------ Captured log call ------------------------------- WARNING tools.url_safety:url_safety.py:351 Blocked request — DNS resolution failed for: example.com WARNING gateway.platforms.matrix:matrix.py:1689 Matrix: blocked unsafe image URL (SSRF protection) ____ TestGatewayPidState.test_get_running_pid_rejects_live_non_gateway_pid _____ self = tmp_path = PosixPath('/tmp/pytest-of-cube/pytest-0/test_get_running_pid_rejects_l0') monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x701467df1b80> def test_get_running_pid_rejects_live_non_gateway_pid(self, tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path)) pid_path = tmp_path / "gateway.pid" pid_path.write_text(str(os.getpid())) assert status.get_running_pid() is None # A legacy, world-readable PID is not trusted or removed by a status # probe; startup fails closed rather than touching an unsafe path. > assert pid_path.exists() E AssertionError: assert False E + where False = exists() E + where exists = PosixPath('/tmp/pytest-of-cube/pytest-0/test_get_running_pid_rejects_l0/gateway.pid').exists tests/gateway/test_status.py:60: AssertionError =============================== warnings summary =============================== tests/gateway/test_api_server.py: 105 warnings /home/cube/projects/richard/hermes-agent/tests/gateway/test_api_server.py:443: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys. https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config app["api_server_adapter"] = adapter tests/gateway/test_api_server_bind_guard.py::TestConnectBindGuard::test_refuses_ipv4_wildcard_without_key tests/gateway/test_api_server_bind_guard.py::TestConnectBindGuard::test_refuses_ipv6_wildcard_without_key tests/gateway/test_api_server_bind_guard.py::TestConnectBindGuard::test_refuses_loopback_without_key tests/gateway/test_weak_credential_guard.py::TestAPIServerPlaceholderKeyGuard::test_refuses_wildcard_with_placeholder_key tests/gateway/test_weak_credential_guard.py::TestAPIServerPlaceholderKeyGuard::test_refuses_wildcard_with_asterisk_key /home/cube/projects/richard/hermes-agent/gateway/platforms/api_server.py:4293: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys. https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config self._app["api_server_adapter"] = self tests/gateway/test_api_server_jobs.py: 40 warnings /home/cube/projects/richard/hermes-agent/tests/gateway/test_api_server_jobs.py:54: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys. https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config app["api_server_adapter"] = adapter tests/gateway/test_api_server_multimodal.py::TestChatCompletionsMultimodalHTTP::test_inline_image_preserved_to_run_agent tests/gateway/test_api_server_multimodal.py::TestChatCompletionsMultimodalHTTP::test_text_only_array_collapses_to_string tests/gateway/test_api_server_multimodal.py::TestChatCompletionsMultimodalHTTP::test_file_part_returns_400 tests/gateway/test_api_server_multimodal.py::TestChatCompletionsMultimodalHTTP::test_non_image_data_url_returns_400 tests/gateway/test_api_server_multimodal.py::TestResponsesMultimodalHTTP::test_input_image_canonicalized_and_forwarded tests/gateway/test_api_server_multimodal.py::TestResponsesMultimodalHTTP::test_input_file_returns_400 /home/cube/projects/richard/hermes-agent/tests/gateway/test_api_server_multimodal.py:132: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys. https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config app["api_server_adapter"] = adapter tests/gateway/test_api_server_runs.py: 22 warnings /home/cube/projects/richard/hermes-agent/tests/gateway/test_api_server_runs.py:46: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys. https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config app["api_server_adapter"] = adapter tests/gateway/test_cron_fire_webhook.py::test_valid_token_accepts_and_fires tests/gateway/test_cron_fire_webhook.py::test_invalid_token_401_and_no_fire tests/gateway/test_cron_fire_webhook.py::test_missing_token_401 tests/gateway/test_cron_fire_webhook.py::test_missing_job_id_400 tests/gateway/test_cron_fire_webhook.py::test_fire_does_not_require_api_server_key /home/cube/projects/richard/hermes-agent/tests/gateway/test_cron_fire_webhook.py:28: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys. https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config app["api_server_adapter"] = adapter tests/gateway/test_discord_race_polish.py::test_concurrent_joins_do_not_double_connect /home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py:2520: RuntimeWarning: coroutine 'DiscordAdapter._voice_timeout_handler' was never awaited self._voice_timeout_tasks[guild_id] = asyncio.ensure_future( Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_discord_race_polish.py::test_concurrent_joins_do_not_double_connect /home/cube/projects/richard/hermes-agent/plugins/platforms/discord/adapter.py:2379: RuntimeWarning: coroutine 'DiscordAdapter._voice_listen_loop' was never awaited self._voice_listen_tasks[guild_id] = asyncio.ensure_future( Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_google_chat.py::TestExtractMessagePayload::test_native_chat_api_format_drops_non_message_events /home/cube/projects/richard/hermes-agent/.venv/lib/python3.12/site-packages/psutil/__init__.py:999: RuntimeWarning: coroutine 'GoogleChatAdapter._dispatch_message' was never awaited reverse_ppid_map[ppid].append(pid) Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_google_chat.py::TestBuildMessageEvent::test_slash_command_yields_command_type /home/cube/miniconda3/lib/python3.12/unittest/mock.py:508: RuntimeWarning: coroutine 'GoogleChatAdapter._dispatch_message' was never awaited def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_loop_exception_handler.py::test_unhandled_transient_error_in_task_does_not_propagate_to_loop /home/cube/projects/richard/hermes-agent/tests/gateway/test_loop_exception_handler.py:203: RuntimeWarning: coroutine 'GoogleChatAdapter._dispatch_message' was never awaited gc.collect() Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_ntfy_plugin.py::TestOnMessage::test_message_with_other_tags_still_dispatched /home/cube/miniconda3/lib/python3.12/asyncio/tasks.py:865: RuntimeWarning: coroutine 'BasePlatformAdapter._keep_typing' was never awaited def shield(arg): Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_ntfy_plugin.py::TestOnMessage::test_source_chat_id_is_topic /home/cube/miniconda3/lib/python3.12/inspect.py:3076: RuntimeWarning: coroutine 'BasePlatformAdapter._keep_typing' was never awaited params = OrderedDict((param.name, param) for param in parameters) Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_ntfy_plugin.py::TestFatalErrorPropagation::test_401_sets_fatal_unauthorized /home/cube/miniconda3/lib/python3.12/json/encoder.py:414: RuntimeWarning: coroutine 'BasePlatformAdapter._keep_typing' was never awaited def _iterencode(o, _current_indent_level): Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_qqbot.py::TestDispatchPayload::test_op10_updates_heartbeat_interval /home/cube/projects/richard/hermes-agent/gateway/platforms/qqbot/adapter.py:833: RuntimeWarning: coroutine 'QQAdapter._send_identify' was never awaited self._create_task(self._send_identify()) Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_qqbot.py::TestOp7ServerReconnect::test_op7_closes_websocket /home/cube/projects/richard/hermes-agent/gateway/platforms/qqbot/adapter.py:866: RuntimeWarning: coroutine 'TestOp7ServerReconnect.test_op7_closes_websocket..FakeWS.close' was never awaited self._create_task(self._ws.close()) Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_qqbot.py::TestOp9InvalidSession::test_op9_not_resumable_clears_session /home/cube/projects/richard/hermes-agent/gateway/platforms/qqbot/adapter.py:883: RuntimeWarning: coroutine 'TestOp9InvalidSession.test_op9_not_resumable_clears_session..FakeWS.close' was never awaited self._create_task(self._ws.close()) Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_qqbot.py::TestOp9InvalidSession::test_op9_resumable_preserves_session /home/cube/projects/richard/hermes-agent/gateway/platforms/qqbot/adapter.py:883: RuntimeWarning: coroutine 'TestOp9InvalidSession.test_op9_resumable_preserves_session..FakeWS.close' was never awaited self._create_task(self._ws.close()) Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_slack.py::TestSendVideo::test_send_video_api_error_falls_back tests/gateway/test_stream_consumer.py::TestFilterAndAccumulate::test_segment_reset_preserves_think_state tests/gateway/test_whatsapp_connect.py::TestConnectCleanup::test_releases_lock_when_npm_install_fails /home/cube/miniconda3/lib/python3.12/unittest/mock.py:2217: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited def __init__(self, name, parent): Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_slack.py: 27 warnings /home/cube/projects/richard/hermes-agent/gateway/platforms/slack.py:2787: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited user_name = await self._resolve_user_name(user_id, chat_id=channel_id) Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_slack.py::TestBangPrefixCommands::test_bang_works_inside_thread tests/gateway/test_slack.py::TestThreadReplyHandling::test_thread_reply_without_mention_with_session_processed tests/gateway/test_slack.py::TestThreadReplyHandling::test_thread_reply_with_mention_strips_bot_id tests/gateway/test_slack.py::TestAssistantThreadLifecycle::test_message_uses_cached_assistant_thread_identity tests/gateway/test_slack_channel_session_scope.py::TestChannelSessionScopeShared::test_thread_reply_scopes_by_thread_even_when_shared tests/gateway/test_slack_channel_session_scope.py::TestThreadReplyAlwaysScopesByThread::test_thread_reply_keyed_by_thread_ts[True] tests/gateway/test_slack_channel_session_scope.py::TestThreadReplyAlwaysScopesByThread::test_thread_reply_keyed_by_thread_ts[False] /home/cube/projects/richard/hermes-agent/gateway/platforms/slack.py:2825: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited await self._fetch_thread_parent_text( Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_slack.py::TestBangPrefixCommands::test_bang_works_inside_thread tests/gateway/test_slack.py::TestAssistantThreadLifecycle::test_message_uses_cached_assistant_thread_identity tests/gateway/test_slack_channel_session_scope.py::TestChannelSessionScopeShared::test_thread_reply_scopes_by_thread_even_when_shared tests/gateway/test_slack_channel_session_scope.py::TestThreadReplyAlwaysScopesByThread::test_thread_reply_keyed_by_thread_ts[True] tests/gateway/test_slack_channel_session_scope.py::TestThreadReplyAlwaysScopesByThread::test_thread_reply_keyed_by_thread_ts[False] /home/cube/projects/richard/hermes-agent/.venv/lib/python3.12/site-packages/_pytest/stash.py:108: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited del self._storage[key] Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_slack_approval_buttons.py::TestSlackThreadContext::test_fetch_thread_context_includes_bot_parent tests/gateway/test_slack_approval_buttons.py::TestSlackThreadContext::test_fetch_thread_context_excludes_self_bot_replies tests/gateway/test_slack_approval_buttons.py::TestSlackThreadContext::test_fetch_thread_context_multi_workspace tests/gateway/test_slack_approval_buttons.py::TestSlackThreadContext::test_fetch_thread_parent_text_from_cache /home/cube/projects/richard/hermes-agent/gateway/platforms/slack.py:3421: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited name = await self._resolve_user_name(display_user, chat_id=channel_id) Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_slack_plugin_action_handlers.py::TestSlackAdapterPluginActionWiring::test_plugin_handler_invoked_with_slack_args /home/cube/miniconda3/lib/python3.12/unittest/mock.py:508: RuntimeWarning: coroutine 'SlackAdapter._socket_watchdog_loop' was never awaited def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_slash_access_dispatch.py::test_whoami_unrestricted_when_no_admin_list tests/gateway/test_stream_consumer.py::TestFilterAndAccumulate::test_segment_reset_preserves_think_state /home/cube/miniconda3/lib/python3.12/unittest/mock.py:2217: RuntimeWarning: coroutine 'SlackAdapter._socket_watchdog_loop' was never awaited def __init__(self, name, parent): Enable tracemalloc to get traceback where the object was allocated. See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info. tests/gateway/test_sms.py::TestStartupGuard::test_insecure_flag_does_not_set_fatal_error tests/gateway/test_sms.py::TestStartupGuard::test_insecure_flag_does_not_set_fatal_error tests/gateway/test_sms.py::TestStartupGuard::test_insecure_flag_allows_start_without_url tests/gateway/test_sms.py::TestStartupGuard::test_insecure_flag_allows_start_without_url tests/gateway/test_sms.py::TestStartupGuard::test_webhook_url_allows_start tests/gateway/test_sms.py::TestStartupGuard::test_webhook_url_allows_start /home/cube/projects/richard/hermes-agent/.venv/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:192: DeprecationWarning: Bare functions are deprecated, use async ones warnings.warn( tests/gateway/test_telegram_polling_receipts_multiprocess.py::test_multiprocess_records_preserve_distinct_update_receipts tests/gateway/test_telegram_polling_receipts_multiprocess.py::test_multiprocess_records_preserve_distinct_update_receipts tests/gateway/test_telegram_polling_receipts_multiprocess.py::test_multiprocess_record_and_cleanup_preserve_serial_authority tests/gateway/test_telegram_polling_receipts_multiprocess.py::test_multiprocess_record_and_cleanup_preserve_serial_authority tests/gateway/test_telegram_polling_receipts_multiprocess.py::test_process_crash_while_holding_receipt_lock_does_not_deadlock_next_process tests/gateway/test_telegram_polling_receipts_multiprocess.py::test_process_crash_while_holding_receipt_lock_does_not_deadlock_next_process /home/cube/miniconda3/lib/python3.12/multiprocessing/popen_fork.py:66: DeprecationWarning: This process (pid=4107229) is multi-threaded, use of fork() may lead to deadlocks in the child. self.pid = os.fork() -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html =========================== short test summary info ============================ FAILED tests/gateway/test_discord_document_handling.py::TestIncomingDocumentHandling::test_pdf_document_cached FAILED tests/gateway/test_discord_document_handling.py::TestIncomingDocumentHandling::test_txt_content_injected FAILED tests/gateway/test_discord_document_handling.py::TestIncomingDocumentHandling::test_md_content_injected FAILED tests/gateway/test_discord_document_handling.py::TestIncomingDocumentHandling::test_log_content_injected FAILED tests/gateway/test_discord_document_handling.py::TestIncomingDocumentHandling::test_mid_sized_zip_under_32mb_is_cached FAILED tests/gateway/test_discord_document_handling.py::TestIncomingDocumentHandling::test_zip_document_cached FAILED tests/gateway/test_discord_document_handling.py::TestIncomingDocumentHandling::test_large_txt_cached_not_injected FAILED tests/gateway/test_discord_document_handling.py::TestIncomingDocumentHandling::test_multiple_text_files_both_injected FAILED tests/gateway/test_discord_document_handling.py::TestAllowAnyAttachment::test_unknown_type_cached_when_flag_on FAILED tests/gateway/test_discord_document_handling.py::TestAllowAnyAttachment::test_unknown_type_no_content_type_becomes_octet_stream FAILED tests/gateway/test_discord_document_handling.py::TestAllowAnyAttachment::test_max_attachment_bytes_zero_means_unlimited FAILED tests/gateway/test_discord_document_handling.py::TestAllowAnyAttachment::test_allowlisted_doc_unchanged_when_flag_on FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_external_media_download_rejects_oversized_content_length FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_external_media_download_rejects_oversized_stream FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_external_media_download_rejects_unsafe_redirect FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_external_media_download_rejects_non_image_content_type FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_send_image_failure_log_redacts_signed_url FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_send_image_failure_response_does_not_expose_signed_url_query FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_send_image_failure_response_does_not_expose_signed_url_fragment FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_send_image_failure_response_preserves_caption FAILED tests/gateway/test_matrix.py::TestMatrixImageOnlyMediaNormalization::test_send_image_failure_log_still_redacts_signed_url FAILED tests/gateway/test_status.py::TestGatewayPidState::test_get_running_pid_rejects_live_non_gateway_pid 22 failed, 8116 passed, 59 skipped, 256 warnings in 557.24s (0:09:17)