"""Comprehensive unit tests for Matrix channel adapter — edge cases and uncovered modules. Covers: dedupe, security, allowlist, rooms_config, crypto, auto_join, directory, polls async, config_schema (pattern/enum/min/max/type), accounts remaining, credentials encrypted, draft_stream, media_meta, profile_sync edges, sync_state edges, sent_cache edges, SSRFGuard edges, concurrency edges, formatter edges, normalizer edges, voice edges, reaction_utils edges, send edges, location/sticker edges. """ import asyncio import os import tempfile from unittest.mock import AsyncMock, MagicMock, patch import pytest from yuxi.channels.adapters.matrix.adapter import MatrixAdapter from yuxi.channels.models import ( ChannelIdentity, ChannelResponse, ChannelType, DeliveryResult, EventType, MessageType, ) # ============================================================================= # EventDeduplicator # ============================================================================= class TestEventDeduplicator: def test_not_duplicate_first_seen(self): from yuxi.channels.adapters.matrix.dedupe import EventDeduplicator d = EventDeduplicator() assert d.is_duplicate("$evt1") is False def test_duplicate_second_time(self): from yuxi.channels.adapters.matrix.dedupe import EventDeduplicator d = EventDeduplicator() d.is_duplicate("$evt1") assert d.is_duplicate("$evt1") is True def test_different_events_distinct(self): from yuxi.channels.adapters.matrix.dedupe import EventDeduplicator d = EventDeduplicator() d.is_duplicate("$evt1") assert d.is_duplicate("$evt2") is False def test_clear_removes_all(self): from yuxi.channels.adapters.matrix.dedupe import EventDeduplicator d = EventDeduplicator() d.is_duplicate("$evt1") d.clear() assert len(d) == 0 assert d.is_duplicate("$evt1") is False def test_len_reflects_stored_count(self): from yuxi.channels.adapters.matrix.dedupe import EventDeduplicator d = EventDeduplicator() d.is_duplicate("$a") d.is_duplicate("$b") assert len(d) == 2 def test_ttl_expiration_evicts_old_entries(self): import time from yuxi.channels.adapters.matrix.dedupe import EventDeduplicator d = EventDeduplicator(ttl_seconds=0.01, max_size=100) d.is_duplicate("$evt1") time.sleep(0.02) assert d.is_duplicate("$evt1") is False def test_max_size_evicts_oldest(self): from yuxi.channels.adapters.matrix.dedupe import EventDeduplicator d = EventDeduplicator(max_size=2, ttl_seconds=3600) d.is_duplicate("$a") d.is_duplicate("$b") d.is_duplicate("$c") assert len(d) <= 2 assert d.is_duplicate("$a") is False def test_duplicate_moves_to_end(self): import time from yuxi.channels.adapters.matrix.dedupe import EventDeduplicator d = EventDeduplicator(max_size=3, ttl_seconds=3600) d.is_duplicate("$a") d.is_duplicate("$b") d.is_duplicate("$a") d.is_duplicate("$c") assert len(d) == 3 assert d.is_duplicate("$b") is True # ============================================================================= # MatrixSecurityPolicy # ============================================================================= class TestMatrixSecurityPolicy: def test_dm_policy_open(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"dm": {"policy": "open"}}) assert p.evaluate_dm_access("@anyone:example.org") is True def test_dm_policy_disabled(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"dm": {"policy": "disabled"}}) assert p.evaluate_dm_access("@anyone:example.org") is False def test_dm_policy_allowlist_match(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"dm": {"policy": "allowlist", "allowFrom": ["@friend:example.org"]}}) assert p.evaluate_dm_access("@friend:example.org") is True def test_dm_policy_allowlist_no_match(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"dm": {"policy": "allowlist", "allowFrom": ["@friend:example.org"]}}) assert p.evaluate_dm_access("@stranger:example.org") is False def test_dm_policy_pairing_match(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"dm": {"policy": "pairing", "allowFrom": ["@partner:example.org"]}}) assert p.evaluate_dm_access("@partner:example.org") is True def test_dm_policy_pairing_no_match(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"dm": {"policy": "pairing", "allowFrom": ["@partner:example.org"]}}) assert p.evaluate_dm_access("@other:example.org") is False def test_group_policy_open(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"groupPolicy": "open"}) assert p.evaluate_group_access("@anyone:example.org", "!room:example.org") is True def test_group_policy_disabled(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"groupPolicy": "disabled"}) assert p.evaluate_group_access("@anyone:example.org", "!room:example.org") is False def test_group_policy_allowlist_match(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"groupPolicy": "allowlist", "groupAllowFrom": ["@member:example.org"]}) assert p.evaluate_group_access("@member:example.org", "!room:example.org") is True def test_group_room_users_match(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"rooms": {"!room:example.org": {"users": ["@room_member:example.org"]}}}) assert p.evaluate_group_access("@room_member:example.org", "!room:example.org") is True assert p.evaluate_group_access("@other:example.org", "!room:example.org") is False def test_allowlist_only_mode(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({ "allowlistOnly": True, "dm": {"policy": "open", "allowFrom": ["@approved:example.org"]}, }) assert p.evaluate_dm_access("@approved:example.org") is True assert p.evaluate_dm_access("@other:example.org") is False def test_should_skip_bot_true(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"allowBots": False}) assert p.should_skip_bot("@[bot]helper:example.org") is True def test_should_skip_bot_false_when_allowed(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"allowBots": True}) assert p.should_skip_bot("@[bot]helper:example.org") is False def test_should_skip_bot_none_config(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({}) assert p.should_skip_bot("@[bot]helper:example.org") is False def test_should_skip_bot_not_bot(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"allowBots": False}) assert p.should_skip_bot("@human:example.org") is False def test_context_visibility_all(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"contextVisibility": "all"}) assert p.is_context_visible("direct") is True assert p.is_context_visible("group") is True def test_context_visibility_none(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"contextVisibility": "none"}) assert p.is_context_visible("direct") is False assert p.is_context_visible("group") is False def test_context_visibility_dm(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"contextVisibility": "dm"}) assert p.is_context_visible("direct") is True assert p.is_context_visible("group") is False def test_context_visibility_group(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"contextVisibility": "group"}) assert p.is_context_visible("direct") is False assert p.is_context_visible("group") is True def test_allowlist_only_property(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"allowlistOnly": True}) assert p.allowlist_only is True def test_context_visibility_property(self): from yuxi.channels.adapters.matrix.security import MatrixSecurityPolicy p = MatrixSecurityPolicy({"contextVisibility": "dm"}) assert p.context_visibility == "dm" # ============================================================================= # allowlist # ============================================================================= class TestAllowlist: def test_normalize_mxid(self): from yuxi.channels.adapters.matrix.allowlist import normalize_mxid assert normalize_mxid("@User:Example.Org") == "@user:example.org" assert normalize_mxid(" @alice:matrix.org ") == "@alice:matrix.org" def test_match_user_empty_list(self): from yuxi.channels.adapters.matrix.allowlist import match_user_in_list assert match_user_in_list("@user:example.org", []) is False assert match_user_in_list("@user:example.org", [" "]) is False def test_match_user_wildcard(self): from yuxi.channels.adapters.matrix.allowlist import match_user_in_list assert match_user_in_list("@user:example.org", ["*"]) is True assert match_user_in_list("@anyone:any.org", ["*"]) is True def test_match_user_wildcard_domain(self): from yuxi.channels.adapters.matrix.allowlist import match_user_in_list assert match_user_in_list("@alice:matrix.org", ["@*:matrix.org"]) is True assert match_user_in_list("@alice:other.org", ["@*:matrix.org"]) is False def test_match_user_wildcard_domain_case(self): from yuxi.channels.adapters.matrix.allowlist import match_user_in_list assert match_user_in_list("@alice:MATRIX.ORG", ["@*:matrix.org"]) is True def test_match_user_exact(self): from yuxi.channels.adapters.matrix.allowlist import match_user_in_list assert match_user_in_list("@alice:matrix.org", ["@alice:matrix.org"]) is True assert match_user_in_list("@bob:matrix.org", ["@alice:matrix.org"]) is False def test_is_likely_bot(self): from yuxi.channels.adapters.matrix.allowlist import is_likely_bot assert is_likely_bot("@[bot]helper:matrix.org") is True assert is_likely_bot("@helper(bot):matrix.org") is True assert is_likely_bot("@human:matrix.org") is False assert is_likely_bot("") is False # ============================================================================= # rooms_config # ============================================================================= class TestRoomsConfig: def test_get_room_config_existing(self): from yuxi.channels.adapters.matrix.rooms_config import get_room_config config = {"rooms": {"!room:example.org": {"allow": True}}} assert get_room_config("!room:example.org", config) == {"allow": True} def test_get_room_config_missing(self): from yuxi.channels.adapters.matrix.rooms_config import get_room_config assert get_room_config("!unknown:example.org", {}) == {} def test_get_room_users(self): from yuxi.channels.adapters.matrix.rooms_config import get_room_users config = {"rooms": {"!room:example.org": {"users": ["@a:x", "@b:x"]}}} assert get_room_users("!room:example.org", config) == ["@a:x", "@b:x"] assert get_room_users("!unknown:example.org", {}) == [] def test_is_room_allowed(self): from yuxi.channels.adapters.matrix.rooms_config import is_room_allowed config = {"rooms": {"!room:example.org": {"allow": False}}} assert is_room_allowed("!room:example.org", config) is False assert is_room_allowed("!other:example.org", {}) is True def test_get_room_require_mention_per_room(self): from yuxi.channels.adapters.matrix.rooms_config import get_room_require_mention config = { "requireMention": True, "rooms": {"!room:example.org": {"requireMention": False}}, } assert get_room_require_mention("!room:example.org", config) is False assert get_room_require_mention("!other:example.org", config) is True def test_get_room_require_mention_default(self): from yuxi.channels.adapters.matrix.rooms_config import get_room_require_mention assert get_room_require_mention("!room:example.org", {}) is True assert get_room_require_mention("!room:example.org", {"requireMention": False}) is False # ============================================================================= # auto_join # ============================================================================= class TestAutoJoin: def test_always(self): from yuxi.channels.adapters.matrix.auto_join import should_auto_join assert should_auto_join("!room:example.org", {"autoJoin": "always"}) is True def test_off(self): from yuxi.channels.adapters.matrix.auto_join import should_auto_join assert should_auto_join("!room:example.org", {"autoJoin": "off"}) is False def test_allowlist_allowed(self): from yuxi.channels.adapters.matrix.auto_join import should_auto_join config = {"autoJoin": "allowlist", "rooms": {"!room:example.org": {"allow": True}}} assert should_auto_join("!room:example.org", config) is True def test_allowlist_not_allowed(self): from yuxi.channels.adapters.matrix.auto_join import should_auto_join config = {"autoJoin": "allowlist", "rooms": {"!room:example.org": {"allow": False}}} assert should_auto_join("!room:example.org", config) is False def test_allowlist_not_in_rooms(self): from yuxi.channels.adapters.matrix.auto_join import should_auto_join assert should_auto_join("!unknown:example.org", {"autoJoin": "allowlist"}) is True def test_unknown_mode(self): from yuxi.channels.adapters.matrix.auto_join import should_auto_join assert should_auto_join("!room:example.org", {"autoJoin": "unknown"}) is False def test_default_off(self): from yuxi.channels.adapters.matrix.auto_join import should_auto_join assert should_auto_join("!room:example.org", {}) is False # ============================================================================= # crypto # ============================================================================= class TestCrypto: @pytest.mark.asyncio async def test_import_room_keys_no_file(self, tmp_path): from yuxi.channels.adapters.matrix.crypto import import_room_keys mock_client = MagicMock() await import_room_keys(mock_client, str(tmp_path)) @pytest.mark.asyncio async def test_import_room_keys_with_file(self, tmp_path): import json from yuxi.channels.adapters.matrix.crypto import import_room_keys keys_file = tmp_path / "room_keys.json" keys_file.write_text(json.dumps({"rooms": {"!room:example.org": {"sessions": []}}}), encoding="utf-8") mock_client = MagicMock() mock_client.import_room_keys = AsyncMock() await import_room_keys(mock_client, str(tmp_path)) mock_client.import_room_keys.assert_awaited_once() @pytest.mark.asyncio async def test_import_room_keys_corrupted_file(self, tmp_path): from yuxi.channels.adapters.matrix.crypto import import_room_keys keys_file = tmp_path / "room_keys.json" keys_file.write_text("not json", encoding="utf-8") mock_client = MagicMock() mock_client.import_room_keys = AsyncMock() await import_room_keys(mock_client, str(tmp_path)) mock_client.import_room_keys.assert_not_awaited() @pytest.mark.asyncio async def test_import_room_keys_unexpected_format(self, tmp_path): import json from yuxi.channels.adapters.matrix.crypto import import_room_keys keys_file = tmp_path / "room_keys.json" keys_file.write_text(json.dumps({"not_rooms": 1}), encoding="utf-8") mock_client = MagicMock() mock_client.import_room_keys = AsyncMock() await import_room_keys(mock_client, str(tmp_path)) mock_client.import_room_keys.assert_not_awaited() @pytest.mark.asyncio async def test_export_room_keys_success(self, tmp_path): from yuxi.channels.adapters.matrix.crypto import export_room_keys mock_client = MagicMock() mock_client.export_room_keys = AsyncMock() result = await export_room_keys(mock_client, str(tmp_path)) assert result is True @pytest.mark.asyncio async def test_export_room_keys_failure(self, tmp_path): from yuxi.channels.adapters.matrix.crypto import export_room_keys mock_client = MagicMock() mock_client.export_room_keys = AsyncMock(side_effect=Exception("fail")) result = await export_room_keys(mock_client, str(tmp_path)) assert result is False # ============================================================================= # directory (async) # ============================================================================= class TestDirectory: @pytest.mark.asyncio async def test_search_users_success(self): from yuxi.channels.adapters.matrix.directory import search_users mock_client = MagicMock() mock_result = MagicMock() mock_result.user_id = "@alice:example.org" mock_result.display_name = "Alice" mock_result.avatar_url = "mxc://avatar" resp = MagicMock() resp.results = [mock_result] mock_client.search_user_directory = AsyncMock(return_value=resp) results = await search_users(mock_client, "alice") assert len(results) == 1 assert results[0]["user_id"] == "@alice:example.org" @pytest.mark.asyncio async def test_search_users_error(self): from yuxi.channels.adapters.matrix.directory import search_users mock_client = MagicMock() mock_client.search_user_directory = AsyncMock(side_effect=Exception("fail")) results = await search_users(mock_client, "alice") assert results == [] @pytest.mark.asyncio async def test_search_groups_success(self): from yuxi.channels.adapters.matrix.directory import search_groups mock_client = MagicMock() mock_result = MagicMock() mock_result.room_id = "!room:example.org" mock_result.name = "Test Room" mock_result.canonical_alias = "#test:example.org" mock_result.topic = "A test room" mock_result.num_joined_members = 42 mock_result.world_readable = True mock_result.guest_can_join = False resp = MagicMock() resp.results = [mock_result] mock_client.room_directory_search = AsyncMock(return_value=resp) results = await search_groups(mock_client, "test") assert len(results) == 1 assert results[0]["room_id"] == "!room:example.org" assert results[0]["num_joined_members"] == 42 @pytest.mark.asyncio async def test_search_groups_error(self): from yuxi.channels.adapters.matrix.directory import search_groups mock_client = MagicMock() mock_client.room_directory_search = AsyncMock(side_effect=Exception("fail")) results = await search_groups(mock_client, "test") assert results == [] @pytest.mark.asyncio async def test_resolve_room_alias_success(self): from yuxi.channels.adapters.matrix.directory import resolve_room_alias mock_client = MagicMock() resp = MagicMock() resp.room_id = "!room:example.org" resp.servers = ["example.org"] mock_client.room_resolve_alias = AsyncMock(return_value=resp) result = await resolve_room_alias(mock_client, "#test:example.org") assert result is not None assert result["room_id"] == "!room:example.org" @pytest.mark.asyncio async def test_resolve_room_alias_error(self): from yuxi.channels.adapters.matrix.directory import resolve_room_alias mock_client = MagicMock() mock_client.room_resolve_alias = AsyncMock(side_effect=Exception("fail")) result = await resolve_room_alias(mock_client, "#test:example.org") assert result is None @pytest.mark.asyncio async def test_list_public_rooms_success(self): from yuxi.channels.adapters.matrix.directory import list_public_rooms mock_client = MagicMock() mock_room = MagicMock() mock_room.room_id = "!pub:example.org" mock_room.name = "Public Room" mock_room.canonical_alias = "#pub:example.org" mock_room.topic = "Welcome" mock_room.num_joined_members = 10 mock_room.world_readable = True resp = MagicMock() resp.chunk = [mock_room] mock_client.public_rooms = AsyncMock(return_value=resp) results = await list_public_rooms(mock_client) assert len(results) == 1 assert results[0]["room_id"] == "!pub:example.org" @pytest.mark.asyncio async def test_list_public_rooms_error(self): from yuxi.channels.adapters.matrix.directory import list_public_rooms mock_client = MagicMock() mock_client.public_rooms = AsyncMock(side_effect=Exception("fail")) results = await list_public_rooms(mock_client) assert results == [] # ============================================================================= # Polls async # ============================================================================= class TestPollsAsync: @pytest.mark.asyncio async def test_create_poll_success(self): from yuxi.channels.adapters.matrix.polls import create_poll mock_client = MagicMock() resp = MagicMock() resp.event_id = "$poll001" mock_client.room_send = AsyncMock(return_value=resp) result = await create_poll(mock_client, "!room:example.org", "Question?", ["A", "B"]) assert result.success is True assert result.message_id == "$poll001" @pytest.mark.asyncio async def test_create_poll_failure(self): from yuxi.channels.adapters.matrix.polls import create_poll mock_client = MagicMock() mock_client.room_send = AsyncMock(side_effect=Exception("fail")) result = await create_poll(mock_client, "!room:example.org", "Q?", ["A"]) assert result.success is False assert "fail" in result.error @pytest.mark.asyncio async def test_send_poll_response_success(self): from yuxi.channels.adapters.matrix.polls import send_poll_response mock_client = MagicMock() resp = MagicMock() resp.event_id = "$vote001" mock_client.room_send = AsyncMock(return_value=resp) result = await send_poll_response(mock_client, "!room:example.org", "$poll001", ["answer_0"]) assert result.success is True assert result.message_id == "$vote001" @pytest.mark.asyncio async def test_send_poll_response_failure(self): from yuxi.channels.adapters.matrix.polls import send_poll_response mock_client = MagicMock() mock_client.room_send = AsyncMock(side_effect=Exception("fail")) result = await send_poll_response(mock_client, "!room:example.org", "$poll001", ["answer_0"]) assert result.success is False @pytest.mark.asyncio async def test_end_poll_success(self): from yuxi.channels.adapters.matrix.polls import end_poll mock_client = MagicMock() resp = MagicMock() resp.event_id = "$end001" mock_client.room_send = AsyncMock(return_value=resp) result = await end_poll(mock_client, "!room:example.org", "$poll001", summary="Done") assert result.success is True assert result.message_id == "$end001" @pytest.mark.asyncio async def test_end_poll_no_summary(self): from yuxi.channels.adapters.matrix.polls import end_poll mock_client = MagicMock() resp = MagicMock() resp.event_id = "$end002" mock_client.room_send = AsyncMock(return_value=resp) result = await end_poll(mock_client, "!room:example.org", "$poll001") assert result.success is True @pytest.mark.asyncio async def test_end_poll_failure(self): from yuxi.channels.adapters.matrix.polls import end_poll mock_client = MagicMock() mock_client.room_send = AsyncMock(side_effect=Exception("fail")) result = await end_poll(mock_client, "!room:example.org", "$poll001") assert result.success is False # ============================================================================= # ConfigSchema edges # ============================================================================= class TestConfigSchemaEdges: def test_validate_pattern_mismatch(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({"user_id": "not-a-valid-user-id"}) assert any("pattern" in e["error"] for e in errors) def test_validate_enum_mismatch(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "autoJoin": "invalid", }) assert any("invalid" in e["error"] for e in errors) def test_validate_min_constraint(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "sync_timeout_ms": 0, }) assert any("below" in e["error"] for e in errors) def test_validate_max_constraint(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "textChunkLimit": 100000, }) assert any("above" in e["error"] for e in errors) def test_validate_wrong_type_int_for_str(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": 123, "access_token": "tok", }) assert any("expected str" in e["error"] for e in errors) def test_validate_wrong_type_str_for_int(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "mediaMaxMb": "ten", }) assert any("expected int" in e["error"] for e in errors) def test_validate_encryption_requires_crypto_dir(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "encryption": True, }) assert any("crypto_store_dir" in e["field"] for e in errors) def test_validate_nested_section(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "dm": {"policy": "invalid"}, }) assert any("dm.policy" in e["field"] for e in errors) def test_validate_float_type(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "probe_timeout_s": "slow", }) assert any("expected float" in e["error"] for e in errors) def test_validate_float_with_int_passes(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "probe_timeout_s": 10, }) assert not any("probe_timeout_s" in e["field"] for e in errors) def test_validate_bool_type(self): from yuxi.channels.adapters.matrix.config_schema import validate_config errors = validate_config({ "user_id": "@bot:example.org", "access_token": "tok", "enabled": "yes", }) assert any("expected bool" in e["error"] for e in errors) # ============================================================================= # Accounts remaining # ============================================================================= class TestAccountsRemaining: def test_resolve_room_account(self): from yuxi.channels.adapters.matrix.accounts import MultiAccountManager config = { "accounts": {"bot1": {"user_id": "@bot1:example.org"}}, "rooms": {"!room:example.org": {"account": "bot1"}}, } mgr = MultiAccountManager(config) assert mgr.resolve_room_account("!room:example.org") == "bot1" assert mgr.resolve_room_account("!unknown:example.org") is None def test_promote_to_default(self): from yuxi.channels.adapters.matrix.accounts import MultiAccountManager config = { "accounts": { "bot1": {"user_id": "@bot1:example.org", "isDefault": True}, "bot2": {"user_id": "@bot2:example.org"}, }, } mgr = MultiAccountManager(config) assert mgr.promote_to_default("bot2") is True assert mgr.default_account_id == "bot2" def test_promote_nonexistent(self): from yuxi.channels.adapters.matrix.accounts import MultiAccountManager mgr = MultiAccountManager({"accounts": {"bot1": {"user_id": "@bot1:example.org"}}}) assert mgr.promote_to_default("nonexistent") is False def test_has_multi_account(self): from yuxi.channels.adapters.matrix.accounts import MultiAccountManager mgr1 = MultiAccountManager({"accounts": {"bot1": {"user_id": "@bot1:example.org"}}}) assert mgr1.has_multi_account is False mgr2 = MultiAccountManager({ "accounts": { "bot1": {"user_id": "@bot1:example.org"}, "bot2": {"user_id": "@bot2:example.org"}, }, }) assert mgr2.has_multi_account is True def test_default_account_none_when_empty(self): from yuxi.channels.adapters.matrix.accounts import MultiAccountManager mgr = MultiAccountManager({}) assert mgr.default_account is None assert mgr.account_count == 0 def test_matrix_account_properties(self): from yuxi.channels.adapters.matrix.accounts import MatrixAccount acc = MatrixAccount("acc1", { "user_id": "@bot:matrix.org", "homeserver": "https://custom.org", "isDefault": True, "enabled": False, }) assert acc.user_id == "@bot:matrix.org" assert acc.homeserver == "https://custom.org" assert acc.is_default is True assert acc.enabled is False def test_matrix_account_repr(self): from yuxi.channels.adapters.matrix.accounts import MatrixAccount acc = MatrixAccount("acc1", {"user_id": "@bot:matrix.org", "homeserver": "https://matrix.org"}) r = repr(acc) assert "acc1" in r assert "@bot:matrix.org" in r # ============================================================================= # Credentials encrypted # ============================================================================= class TestCredentialsEncrypted: def test_save_and_load_encrypted(self, tmp_path): from yuxi.channels.adapters.matrix.credentials import MatrixCredentials, CredentialStore os.environ["MATRIX_CREDENTIAL_KEY"] = "test-encryption-key-32bytes!!" try: store = CredentialStore(base_dir=str(tmp_path)) creds = MatrixCredentials(user_id="@bot:a", homeserver="https://a", access_token="secret123") store.save(creds) loaded = store.load("@bot:a") assert loaded is not None assert loaded.access_token == "secret123" finally: del os.environ["MATRIX_CREDENTIAL_KEY"] def test_list_accounts(self, tmp_path): from yuxi.channels.adapters.matrix.credentials import MatrixCredentials, CredentialStore store = CredentialStore(base_dir=str(tmp_path)) store.save(MatrixCredentials(user_id="@bot1:a", homeserver="https://a", access_token="t1")) store.save(MatrixCredentials(user_id="@bot2:a", homeserver="https://a", access_token="t2")) accounts = store.list_accounts() assert len(accounts) >= 1 def test_load_all(self, tmp_path): from yuxi.channels.adapters.matrix.credentials import MatrixCredentials, CredentialStore store = CredentialStore(base_dir=str(tmp_path)) store.save(MatrixCredentials(user_id="@bot:a", homeserver="https://a", access_token="t")) loaded = store.load_all() assert len(loaded) >= 1 def test_from_dict(self): from yuxi.channels.adapters.matrix.credentials import MatrixCredentials creds = MatrixCredentials.from_dict({ "user_id": "@bot:a", "homeserver": "https://a", "access_token": "tok", "device_id": "dev1", "display_name": "Bot", }) assert creds.user_id == "@bot:a" assert creds.device_id == "dev1" assert creds.display_name == "Bot" # ============================================================================= # DraftStream # ============================================================================= class TestDraftStreamEdges: def test_draft_session_start_update_finish(self): from yuxi.channels.adapters.matrix.draft_stream import DraftStreamSession s = DraftStreamSession("!room:example.org") assert not s.is_active s.start("$evt1") assert s.is_active assert s.draft_event_id == "$evt1" s.update("Hello world") assert s.current_text == "Hello world" s.finish() assert not s.is_active def test_get_or_create_draft_reuse(self): from yuxi.channels.adapters.matrix.draft_stream import get_or_create_draft, remove_draft d1 = get_or_create_draft("!room:example.org") d2 = get_or_create_draft("!room:example.org") assert d1 is d2 remove_draft("!room:example.org") def test_remove_nonexistent_does_not_raise(self): from yuxi.channels.adapters.matrix.draft_stream import remove_draft remove_draft("!nonexistent:example.org") @pytest.mark.asyncio async def test_send_live_marker_success(self): from yuxi.channels.adapters.matrix.draft_stream import send_live_marker mock_client = MagicMock() resp = MagicMock() resp.event_id = "$live001" mock_client.room_send = AsyncMock(return_value=resp) result = await send_live_marker( mock_client, "!room:example.org", "$evt", {"msgtype": "m.text", "body": "hello"}, finished=True, ) assert result.success is True @pytest.mark.asyncio async def test_send_live_marker_failure(self): from yuxi.channels.adapters.matrix.draft_stream import send_live_marker mock_client = MagicMock() mock_client.room_send = AsyncMock(side_effect=Exception("fail")) result = await send_live_marker( mock_client, "!room:example.org", "$evt", {"msgtype": "m.text", "body": "hello"}, ) assert result.success is False # ============================================================================= # MediaMeta # ============================================================================= class TestMediaMetaEdges: def test_extract_duration_bytes_wav(self): from yuxi.channels.adapters.matrix.media_meta import extract_duration_bytes header = ( b"RIFF" + (44).to_bytes(4, "little") + b"WAVEfmt " + b"\x10\x00\x00\x00\x01\x00\x01\x00" + (44100).to_bytes(4, "little") + (88200).to_bytes(4, "little") + b"\x02\x00\x10\x00" + b"data" + (100).to_bytes(4, "little") ) data = header + b"\x00" * 100 duration = extract_duration_bytes(data, "audio/wav") assert duration >= 0 def test_extract_duration_bytes_unknown(self): from yuxi.channels.adapters.matrix.media_meta import extract_duration_bytes duration = extract_duration_bytes(b"random_bytes" * 10, "audio/unknown") assert duration == 0 def test_extract_duration_bytes_empty(self): from yuxi.channels.adapters.matrix.media_meta import extract_duration_bytes duration = extract_duration_bytes(b"", "audio/ogg") assert duration == 0 def test_detect_codec_from_mime_type(self): from yuxi.channels.adapters.matrix.media_meta import _detect_codec assert _detect_codec(MagicMock(), "audio/mp3") == "mp3" assert _detect_codec(MagicMock(), "audio/ogg") == "ogg" assert _detect_codec(MagicMock(), None) == "magicmock" def test_detect_codec_from_class_name(self): from yuxi.channels.adapters.matrix.media_meta import _detect_codec mp3_mock = MagicMock() type(mp3_mock).__name__ = "MP3" assert _detect_codec(mp3_mock, None) == "mp3" ogg_mock = MagicMock() type(ogg_mock).__name__ = "OggVorbis" assert _detect_codec(ogg_mock, None) == "vorbis" flac_mock = MagicMock() type(flac_mock).__name__ = "FLAC" assert _detect_codec(flac_mock, None) == "flac" wav_mock = MagicMock() type(wav_mock).__name__ = "Wave" assert _detect_codec(wav_mock, None) == "pcm" opus_mock = MagicMock() type(opus_mock).__name__ = "Opus" assert _detect_codec(opus_mock, None) == "opus" aac_mock = MagicMock() type(aac_mock).__name__ = "AAC" assert _detect_codec(aac_mock, None) == "aac" # ============================================================================= # ProfileSync edges # ============================================================================= class TestProfileSyncEdges: def test_resolve_avatar_url_http_no_homeserver(self): from yuxi.channels.adapters.matrix.profile_sync import resolve_avatar_url result = resolve_avatar_url("https://cdn.example.org/avatar.png") assert result == "https://cdn.example.org/avatar.png" def test_resolve_avatar_url_invalid(self): from yuxi.channels.adapters.matrix.profile_sync import resolve_avatar_url assert resolve_avatar_url("not-a-url", "example.org") is None def test_resolve_display_name_unknown(self): from yuxi.channels.adapters.matrix.profile_sync import resolve_display_name assert resolve_display_name({}) == "unknown" def test_resolve_display_name_non_mxid(self): from yuxi.channels.adapters.matrix.profile_sync import resolve_display_name assert resolve_display_name({"user_id": "plain_user"}) == "plain_user" def test_compare_profiles_no_changes(self): from yuxi.channels.adapters.matrix.profile_sync import compare_profiles current = {"displayname": "Same", "avatar_url": "mxc://same"} incoming = {"displayname": "Same", "avatar_url": "mxc://same"} changes = compare_profiles(current, incoming) assert len(changes) == 0 def test_build_profile_update_empty(self): from yuxi.channels.adapters.matrix.profile_sync import build_profile_update assert build_profile_update() == {} assert build_profile_update(display_name="", avatar_url="") == {} def test_mxc_to_http_invalid(self): from yuxi.channels.adapters.matrix.profile_sync import mxc_to_http assert mxc_to_http("not-mxc", "https://example.org") == "not-mxc" def test_mxc_to_http_plain_homeserver(self): from yuxi.channels.adapters.matrix.profile_sync import mxc_to_http url = mxc_to_http("mxc://example.org/abc123", "example.org") assert url.startswith("https://example.org") # ============================================================================= # SyncStateMachine edges # ============================================================================= class TestSyncStateMachineEdges: def test_can_transition_valid(self): from yuxi.channels.adapters.matrix.sync_state import SyncStateMachine, SyncState sm = SyncStateMachine() assert sm.can_transition(SyncState.SYNCING) is True assert sm.can_transition(SyncState.ERROR) is True def test_can_transition_invalid(self): from yuxi.channels.adapters.matrix.sync_state import SyncStateMachine, SyncState sm = SyncStateMachine() sm.to_healthy() assert sm.can_transition(SyncState.PREPARED) is False def test_transition_to_prepared(self): from yuxi.channels.adapters.matrix.sync_state import SyncStateMachine, SyncState sm = SyncStateMachine() sm.to_stopped() assert sm.to_prepared() is True assert sm.state == SyncState.PREPARED def test_should_stop_retry(self): from yuxi.channels.adapters.matrix.sync_state import SyncStateMachine sm = SyncStateMachine() sm.to_healthy() for _ in range(sm._max_retries): sm.to_error() sm.to_reconnecting() sm.to_error() assert sm.should_stop_retry is True def test_should_auto_recover_from_error(self): import time from yuxi.channels.adapters.matrix.sync_state import SyncStateMachine, SyncState sm = SyncStateMachine() sm.to_healthy() sm.to_error() assert sm.should_auto_recover is False sm._last_error_time -= 120 assert sm.should_auto_recover is True def test_should_auto_recover_not_in_error(self): from yuxi.channels.adapters.matrix.sync_state import SyncStateMachine sm = SyncStateMachine() sm.to_healthy() assert sm.should_auto_recover is False def test_is_active(self): from yuxi.channels.adapters.matrix.sync_state import SyncStateMachine sm = SyncStateMachine() assert sm.is_active is True sm.to_healthy() assert sm.is_active is True sm.to_error() assert sm.is_active is False def test_error_clears_retry_on_healthy(self): from yuxi.channels.adapters.matrix.sync_state import SyncStateMachine sm = SyncStateMachine() sm.to_healthy() sm.to_error() sm.to_reconnecting() sm.to_error() sm.to_reconnecting() assert sm.retry_count == 2 sm.to_healthy() assert sm.retry_count == 0 # ============================================================================= # SentMessageCache edges # ============================================================================= class TestSentMessageCacheEdges: def test_max_size_eviction(self): from yuxi.channels.adapters.matrix.sent_cache import SentMessageCache cache = SentMessageCache(max_size=2) cache.add("!room:example.org", "hash1", "$evt1") cache.add("!room:example.org", "hash2", "$evt2") cache.add("!room:example.org", "hash3", "$evt3") assert len(cache) <= 2 assert cache.get("!room:example.org", "hash1") is None def test_hashing_deterministic(self): from yuxi.channels.adapters.matrix.sent_cache import hash_content h1 = hash_content("hello") h2 = hash_content("hello") assert h1 == h2 def test_hashing_different(self): from yuxi.channels.adapters.matrix.sent_cache import hash_content h1 = hash_content("hello") h2 = hash_content("world") assert h1 != h2 # ============================================================================= # SSRFGuard edges # ============================================================================= class TestSSRFGuardEdges: def test_is_private_cgnat_range(self): from yuxi.channels.adapters.matrix.sync_store import SSRFGuard assert SSRFGuard.is_private_host("100.64.0.1") is True assert SSRFGuard.is_private_host("100.127.255.255") is True assert SSRFGuard.is_private_host("100.63.0.1") is False assert SSRFGuard.is_private_host("100.128.0.1") is False def test_is_private_ipv6_localhost(self): from yuxi.channels.adapters.matrix.sync_store import SSRFGuard assert SSRFGuard.is_private_host("::1") is True assert SSRFGuard.is_private_host("[::1]") is True def test_is_private_zero(self): from yuxi.channels.adapters.matrix.sync_store import SSRFGuard assert SSRFGuard.is_private_host("0.0.0.0") is True def test_is_private_127_variants(self): from yuxi.channels.adapters.matrix.sync_store import SSRFGuard assert SSRFGuard.is_private_host("127.0.0.1") is True assert SSRFGuard.is_private_host("127.0.0.2") is True assert SSRFGuard.is_private_host("127.255.255.255") is True def test_is_private_172_range_boundaries(self): from yuxi.channels.adapters.matrix.sync_store import SSRFGuard assert SSRFGuard.is_private_host("172.16.0.1") is True assert SSRFGuard.is_private_host("172.31.255.255") is True assert SSRFGuard.is_private_host("172.15.0.1") is False assert SSRFGuard.is_private_host("172.32.0.1") is False def test_validate_url_no_hostname(self): from yuxi.channels.adapters.matrix.sync_store import SSRFGuard error = SSRFGuard.validate_url("bad:///path") assert error is not None def test_parse_second_octet(self): from yuxi.channels.adapters.matrix.sync_store import _parse_second_octet assert _parse_second_octet("100.64.0.1") == 64 assert _parse_second_octet("invalid") == 0 assert _parse_second_octet("10") == 0 # ============================================================================= # Concurrency edges # ============================================================================= class TestConcurrencyEdges: def test_startup_serial_lock_release_without_acquire(self): from yuxi.channels.adapters.matrix.concurrency import StartupSerialLock lock = StartupSerialLock() lock.release_startup() assert lock.is_starting is False def test_startup_serial_lock_double_release(self): from yuxi.channels.adapters.matrix.concurrency import StartupSerialLock lock = StartupSerialLock() lock.acquire_startup() lock.release_startup() lock.release_startup() assert lock.is_starting is False @pytest.mark.asyncio async def test_account_data_write_queue_drain_empty(self): from yuxi.channels.adapters.matrix.concurrency import AccountDataWriteQueue queue = AccountDataWriteQueue() items = await queue.drain() assert items == [] @pytest.mark.asyncio async def test_account_data_write_queue_consumer(self): import asyncio from yuxi.channels.adapters.matrix.concurrency import AccountDataWriteQueue results = [] async def write_fn(account_type, data): results.append((account_type, data)) queue = AccountDataWriteQueue() await queue.enqueue("m.direct", {"key": "val"}) task = queue.start_consumer(write_fn, interval=0.01) await asyncio.sleep(0.05) await queue.stop_consumer() assert len(results) >= 0 # ============================================================================= # Formatter edges # ============================================================================= class TestFormatterEdges: def test_empty_string(self): from yuxi.channels.adapters.matrix.formatter import markdown_to_matrix_html result = markdown_to_matrix_html("") assert result == "" def test_multiple_code_blocks(self): from yuxi.channels.adapters.matrix.formatter import markdown_to_matrix_html result = markdown_to_matrix_html("```\na\n```\n```\nb\n```") assert result.count("
") == 2

    def test_heading_level_1(self):
        from yuxi.channels.adapters.matrix.formatter import markdown_to_matrix_html

        result = markdown_to_matrix_html("# H1")
        assert "

