507 lines
19 KiB
Python
507 lines
19 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
|
|
||
|
|
import httpx
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from yuxi.channels.models import DeliveryResult
|
||
|
|
|
||
|
|
|
||
|
|
class TestWeChatSetupWizardValidation:
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_wecom_success(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
wizard._config_snapshot = {
|
||
|
|
"corp_id": "test_corp_id",
|
||
|
|
"corp_secret": "test_corp_secret",
|
||
|
|
"agent_id": "1000001",
|
||
|
|
}
|
||
|
|
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_resp = MagicMock()
|
||
|
|
mock_resp.headers = {"content-type": "application/json"}
|
||
|
|
mock_resp.json.return_value = {"access_token": "test_token_123"}
|
||
|
|
mock_client.get.return_value = mock_resp
|
||
|
|
|
||
|
|
result = await wizard._probe_connection(mock_client)
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["next_step"] == "webhook"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_wecom_failure(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
wizard._config_snapshot = {
|
||
|
|
"corp_id": "invalid_corp",
|
||
|
|
"corp_secret": "invalid_secret",
|
||
|
|
"agent_id": "1000001",
|
||
|
|
}
|
||
|
|
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_resp = MagicMock()
|
||
|
|
mock_resp.headers = {"content-type": "application/json"}
|
||
|
|
mock_resp.json.return_value = {"errcode": 40001, "errmsg": "invalid credential"}
|
||
|
|
mock_client.get.return_value = mock_resp
|
||
|
|
|
||
|
|
result = await wizard._probe_connection(mock_client)
|
||
|
|
assert result["success"] is False
|
||
|
|
assert result["next_step"] == "credentials"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_mp_success(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "mp"
|
||
|
|
wizard._config_snapshot = {
|
||
|
|
"app_id": "wx_test_app",
|
||
|
|
"app_secret": "test_app_secret",
|
||
|
|
}
|
||
|
|
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_resp = MagicMock()
|
||
|
|
mock_resp.headers = {"content-type": "application/json"}
|
||
|
|
mock_resp.json.return_value = {"access_token": "mp_test_token"}
|
||
|
|
mock_client.get.return_value = mock_resp
|
||
|
|
|
||
|
|
result = await wizard._probe_connection(mock_client)
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["next_step"] == "webhook"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_mp_failure(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "mp"
|
||
|
|
wizard._config_snapshot = {
|
||
|
|
"app_id": "invalid_app",
|
||
|
|
"app_secret": "invalid_secret",
|
||
|
|
}
|
||
|
|
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_resp = MagicMock()
|
||
|
|
mock_resp.headers = {"content-type": "application/json"}
|
||
|
|
mock_resp.json.return_value = {"errcode": 40001}
|
||
|
|
mock_client.get.return_value = mock_resp
|
||
|
|
|
||
|
|
result = await wizard._probe_connection(mock_client)
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_bridge_success(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "personal"
|
||
|
|
wizard._config_snapshot = {"bridge_url": "http://localhost:5555"}
|
||
|
|
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_health = MagicMock()
|
||
|
|
mock_health.status_code = 200
|
||
|
|
mock_login = MagicMock()
|
||
|
|
mock_login.json.return_value = {"logged_in": True}
|
||
|
|
|
||
|
|
mock_client.get.side_effect = [mock_health, mock_login]
|
||
|
|
|
||
|
|
result = await wizard._probe_connection(mock_client)
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["next_step"] == "webhook"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_bridge_health_failure(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "personal"
|
||
|
|
wizard._config_snapshot = {"bridge_url": "http://localhost:5555"}
|
||
|
|
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_health = MagicMock()
|
||
|
|
mock_health.status_code = 500
|
||
|
|
mock_client.get.return_value = mock_health
|
||
|
|
|
||
|
|
result = await wizard._probe_connection(mock_client)
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_network_error(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "personal"
|
||
|
|
wizard._config_snapshot = {"bridge_url": "http://localhost:5555"}
|
||
|
|
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_client.get.side_effect = httpx.ConnectError("Connection refused")
|
||
|
|
|
||
|
|
result = await wizard._probe_connection(mock_client)
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_requires_mode_and_creds(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
result = await wizard.validate_connection()
|
||
|
|
assert result["success"] is False
|
||
|
|
assert "No credentials" in result["error"]
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_empty_credentials(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
wizard._config_snapshot = {"corp_id": "", "corp_secret": ""}
|
||
|
|
result = await wizard.validate_connection()
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_connection_with_factory(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
wizard._config_snapshot = {
|
||
|
|
"corp_id": "test_corp",
|
||
|
|
"corp_secret": "test_secret",
|
||
|
|
"agent_id": "1000001",
|
||
|
|
}
|
||
|
|
|
||
|
|
mock_client = AsyncMock()
|
||
|
|
mock_resp = MagicMock()
|
||
|
|
mock_resp.headers = {"content-type": "application/json"}
|
||
|
|
mock_resp.json.return_value = {"access_token": "test_token"}
|
||
|
|
mock_client.get.return_value = mock_resp
|
||
|
|
|
||
|
|
factory = lambda: mock_client
|
||
|
|
result = await wizard.validate_connection(http_client_factory=factory)
|
||
|
|
assert result["success"] is True
|
||
|
|
|
||
|
|
|
||
|
|
class TestSetupWizardLifecycle:
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_select_valid_mode(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
result = await wizard.select_mode("wecom")
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["mode"] == "wecom"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_select_invalid_mode(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
result = await wizard.select_mode("invalid")
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_set_credentials_wecom_valid(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
|
||
|
|
result = await wizard.set_credentials({
|
||
|
|
"corp_id": "test_corp",
|
||
|
|
"corp_secret": "test_secret",
|
||
|
|
"agent_id": "1000001",
|
||
|
|
})
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["next_step"] == "validation"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_set_credentials_missing_fields(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
|
||
|
|
result = await wizard.set_credentials({
|
||
|
|
"corp_id": "test_corp",
|
||
|
|
})
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_set_credentials_no_mode(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
result = await wizard.set_credentials({"corp_id": "test"})
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_configure_webhook(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
wizard._config_snapshot = {"corp_id": "test"}
|
||
|
|
|
||
|
|
result = await wizard.configure_webhook("https://example.com/webhook")
|
||
|
|
assert result["success"] is True
|
||
|
|
assert wizard._config_snapshot["webhook_url"] == "https://example.com/webhook"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_confirm_and_apply(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import (
|
||
|
|
WeChatSetupWizard,
|
||
|
|
WizardStep,
|
||
|
|
)
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
wizard._config_snapshot = {"corp_id": "test", "corp_secret": "test", "agent_id": "1000001"}
|
||
|
|
wizard._current_step = WizardStep.CONFIRMATION
|
||
|
|
|
||
|
|
result = await wizard.confirm_and_apply()
|
||
|
|
assert result["success"] is True
|
||
|
|
assert result["config"]["enabled"] is True
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_confirm_before_complete_fails(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
result = await wizard.confirm_and_apply()
|
||
|
|
assert result["success"] is False
|
||
|
|
|
||
|
|
def test_reset(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "wecom"
|
||
|
|
wizard._config_snapshot = {"corp_id": "test"}
|
||
|
|
wizard.reset()
|
||
|
|
|
||
|
|
assert wizard._selected_mode == ""
|
||
|
|
assert wizard._config_snapshot == {}
|
||
|
|
|
||
|
|
def test_get_current_step_info(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
wizard._selected_mode = "mp"
|
||
|
|
wizard._config_snapshot = {"app_id": "wx_test"}
|
||
|
|
|
||
|
|
info = wizard.get_current_step_info()
|
||
|
|
assert info["mode"] == "mp"
|
||
|
|
|
||
|
|
def test_get_available_modes(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupWizard
|
||
|
|
|
||
|
|
wizard = WeChatSetupWizard()
|
||
|
|
modes = wizard.get_available_modes()
|
||
|
|
assert len(modes) == 3
|
||
|
|
|
||
|
|
mode_ids = {m["id"] for m in modes}
|
||
|
|
assert mode_ids == {"wecom", "mp", "personal"}
|
||
|
|
|
||
|
|
|
||
|
|
class TestWeChatSetupAdapter:
|
||
|
|
def test_wizard_summary_wecom(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupAdapter
|
||
|
|
|
||
|
|
config = {
|
||
|
|
"corp_id": "test_corp",
|
||
|
|
"corp_secret": "test_secret",
|
||
|
|
"agent_id": "1000001",
|
||
|
|
"enabled": True,
|
||
|
|
"webhook_url": "https://example.com/webhook",
|
||
|
|
}
|
||
|
|
summary = WeChatSetupAdapter.get_wizard_summary(config)
|
||
|
|
assert summary["mode"] == "wecom"
|
||
|
|
assert summary["configured"] is True
|
||
|
|
assert summary["webhook_configured"] is True
|
||
|
|
|
||
|
|
def test_wizard_summary_mp(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupAdapter
|
||
|
|
|
||
|
|
config = {"app_id": "wx_test", "app_secret": "test_secret", "enabled": True}
|
||
|
|
summary = WeChatSetupAdapter.get_wizard_summary(config)
|
||
|
|
assert summary["mode"] == "mp"
|
||
|
|
assert summary["configured"] is True
|
||
|
|
|
||
|
|
def test_wizard_summary_personal(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupAdapter
|
||
|
|
|
||
|
|
config = {"bridge_url": "http://localhost:5555", "enabled": False}
|
||
|
|
summary = WeChatSetupAdapter.get_wizard_summary(config)
|
||
|
|
assert summary["mode"] == "personal"
|
||
|
|
assert summary["configured"] is True
|
||
|
|
assert summary["enabled"] is False
|
||
|
|
|
||
|
|
def test_wizard_summary_unconfigured(self):
|
||
|
|
from yuxi.channels.adapters.wechat.setup_wizard import WeChatSetupAdapter
|
||
|
|
|
||
|
|
summary = WeChatSetupAdapter.get_wizard_summary({})
|
||
|
|
assert summary["mode"] == "unconfigured"
|
||
|
|
assert summary["configured"] is False
|
||
|
|
|
||
|
|
|
||
|
|
class TestAdapterDedupEnhancement:
|
||
|
|
def test_dedup_ttl_from_config(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({
|
||
|
|
"bridge_url": "http://localhost:5555",
|
||
|
|
"dm_policy": "open",
|
||
|
|
"dedup_ttl_seconds": 5.0,
|
||
|
|
"max_dedup_entries": 500,
|
||
|
|
})
|
||
|
|
assert adapter._dedup_ttl == 5.0
|
||
|
|
assert adapter._max_dedup_entries == 500
|
||
|
|
|
||
|
|
def test_dedup_ttl_default(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
assert adapter._dedup_ttl == 1.0
|
||
|
|
assert adapter._max_dedup_entries == 10000
|
||
|
|
|
||
|
|
def test_export_dedup_state(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
adapter._dedup = {("user1", "msg1"): 100.0, ("user2", "msg2"): 200.0}
|
||
|
|
adapter._dedup_ttl = 60.0
|
||
|
|
|
||
|
|
state = adapter.export_dedup_state()
|
||
|
|
assert state["dedup_ttl"] == 60.0
|
||
|
|
assert state["max_entries"] == 10000
|
||
|
|
assert state["entry_count"] >= 0
|
||
|
|
|
||
|
|
def test_import_dedup_state(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
adapter._dedup = {}
|
||
|
|
|
||
|
|
import time
|
||
|
|
|
||
|
|
now = time.monotonic()
|
||
|
|
state = {
|
||
|
|
"dedup_ttl": 10.0,
|
||
|
|
"entries": {
|
||
|
|
"user1:12345": now,
|
||
|
|
"user2:67890": now,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
adapter.import_dedup_state(state)
|
||
|
|
assert len(adapter._dedup) >= 2
|
||
|
|
|
||
|
|
def test_state_snapshot_includes_dedup(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
adapter._dedup = {("u1", "c1"): 100.0, ("u2", "c2"): 200.0}
|
||
|
|
|
||
|
|
snapshot = adapter.state_snapshot
|
||
|
|
assert snapshot.probe["dedup_entries"] == 2
|
||
|
|
assert snapshot.probe["dedup_ttl"] == 1.0
|
||
|
|
|
||
|
|
|
||
|
|
class TestAdapterMPMediaEnhancement:
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_send_mp_media_routes_voice(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
from yuxi.channels.adapters.wechat.mp.client import MPClient
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
adapter._mode = "mp"
|
||
|
|
adapter._mp_client = MPClient(AsyncMock(), {"app_id": "wx_test", "app_secret": "test"})
|
||
|
|
|
||
|
|
with patch(
|
||
|
|
"yuxi.channels.adapters.wechat.mp.send.send_mp_voice",
|
||
|
|
new_callable=AsyncMock,
|
||
|
|
) as mock_send:
|
||
|
|
mock_send.return_value = DeliveryResult(success=True)
|
||
|
|
adapter._http_client = AsyncMock()
|
||
|
|
|
||
|
|
result = await adapter._send_mp_media("voice", b"voice_data", "user123")
|
||
|
|
assert result.success is True
|
||
|
|
mock_send.assert_called_once()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_send_mp_media_routes_video(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
from yuxi.channels.adapters.wechat.mp.client import MPClient
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
adapter._mode = "mp"
|
||
|
|
adapter._mp_client = MPClient(AsyncMock(), {"app_id": "wx_test", "app_secret": "test"})
|
||
|
|
|
||
|
|
with patch(
|
||
|
|
"yuxi.channels.adapters.wechat.mp.send.send_mp_video",
|
||
|
|
new_callable=AsyncMock,
|
||
|
|
) as mock_send:
|
||
|
|
mock_send.return_value = DeliveryResult(success=True)
|
||
|
|
adapter._http_client = AsyncMock()
|
||
|
|
|
||
|
|
result = await adapter._send_mp_media("video", b"video_data", "user123")
|
||
|
|
assert result.success is True
|
||
|
|
mock_send.assert_called_once()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_send_mp_media_routes_image(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
from yuxi.channels.adapters.wechat.mp.client import MPClient
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
adapter._mode = "mp"
|
||
|
|
adapter._mp_client = MPClient(AsyncMock(), {"app_id": "wx_test", "app_secret": "test"})
|
||
|
|
|
||
|
|
with patch(
|
||
|
|
"yuxi.channels.adapters.wechat.mp.send.send_mp_image",
|
||
|
|
new_callable=AsyncMock,
|
||
|
|
) as mock_send:
|
||
|
|
mock_send.return_value = DeliveryResult(success=True)
|
||
|
|
adapter._http_client = AsyncMock()
|
||
|
|
|
||
|
|
result = await adapter._send_mp_media("image", b"image_data", "user123")
|
||
|
|
assert result.success is True
|
||
|
|
mock_send.assert_called_once()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_send_mp_media_rejects_unsupported_type(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
from yuxi.channels.adapters.wechat.mp.client import MPClient
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
adapter._mode = "mp"
|
||
|
|
adapter._mp_client = MPClient(AsyncMock(), {"app_id": "wx_test", "app_secret": "test"})
|
||
|
|
adapter._http_client = AsyncMock()
|
||
|
|
|
||
|
|
result = await adapter._send_mp_media("document", b"data", "user123")
|
||
|
|
assert result.success is False
|
||
|
|
assert "only supports image/voice/video" in result.error
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_send_mp_media_rejects_non_bytes(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
from yuxi.channels.adapters.wechat.mp.client import MPClient
|
||
|
|
|
||
|
|
adapter = WeChatAdapter({"bridge_url": "http://localhost:5555", "dm_policy": "open"})
|
||
|
|
adapter._mode = "mp"
|
||
|
|
adapter._mp_client = MPClient(AsyncMock(), {"app_id": "wx_test", "app_secret": "test"})
|
||
|
|
adapter._http_client = AsyncMock()
|
||
|
|
|
||
|
|
result = await adapter._send_mp_media("image", "not_bytes", "user123")
|
||
|
|
assert result.success is False
|
||
|
|
assert "raw bytes" in result.error
|
||
|
|
|
||
|
|
|
||
|
|
class TestAdapterWebhookPath:
|
||
|
|
def test_webhook_path_set(self):
|
||
|
|
from yuxi.channels.adapters.wechat.adapter import WeChatAdapter
|
||
|
|
|
||
|
|
assert WeChatAdapter.webhook_path == "wechat"
|