tests/gateway/test_dingtalk.py::TestDingTalkRequirements::test_returns_false_when_sdk_missing tests/gateway/test_dingtalk.py::TestDingTalkRequirements::test_returns_false_when_env_vars_missing tests/gateway/test_dingtalk.py::TestDingTalkRequirements::test_returns_true_when_all_available tests/gateway/test_dingtalk.py::TestDingTalkAdapterInit::test_reads_config_from_extra tests/gateway/test_dingtalk.py::TestDingTalkAdapterInit::test_falls_back_to_env_vars tests/gateway/test_dingtalk.py::TestExtractText::test_text_as_dict_legacy tests/gateway/test_dingtalk.py::TestExtractText::test_text_as_textcontent_object tests/gateway/test_dingtalk.py::TestExtractText::test_text_content_attr_with_empty_string tests/gateway/test_dingtalk.py::TestExtractText::test_rich_text_content_new_shape tests/gateway/test_dingtalk.py::TestExtractText::test_rich_text_legacy_shape tests/gateway/test_dingtalk.py::TestExtractText::test_empty_message tests/gateway/test_dingtalk.py::TestDeduplication::test_first_message_not_duplicate tests/gateway/test_dingtalk.py::TestDeduplication::test_second_same_message_is_duplicate tests/gateway/test_dingtalk.py::TestDeduplication::test_different_messages_not_duplicate tests/gateway/test_dingtalk.py::TestDeduplication::test_cache_cleanup_on_overflow tests/gateway/test_dingtalk.py::TestSend::test_send_posts_to_webhook tests/gateway/test_dingtalk.py::TestSend::test_send_fails_without_webhook tests/gateway/test_dingtalk.py::TestSend::test_send_uses_cached_webhook tests/gateway/test_dingtalk.py::TestSend::test_send_handles_http_error tests/gateway/test_dingtalk.py::TestSend::test_send_image_renders_markdown_image tests/gateway/test_dingtalk.py::TestSend::test_send_image_file_returns_explicit_unsupported_error tests/gateway/test_dingtalk.py::TestSend::test_send_document_returns_explicit_unsupported_error tests/gateway/test_dingtalk.py::TestConnect::test_disconnect_closes_session_websocket tests/gateway/test_dingtalk.py::TestConnect::test_connect_fails_without_sdk tests/gateway/test_dingtalk.py::TestConnect::test_connect_fails_without_credentials tests/gateway/test_dingtalk.py::TestConnect::test_disconnect_cleans_up tests/gateway/test_dingtalk.py::TestConnect::test_disconnect_finalizes_open_streaming_cards tests/gateway/test_dingtalk.py::TestWebhookDomainAllowlist::test_api_domain_accepted tests/gateway/test_dingtalk.py::TestWebhookDomainAllowlist::test_oapi_domain_accepted tests/gateway/test_dingtalk.py::TestWebhookDomainAllowlist::test_http_rejected tests/gateway/test_dingtalk.py::TestWebhookDomainAllowlist::test_suffix_attack_rejected tests/gateway/test_dingtalk.py::TestWebhookDomainAllowlist::test_unsanctioned_subdomain_rejected tests/gateway/test_dingtalk.py::TestHandlerProcessIsAsync::test_process_is_coroutine_function tests/gateway/test_dingtalk.py::TestExtractMedia::test_voice_rich_text_item_classified_as_voice tests/gateway/test_dingtalk.py::TestExtractMedia::test_audio_rich_text_item_stays_audio tests/gateway/test_dingtalk.py::TestAllowedUsersGate::test_empty_allowlist_allows_everyone tests/gateway/test_dingtalk.py::TestAllowedUsersGate::test_wildcard_allowlist_allows_everyone tests/gateway/test_dingtalk.py::TestAllowedUsersGate::test_matches_sender_id_case_insensitive tests/gateway/test_dingtalk.py::TestAllowedUsersGate::test_matches_staff_id tests/gateway/test_dingtalk.py::TestAllowedUsersGate::test_rejects_unknown_user tests/gateway/test_dingtalk.py::TestAllowedUsersGate::test_env_var_csv_populates_allowlist tests/gateway/test_dingtalk.py::TestMentionPatterns::test_empty_patterns_list tests/gateway/test_dingtalk.py::TestMentionPatterns::test_pattern_matches_text tests/gateway/test_dingtalk.py::TestMentionPatterns::test_pattern_is_case_insensitive tests/gateway/test_dingtalk.py::TestMentionPatterns::test_invalid_regex_is_skipped_not_raised tests/gateway/test_dingtalk.py::TestMentionPatterns::test_env_var_json_populates_patterns tests/gateway/test_dingtalk.py::TestMentionPatterns::test_env_var_newline_fallback_when_not_json tests/gateway/test_dingtalk.py::TestShouldProcessMessage::test_dm_always_accepted tests/gateway/test_dingtalk.py::TestShouldProcessMessage::test_group_rejected_when_require_mention_and_no_trigger tests/gateway/test_dingtalk.py::TestShouldProcessMessage::test_group_accepted_when_require_mention_disabled tests/gateway/test_dingtalk.py::TestShouldProcessMessage::test_group_accepted_when_bot_is_mentioned tests/gateway/test_dingtalk.py::TestShouldProcessMessage::test_group_accepted_when_text_matches_wake_word tests/gateway/test_dingtalk.py::TestShouldProcessMessage::test_group_accepted_when_chat_in_free_response_list tests/gateway/test_dingtalk.py::TestIncomingHandlerProcess::test_process_extracts_session_webhook tests/gateway/test_dingtalk.py::TestIncomingHandlerProcess::test_process_fallback_session_webhook_when_from_dict_misses_it tests/gateway/test_dingtalk.py::TestIncomingHandlerProcess::test_process_returns_ack_immediately tests/gateway/test_dingtalk.py::TestExtractTextMentions::test_preserves_at_mentions_in_text tests/gateway/test_dingtalk.py::TestExtractTextMentions::test_dingtalk_in_platform_enum tests/gateway/test_dingtalk.py::TestMessageContextIsolation::test_contexts_keyed_by_chat_id tests/gateway/test_dingtalk.py::TestCardLifecycle::test_final_reply_finalizes_card tests/gateway/test_dingtalk.py::TestCardLifecycle::test_intermediate_send_stays_streaming tests/gateway/test_dingtalk.py::TestCardLifecycle::test_done_fires_only_when_reply_to_is_set tests/gateway/test_dingtalk.py::TestCardLifecycle::test_edit_message_finalize_fires_done tests/gateway/test_dingtalk.py::TestCardLifecycle::test_edit_message_finalize_false_tracks_sibling tests/gateway/test_dingtalk.py::TestCardLifecycle::test_next_send_auto_closes_sibling_streaming_cards tests/gateway/test_dingtalk.py::TestCardLifecycle::test_edit_message_requires_message_id tests/gateway/test_dingtalk.py::TestCardLifecycle::test_fire_done_reaction_is_idempotent tests/gateway/test_dingtalk.py::TestDingTalkAdapterAICards::test_send_uses_ai_card_if_configured