H1

" in result def test_heading_level_6(self): from yuxi.channels.adapters.matrix.formatter import markdown_to_matrix_html result = markdown_to_matrix_html("###### H6") assert "
H6
" in result def test_mixed_unsafe_tags_and_safe(self): from yuxi.channels.adapters.matrix.formatter import markdown_to_matrix_html result = markdown_to_matrix_html('
safe
') assert "safe" in result assert "") assert "") assert "") assert "") assert "") assert "") assert "") assert "") assert "alert(1)") assert "<script>" in result assert "") assert "<script>" in result def test_list_items_escape_html(self): from yuxi.channels.adapters.matrix.formatter import markdown_to_matrix_html result = markdown_to_matrix_html("- ") assert "<script>" in result def test_style_expression_stripped(self): from yuxi.channels.adapters.matrix.formatter import markdown_to_matrix_html result = markdown_to_matrix_html('
') assert "expression" not in result # ============================================================================= # Normalizer edges # ============================================================================= class TestNormalizerEdges: @pytest.fixture def adapter(self): a = MatrixAdapter({ "user_id": "@bot:example.org", "homeserver": "https://example.org", "requireMention": False, }) a._client = MagicMock() a._client.user_id = "@bot:example.org" a._direct_rooms.add("!dm:example.org") return a def test_edit_event_detection(self, adapter): event = MagicMock() event.sender = "@user:example.org" event.body = "edited" event.event_id = "$evt_edit" event.server_timestamp = 1700000000000 event.source = { "content": { "body": "edited text", "msgtype": "m.text", "m.relates_to": { "rel_type": "m.replace", "event_id": "$original", }, "m.new_content": { "body": "edited text", "msgtype": "m.text", }, }, } msg = adapter._normalizer.normalize(event, "!dm:example.org") assert msg is not None assert msg.event_type == EventType.MESSAGE_UPDATED def test_formatted_text_event(self, adapter): from nio import RoomMessageFormatted event = MagicMock(spec=RoomMessageFormatted) event.sender = "@user:example.org" event.body = "hello" event.formatted_body = "hello" event.event_id = "$evt_formatted" event.server_timestamp = 1700000000000 event.source = { "content": { "msgtype": "m.text", "body": "hello", "formatted_body": "hello", }, } msg = adapter._normalizer.normalize(event, "!dm:example.org") assert msg is not None assert msg.message_type == MessageType.TEXT def test_require_mention_filters_when_not_mentioned(self, adapter): adapter.config["requireMention"] = True event = MagicMock() event.sender = "@user:example.org" event.body = "hello without bot mention" event.event_id = "$evt_filtered" event.server_timestamp = 1700000000000 event.source = { "content": { "body": "hello without bot mention", "msgtype": "m.text", }, } msg = adapter._normalizer.normalize(event, "!room:example.org") assert msg is None def test_thread_events_skip_mention_filter(self, adapter): adapter.config["requireMention"] = True event = MagicMock() event.sender = "@user:example.org" event.body = "hello in thread" event.event_id = "$evt_thread" event.server_timestamp = 1700000000000 event.source = { "content": { "body": "hello in thread", "msgtype": "m.text", "m.relates_to": { "rel_type": "m.thread", "event_id": "$root", }, }, } msg = adapter._normalizer.normalize(event, "!room:example.org") assert msg is not None def test_dm_skip_mention_filter(self, adapter): adapter.config["requireMention"] = True event = MagicMock() event.sender = "@user:example.org" event.body = "hello DM" event.event_id = "$evt_dm" event.server_timestamp = 1700000000000 event.source = { "content": { "body": "hello DM", "msgtype": "m.text", }, } msg = adapter._normalizer.normalize(event, "!dm:example.org") assert msg is not None def test_mentions_from_url_pattern(self, adapter): formatted = f'Alice' event = MagicMock() event.sender = "@user:example.org" event.body = "hey" event.event_id = "$evt_url_mention" event.server_timestamp = 1700000000000 event.source = { "content": { "body": "hey", "formatted_body": formatted, "msgtype": "m.text", }, } msg = adapter._normalizer.normalize(event, "!room:example.org") assert msg.mentions is not None assert any("alice:example.org" in uid for uid in msg.mentions.mentioned_user_ids) def test_no_timestamp_uses_current(self, adapter): event = MagicMock() event.sender = "@user:example.org" event.body = "hello" event.event_id = "$evt_no_ts" event.server_timestamp = None event.source = { "content": { "body": "hello", "msgtype": "m.text", }, } msg = adapter._normalizer.normalize(event, "!dm:example.org") assert msg is not None assert msg.timestamp is not None def test_resolve_chat_type_direct_via_direct_rooms(self, adapter): from yuxi.channels.adapters.matrix.session import MatrixSessionHelper assert adapter._session.resolve_chat_type("!dm:example.org") == "direct" def test_empty_mentions_when_nothing(self, adapter): event = MagicMock() event.sender = "@user:example.org" event.body = "" event.event_id = "$evt_empty" event.server_timestamp = 1700000000000 event.source = { "content": { "body": "", "formatted_body": "", "msgtype": "m.text", }, } msg = adapter._normalizer.normalize(event, "!dm:example.org") assert msg.mentions is None # ============================================================================= # Voice edges # ============================================================================= class TestVoiceEdges: def test_is_voice_message_not_audio_msgtype(self): from yuxi.channels.adapters.matrix.voice import is_voice_message assert is_voice_message({"content": {"msgtype": "m.text", "org.matrix.msc3245.voice": {}}}) is False def test_is_voice_message_msc1767_no_duration(self): from yuxi.channels.adapters.matrix.voice import is_voice_message assert is_voice_message({ "content": {"msgtype": "m.audio", "org.matrix.msc1767.audio": {}}, }) is False def test_extract_audio_info_no_info(self): from yuxi.channels.adapters.matrix.voice import extract_audio_info info = extract_audio_info({"content": {"msgtype": "m.audio"}}) assert info["duration_ms"] == 0 assert info["is_voice"] is False @pytest.mark.asyncio async def test_send_tts_message(self): from yuxi.channels.adapters.matrix.voice import send_tts_message mock_client = MagicMock() resp = MagicMock() resp.content_uri = "mxc://example.org/tts" resp2 = MagicMock() resp2.event_id = "$tts001" mock_client.upload = AsyncMock(return_value=(resp, {})) mock_client.room_send = AsyncMock(return_value=resp2) result = await send_tts_message( mock_client, "!room:example.org", "Hello this is a TTS message", b"audio_data", ) assert result.success is True assert result.message_id == "$tts001" @pytest.mark.asyncio async def test_send_voice_note_upload_failure(self): from yuxi.channels.adapters.matrix.voice import send_voice_note mock_client = MagicMock() mock_client.upload = AsyncMock(side_effect=Exception("upload fail")) result = await send_voice_note(mock_client, "!room:example.org", b"audio") assert result.success is False assert "upload" in result.error.lower() # ============================================================================= # ReactionUtils edges # ============================================================================= class TestReactionUtilsEdges: def test_extract_reaction_no_source(self): from yuxi.channels.adapters.matrix.reaction_utils import extract_reaction event = MagicMock() event.source = {} assert extract_reaction(event) is None def test_select_reaction_mapped_emoji(self): from yuxi.channels.adapters.matrix.reaction_utils import select_reaction available = [{"key": "👍"}, {"key": "❤️"}] result = select_reaction(available, "+1") assert result is not None assert result["key"] == "👍" def test_select_reaction_case_insensitive(self): from yuxi.channels.adapters.matrix.reaction_utils import select_reaction available = [{"key": "👍"}] result = select_reaction(available, "+1") assert result is not None def test_build_batch_remove_single(self): from yuxi.channels.adapters.matrix.reaction_utils import build_batch_remove reactions = [{"key": "👍", "target_event_id": "$evt"}] result = build_batch_remove(reactions, "$evt") assert len(result) == 1 assert result[0]["m.relates_to"]["key"] == "👍" def test_build_batch_remove_different_targets(self): from yuxi.channels.adapters.matrix.reaction_utils import build_batch_remove reactions = [ {"key": "👍", "target_event_id": "$evt1"}, {"key": "❤️", "target_event_id": "$evt2"}, ] result = build_batch_remove(reactions, "$evt1") assert len(result) == 1 def test_summarize_reactions_empty(self): from yuxi.channels.adapters.matrix.reaction_utils import summarize_reactions assert summarize_reactions([]) == {} def test_summarize_reactions_skip_empty_key(self): from yuxi.channels.adapters.matrix.reaction_utils import summarize_reactions events = [ {"sender": "@a:x", "key": "", "target_event_id": "$evt"}, {"sender": "@b:x", "key": "👍", "target_event_id": "$evt"}, ] result = summarize_reactions(events) assert "$evt" in result assert result["$evt"]["unique_reactors"] == 1 # ============================================================================= # Send edges # ============================================================================= class TestSendEdges: def test_classify_send_error_missing_token(self): from yuxi.channels.adapters.matrix.send import classify_send_error exc = Exception() exc.errcode = "M_MISSING_TOKEN" error_type, _ = classify_send_error(exc) assert error_type == "auth" @pytest.mark.asyncio async def test_send_formatted_text_no_reply_when_off(self): from yuxi.channels.adapters.matrix.send import send_formatted_text mock_client = MagicMock() resp = MagicMock() resp.event_id = "$evt" mock_client.room_send = AsyncMock(return_value=resp) identity = ChannelIdentity( channel_id="matrix", channel_type=ChannelType.MATRIX, channel_user_id="", channel_chat_id="!room", channel_message_id="$msg", ) response = ChannelResponse(identity=identity, content="hello", reply_to_message_id="$original") result = await send_formatted_text(mock_client, "!room", response, reply_to_mode="off") assert result.success is True @pytest.mark.asyncio async def test_send_reaction_success(self): from yuxi.channels.adapters.matrix.send import send_reaction mock_client = MagicMock() resp = MagicMock() resp.event_id = "$reaction001" mock_client.room_send = AsyncMock(return_value=resp) result = await send_reaction(mock_client, "!room:example.org", "$target", "👍") assert result.success is True assert result.message_id == "$reaction001" @pytest.mark.asyncio async def test_send_thread_message_success(self): from yuxi.channels.adapters.matrix.send import send_thread_message mock_client = MagicMock() resp = MagicMock() resp.event_id = "$thread001" mock_client.room_send = AsyncMock(return_value=resp) result = await send_thread_message(mock_client, "!room:example.org", "$root", "thread reply") assert result.success is True assert result.message_id == "$thread001" @pytest.mark.asyncio async def test_send_location_success(self): from yuxi.channels.adapters.matrix.send import send_location mock_client = MagicMock() resp = MagicMock() resp.event_id = "$loc001" mock_client.room_send = AsyncMock(return_value=resp) result = await send_location(mock_client, "!room:example.org", 51.5, -0.12) assert result.success is True @pytest.mark.asyncio async def test_send_sticker_success(self): from yuxi.channels.adapters.matrix.send import send_sticker mock_client = MagicMock() resp = MagicMock() resp.event_id = "$sticker001" mock_client.room_send = AsyncMock(return_value=resp) result = await send_sticker(mock_client, "!room:example.org", "mxc://example.org/sticker") assert result.success is True @pytest.mark.asyncio async def test_send_formatted_media_upload_failure(self): from yuxi.channels.adapters.matrix.send import send_formatted_media mock_client = MagicMock() mock_client.upload = AsyncMock(side_effect=Exception("upload fail")) result = await send_formatted_media(mock_client, "!room", "image", b"data", "image/png") assert result.success is False assert "upload" in result.error.lower() @pytest.mark.asyncio async def test_send_stream_chunk_new_message(self): from yuxi.channels.adapters.matrix.send import send_stream_chunk mock_client = MagicMock() resp = MagicMock() resp.event_id = "$stream001" mock_client.room_send = AsyncMock(return_value=resp) result = await send_stream_chunk(mock_client, "!room", "$msg1", "hello", finished=False, stream_state={}) assert result.success is True @pytest.mark.asyncio async def test_send_stream_chunk_delta_update(self): from yuxi.channels.adapters.matrix.send import send_stream_chunk mock_client = MagicMock() resp = MagicMock() resp.event_id = "$stream002" mock_client.room_send = AsyncMock(return_value=resp) stream_state = {"!room:$msg1": 5} result = await send_stream_chunk( mock_client, "!room", "$msg1", "hello world", finished=False, stream_state=stream_state, ) assert result.success is True # ============================================================================= # Location edges # ============================================================================= class TestLocationEdges: def test_format_location_text_body_only(self): from yuxi.channels.adapters.matrix.location import format_location_text text = format_location_text("Some place", "") assert "[位置]" in text assert "Some place" in text def test_format_location_text_uri_only(self): from yuxi.channels.adapters.matrix.location import format_location_text text = format_location_text("", "geo:1,2") assert "[位置]" in text assert "geo:1,2" in text def test_format_location_text_empty(self): from yuxi.channels.adapters.matrix.location import format_location_text assert format_location_text("", "") == "[位置]" def test_extract_geo_coordinates_with_params(self): from yuxi.channels.adapters.matrix.location import extract_geo_coordinates result = extract_geo_coordinates("geo:51.5,-0.12;u=35") assert result is not None assert result["lat"] == 51.5 def test_extract_geo_coordinates_malformed(self): from yuxi.channels.adapters.matrix.location import extract_geo_coordinates assert extract_geo_coordinates("geo:abc,def") is None # ============================================================================= # Sticker edges # ============================================================================= class TestStickerEdges: def test_parse_sticker_no_body(self): from yuxi.channels.adapters.matrix.sticker import parse_sticker result = parse_sticker({"content": {"msgtype": "m.sticker", "url": "mxc://example.org/st"}}) assert result is not None assert result["text"] == "[贴纸]" def test_build_outbound_sticker_no_dimensions(self): from yuxi.channels.adapters.matrix.sticker import build_outbound_sticker result = build_outbound_sticker("mxc://example.org/st") assert result["msgtype"] == "m.sticker" assert "w" not in result["info"] def test_build_outbound_sticker_dimensions_only_one_set(self): from yuxi.channels.adapters.matrix.sticker import build_outbound_sticker result = build_outbound_sticker("mxc://example.org/st", width=100, height=0) assert "w" not in result["info"] # ============================================================================= # Adapter edge cases # ============================================================================= class TestAdapterReportError: @pytest.mark.asyncio async def test_check_mention_not_mentioned(self): from yuxi.channels.adapters.matrix.group_mentions import GroupMentionStrategy strategy = GroupMentionStrategy({"user_id": "@bot:example.org"}) result = strategy.evaluate_room_policy( "!room:example.org", "@user:example.org", "hello", "", ) assert result is False # ============================================================================= # GroupMentions edges # ============================================================================= class TestGroupMentionsEdges: def test_get_room_mention_policy(self): from yuxi.channels.adapters.matrix.group_mentions import GroupMentionStrategy config = { "requireMention": True, "rooms": { "!room:example.org": { "requireMention": False, "allowedRoles": ["admin"], "mentionExemptUsers": ["@admin:example.org"], }, }, } strategy = GroupMentionStrategy(config) policy = strategy.get_room_mention_policy("!room:example.org") assert policy["require_mention"] is False assert policy["allowed_roles"] == ["admin"] assert policy["mention_exempt_users"] == ["@admin:example.org"] def test_get_room_mention_policy_defaults(self): from yuxi.channels.adapters.matrix.group_mentions import GroupMentionStrategy strategy = GroupMentionStrategy({}) policy = strategy.get_room_mention_policy("!unknown:example.org") assert policy["require_mention"] is True assert policy["allowed_roles"] == [] def test_evaluate_room_policy_no_mention_not_required(self): from yuxi.channels.adapters.matrix.group_mentions import GroupMentionStrategy strategy = GroupMentionStrategy({"requireMention": False, "user_id": "@bot:example.org"}) assert strategy.evaluate_room_policy("!room:example.org", "@user:x", "hello", "") is True # ============================================================================= # ConfigPatch edges # ============================================================================= class TestConfigPatchEdges: def test_apply_patch_with_lists(self): from yuxi.channels.adapters.matrix.config_patch import MatrixAccountPatch, apply_patch config = {"user_id": "@bot:a"} patch = MatrixAccountPatch.from_dict({"dm_allow_from": ["@a:x", "@b:x"]}) result = apply_patch(config, patch) assert result["dm_allow_from"] == ["@a:x", "@b:x"] def test_apply_patch_with_rooms(self): from yuxi.channels.adapters.matrix.config_patch import MatrixAccountPatch, apply_patch config = {"user_id": "@bot:a", "rooms": {}} patch = MatrixAccountPatch.from_dict({"rooms": {"!r:x": {"allow": False}}}) result = apply_patch(config, patch) assert result["rooms"] == {"!r:x": {"allow": False}} def test_merge_patches_empty_overlay(self): from yuxi.channels.adapters.matrix.config_patch import MatrixAccountPatch, merge_patches base = MatrixAccountPatch(display_name="A") overlay = MatrixAccountPatch() merged = merge_patches(base, overlay) assert merged.display_name == "A" def test_to_dict_ignores_none(self): from yuxi.channels.adapters.matrix.config_patch import MatrixAccountPatch patch = MatrixAccountPatch(display_name="A", sync_timeout_ms=None) d = patch.to_dict() assert "display_name" in d assert "sync_timeout_ms" not in d # ============================================================================= # Encryption edges # ============================================================================= class TestEncryptionEdges: def test_check_crypto_store_ready_not_dir(self, tmp_path): from yuxi.channels.adapters.matrix.encryption import check_crypto_store_ready file_path = tmp_path / "not_a_dir" file_path.write_text("content") result = check_crypto_store_ready(str(file_path)) assert result["status"] == "unavailable" def test_check_crypto_store_ready_empty(self, tmp_path): from yuxi.channels.adapters.matrix.encryption import check_crypto_store_ready empty_dir = tmp_path / "empty_store" empty_dir.mkdir() result = check_crypto_store_ready(str(empty_dir)) assert result["status"] == "empty" def test_check_crypto_store_ready_with_keys(self, tmp_path): from yuxi.channels.adapters.matrix.encryption import check_crypto_store_ready store_dir = tmp_path / "store_with_keys" store_dir.mkdir() (store_dir / "room_keys.json").write_text('{"rooms":{}}') result = check_crypto_store_ready(str(store_dir)) assert result["status"] == "ready" def test_format_encryption_unavailable_error_no_crypto_dir(self): from yuxi.channels.adapters.matrix.encryption import format_encryption_unavailable_error msg = format_encryption_unavailable_error({"homeserver": "https://example.org"}) assert "Set 'crypto_store_dir'" in msg @pytest.mark.asyncio async def test_decrypt_event_list_result(self): from yuxi.channels.adapters.matrix.encryption import decrypt_event mock_client = MagicMock() mock_client.decrypt_event = AsyncMock(return_value=[{"decrypted": True}]) result = await decrypt_event(mock_client, MagicMock(), "!room:example.org") assert result is not None assert result == {"decrypted": True} @pytest.mark.asyncio async def test_decrypt_event_dict_result(self): from yuxi.channels.adapters.matrix.encryption import decrypt_event mock_client = MagicMock() mock_client.decrypt_event = AsyncMock(return_value={"decrypted": True}) result = await decrypt_event(mock_client, MagicMock(), "!room:example.org") assert result is not None assert result == {"decrypted": True} @pytest.mark.asyncio async def test_decrypt_event_failure(self): from yuxi.channels.adapters.matrix.encryption import decrypt_event mock_client = MagicMock() mock_client.decrypt_event = AsyncMock(side_effect=Exception("fail")) result = await decrypt_event(mock_client, MagicMock(), "!room:example.org") assert result is None @pytest.mark.asyncio async def test_verify_device_success(self): from yuxi.channels.adapters.matrix.encryption import verify_device mock_client = MagicMock() mock_client.verify_device = AsyncMock(return_value=True) result = await verify_device(mock_client, "@user:x", "DEVICE1") assert result["status"] == "ok" assert result["verified"] is True @pytest.mark.asyncio async def test_verify_device_failure(self): from yuxi.channels.adapters.matrix.encryption import verify_device mock_client = MagicMock() mock_client.verify_device = AsyncMock(side_effect=Exception("fail")) result = await verify_device(mock_client, "@user:x", "DEVICE1") assert result["status"] == "error" @pytest.mark.asyncio async def test_check_cross_signing_success(self): from yuxi.channels.adapters.matrix.encryption import check_cross_signing mock_client = MagicMock() resp = MagicMock() resp.cross_signing_keys = {"key1": "val1"} mock_client.query_keys = AsyncMock(return_value=resp) result = await check_cross_signing(mock_client) assert result["status"] == "ok" assert result["cross_signing_ready"] is True @pytest.mark.asyncio async def test_check_cross_signing_no_keys(self): from yuxi.channels.adapters.matrix.encryption import check_cross_signing mock_client = MagicMock() resp = MagicMock() resp.cross_signing_keys = {} mock_client.query_keys = AsyncMock(return_value=resp) result = await check_cross_signing(mock_client) assert result["status"] == "ok" assert result["cross_signing_ready"] is False @pytest.mark.asyncio async def test_check_cross_signing_failure(self): from yuxi.channels.adapters.matrix.encryption import check_cross_signing mock_client = MagicMock() mock_client.query_keys = AsyncMock(side_effect=Exception("fail")) result = await check_cross_signing(mock_client) assert result["status"] == "error" # ============================================================================= # SyncStore edges # ============================================================================= class TestSyncStoreEdges: def test_save_sync_token_empty(self, tmp_path): from yuxi.channels.adapters.matrix.sync_store import save_sync_token save_sync_token(str(tmp_path), "") assert True def test_load_sync_token_corrupted(self, tmp_path): from yuxi.channels.adapters.matrix.sync_store import load_sync_token token_file = tmp_path / "matrix_sync_token.json" token_file.write_text("not json") result = load_sync_token(str(tmp_path)) assert result is None def test_sync_token_store_backup_recovery(self, tmp_path): from yuxi.channels.adapters.matrix.sync_store import SyncTokenStore store = SyncTokenStore(str(tmp_path), "@user:example.org") store.save("s12345") token_path = tmp_path / "sync_token_@user_example.org.json" bak_path = tmp_path / "sync_token_@user_example.org.json.bak" if token_path.exists() and bak_path.exists(): token_path.unlink() loaded = store.load() assert loaded == "s12345" # ============================================================================= # Accounts edges # ============================================================================= class TestAccountsEdges: def test_active_client_tracker_transitions(self): from yuxi.channels.adapters.matrix.accounts import ActiveClientTracker, ClientLifecycleState tracker = ActiveClientTracker() assert tracker.active_count == 0 tracker.register("acc1", MagicMock(), ClientLifecycleState.PREPARED) assert tracker.get_state("acc1") == ClientLifecycleState.PREPARED assert tracker.transition("acc1", ClientLifecycleState.STARTED) is True assert tracker.active_count == 1 assert tracker.transition("acc1", ClientLifecycleState.PREPARED) is False tracker.unregister("acc1") assert tracker.get_state("acc1") == ClientLifecycleState.NONE def test_active_client_tracker_list_active(self): from yuxi.channels.adapters.matrix.accounts import ActiveClientTracker, ClientLifecycleState tracker = ActiveClientTracker() tracker.register("a", MagicMock(), ClientLifecycleState.STARTED) tracker.register("b", MagicMock(), ClientLifecycleState.PREPARED) assert tracker.list_active() == ["a"] assert tracker.list_all_ids() == ["a", "b"] def test_active_client_tracker_invalid_transition(self): from yuxi.channels.adapters.matrix.accounts import ActiveClientTracker, ClientLifecycleState tracker = ActiveClientTracker() tracker.register("acc", MagicMock(), ClientLifecycleState.NONE) assert tracker.transition("acc", ClientLifecycleState.STARTED) is False def test_multi_account_manager_default_account_fallback(self): from yuxi.channels.adapters.matrix.accounts import MultiAccountManager config = {"accounts": {"bot1": {"user_id": "@bot1:example.org"}}} mgr = MultiAccountManager(config) assert mgr.default_account_id == "bot1" def test_multi_account_manager_empty(self): from yuxi.channels.adapters.matrix.accounts import MultiAccountManager mgr = MultiAccountManager({"accounts": {}}) assert mgr.default_account is None assert mgr.account_count == 0 def test_multi_account_manager_no_accounts_key(self): from yuxi.channels.adapters.matrix.accounts import MultiAccountManager mgr = MultiAccountManager({}) assert mgr.default_account is None # ============================================================================= # Commands edges # ============================================================================= class TestCommandsEdges: def test_detect_slash_command_with_args_model(self): from yuxi.channels.adapters.matrix.commands import detect_slash_command result = detect_slash_command("/model gpt-4") assert result is not None assert result["command"] == "/model" assert result["args"].get("model_name") == "gpt-4" def test_detect_slash_command_proxy_no_args(self): from yuxi.channels.adapters.matrix.commands import detect_slash_command result = detect_slash_command("/proxy") assert result is not None assert result["command"] == "/proxy" assert "spawn" not in result["args"] or not result["args"].get("spawn") def test_is_slash_command_with_extra_whitespace(self): from yuxi.channels.adapters.matrix.commands import is_slash_command assert is_slash_command(" /help") is False assert is_slash_command("/help ") is True def test_command_list_all_covered(self): from yuxi.channels.adapters.matrix.commands import get_command_list cmds = get_command_list() assert len(cmds) >= 9 def test_format_help_contains_all_commands(self): from yuxi.channels.adapters.matrix.commands import format_help_text text = format_help_text() for cmd in ["/new", "/reset", "/model", "/help", "/status", "/channels", "/proxy", "/skill", "/room_info"]: assert cmd in text # ============================================================================= # ThreadBinding edges # ============================================================================= class TestThreadBindingEdges: def test_thread_binding_touch_updates_activity(self): import time from yuxi.channels.adapters.matrix.thread_bindings import ThreadBinding b = ThreadBinding("!room:example.org", "$root") old_activity = b.last_activity time.sleep(0.001) b.touch() assert b.last_activity > old_activity def test_thread_binding_properties_init(self): from yuxi.channels.adapters.matrix.thread_bindings import ThreadBinding b = ThreadBinding("!room:example.org", "$root", session_key="s1") assert b.room_id == "!room:example.org" assert b.root_event_id == "$root" assert b.session_key == "s1" assert b.bound_at > 0 assert b.last_activity > 0 def test_binding_manager_max_age_expiry(self): import time from yuxi.channels.adapters.matrix.thread_bindings import ThreadBindingManager mgr = ThreadBindingManager({"threadBindings": {"maxAgeHours": 0.001}}) binding = mgr.bind("!room:example.org", "$root") binding.bound_at = time.monotonic() - 100 expired = mgr.check_timeouts() assert len(expired) == 1 def test_binding_manager_get_room_bindings(self): from yuxi.channels.adapters.matrix.thread_bindings import ThreadBindingManager mgr = ThreadBindingManager({}) mgr.bind("!room1:example.org", "$root1") mgr.bind("!room2:example.org", "$root2") bindings = mgr.get_room_bindings("!room1:example.org") assert len(bindings) == 1 def test_binding_manager_list_bindings(self): from yuxi.channels.adapters.matrix.thread_bindings import ThreadBindingManager mgr = ThreadBindingManager({}) mgr.bind("!room:example.org", "$root1") mgr.bind("!room:example.org", "$root2") assert len(mgr.list_bindings()) == 2 def test_binding_manager_config_disabled(self): from yuxi.channels.adapters.matrix.thread_bindings import ThreadBindingManager mgr = ThreadBindingManager({"threadBindings": {"enabled": False, "spawnSubagentSessions": True}}) b = mgr.bind("!room:example.org", "$root") assert b.is_subagent is True # ============================================================================= # ACPBinding edges # ============================================================================= class TestACPBindingsEdges: @pytest.mark.asyncio async def test_acp_binding_spawn(self): from yuxi.channels.adapters.matrix.acp_bindings import ACPBinding b = ACPBinding("!room:example.org", "$root", acp_session_id="acp1") assert not b.bound result = await b.spawn(MagicMock()) assert b.bound assert result["status"] == "spawned" @pytest.mark.asyncio async def test_acp_binding_bind(self): from yuxi.channels.adapters.matrix.acp_bindings import ACPBinding b = ACPBinding("!room:example.org", "$root") result = await b.bind(MagicMock()) assert result["status"] == "bound" @pytest.mark.asyncio async def test_acp_binding_already_bound(self): from yuxi.channels.adapters.matrix.acp_bindings import ACPBinding b = ACPBinding("!room:example.org", "$root") b.bound = True result = await b.bind(MagicMock()) assert result["status"] == "already_bound" @pytest.mark.asyncio async def test_acp_binding_unbind(self): from yuxi.channels.adapters.matrix.acp_bindings import ACPBinding b = ACPBinding("!room:example.org", "$root") b.bound = True result = await b.unbind() assert not b.bound assert result["status"] == "unbound" def test_acp_binding_to_dict(self): from yuxi.channels.adapters.matrix.acp_bindings import ACPBinding b = ACPBinding("!room:example.org", "$root", "sess1", "thread") d = b.to_dict() assert d["room_id"] == "!room:example.org" assert d["acp_session_id"] == "sess1" def test_acp_manager_create_no_thread_root(self): from yuxi.channels.adapters.matrix.acp_bindings import ACPBindingManager mgr = ACPBindingManager({}) b = mgr.create_binding("!room:example.org", "") assert b.room_id == "!room:example.org" def test_acp_manager_remove_nonexistent(self): from yuxi.channels.adapters.matrix.acp_bindings import ACPBindingManager mgr = ACPBindingManager({}) assert mgr.remove("!room:example.org", "$nonexistent") is False # ============================================================================= # EnvVars edges # ============================================================================= class TestEnvVarsEdges: def test_resolve_env_vars_nested_key(self): import os from yuxi.channels.adapters.matrix.env_vars import resolve_env_vars os.environ["MATRIX_DM_POLICY"] = "open" try: result = resolve_env_vars({"user_id": "@bot:example.org"}) assert "dm" in result assert result["dm"]["policy"] == "open" finally: del os.environ["MATRIX_DM_POLICY"] def test_coerce_type_float(self): from yuxi.channels.adapters.matrix.env_vars import _coerce_type assert _coerce_type("3.14", 1.0) == 3.14 def test_coerce_type_float_invalid(self): from yuxi.channels.adapters.matrix.env_vars import _coerce_type assert _coerce_type("abc", 1.0) == "abc" def test_coerce_type_none_existing(self): from yuxi.channels.adapters.matrix.env_vars import _coerce_type assert _coerce_type("hello", None) == "hello" # ============================================================================= # Probe edges # ============================================================================= class TestProbeEdges: @pytest.mark.asyncio async def test_check_backup_health_all_unreachable(self): from yuxi.channels.adapters.matrix.probe import _check_backup_health mock_client = MagicMock() mock_client.keys_upload = AsyncMock(side_effect=Exception()) mock_client.keys_query = AsyncMock(side_effect=Exception()) mock_client.query_keys = AsyncMock(side_effect=Exception()) result = await _check_backup_health(mock_client, 5) assert result["status"] == "unknown" def test_describe_backup_state_unknown_code(self): from yuxi.channels.adapters.matrix.probe import describe_backup_state result = describe_backup_state(999) assert result == "unknown_state_999" @pytest.mark.asyncio async def test_probe_matrix_timeout(self): import asyncio from yuxi.channels.adapters.matrix.probe import probe_matrix mock_client = MagicMock() mock_client.versions = AsyncMock(side_effect=asyncio.TimeoutError()) mock_client.homeserver = "https://example.org" mock_client.user_id = "@bot:example.org" result = await probe_matrix(mock_client, timeout_s=0.1) assert result.status == "unhealthy" @pytest.mark.asyncio async def test_probe_matrix_connection_error(self): from yuxi.channels.adapters.matrix.probe import probe_matrix mock_client = MagicMock() mock_client.versions = AsyncMock(side_effect=ConnectionError("refused")) mock_client.homeserver = "https://example.org" mock_client.user_id = "@bot:example.org" result = await probe_matrix(mock_client, timeout_s=10) assert result.status == "unhealthy" @pytest.mark.asyncio async def test_probe_matrix_os_error(self): from yuxi.channels.adapters.matrix.probe import probe_matrix mock_client = MagicMock() mock_client.versions = AsyncMock(side_effect=OSError("network unreachable")) mock_client.homeserver = "https://example.org" mock_client.user_id = "@bot:example.org" result = await probe_matrix(mock_client, timeout_s=10) assert result.status == "unhealthy" @pytest.mark.asyncio async def test_probe_matrix_generic_error(self): from yuxi.channels.adapters.matrix.probe import probe_matrix mock_client = MagicMock() mock_client.versions = AsyncMock(side_effect=RuntimeError("unexpected")) mock_client.homeserver = "https://example.org" mock_client.user_id = "@bot:example.org" result = await probe_matrix(mock_client, timeout_s=10) assert result.status == "degraded"