from __future__ import annotations from unittest.mock import AsyncMock, MagicMock, patch import httpx import pytest from yuxi.channels.adapters.wechat.mp.client import MPClient from yuxi.channels.adapters.wechat.wecom.client import WeComClient from yuxi.channels.exceptions import ChannelAuthenticationError class TestMPClient: def setup_method(self): self.http_client = AsyncMock(spec=httpx.AsyncClient) self.config = {"app_id": "wx_test_id", "app_secret": "wx_test_secret"} self.client = MPClient(self.http_client, self.config) def test_app_id_property(self): assert self.client.app_id == "wx_test_id" @pytest.mark.asyncio async def test_get_access_token_success(self): mock_resp = MagicMock() mock_resp.json.return_value = {"access_token": "test_token", "expires_in": 7200} self.http_client.get.return_value = mock_resp token = await self.client.get_access_token() assert token == "test_token" @pytest.mark.asyncio async def test_get_access_token_cached(self): self.client.set_token("cached_token", 9999999999.0) token = await self.client.get_access_token() assert token == "cached_token" self.http_client.get.assert_not_called() @pytest.mark.asyncio async def test_get_access_token_expired_refreshes(self): self.client.set_token("old_token", 1.0) mock_resp = MagicMock() mock_resp.json.return_value = {"access_token": "new_token", "expires_in": 7200} self.http_client.get.return_value = mock_resp token = await self.client.get_access_token() assert token == "new_token" @pytest.mark.asyncio async def test_get_access_token_authentication_error(self): mock_resp = MagicMock() mock_resp.json.return_value = {"errcode": 40001, "errmsg": "invalid secret"} self.http_client.get.return_value = mock_resp with pytest.raises(ChannelAuthenticationError): await self.client.get_access_token() @pytest.mark.asyncio async def test_get_access_token_retry_on_timeout(self): self.http_client.get.side_effect = [ httpx.TimeoutException("timeout"), MagicMock(json=MagicMock(return_value={"access_token": "retry_token", "expires_in": 7200})), ] token = await self.client.get_access_token() assert token == "retry_token" @pytest.mark.asyncio async def test_get_access_token_exhaust_retries(self): self.http_client.get.side_effect = httpx.TimeoutException("timeout") with pytest.raises(ChannelAuthenticationError): await self.client.get_access_token() def test_invalidate_token(self): self.client.set_token("token", 99999.0) self.client.invalidate_token() assert self.client._access_token is None assert self.client._token_expires_at == 0.0 def test_set_token(self): self.client.set_token("my_token", 12345.0) assert self.client._access_token == "my_token" assert self.client._token_expires_at == 12345.0 @pytest.mark.asyncio async def test_upload_temp_media_success(self): self.client.set_token("upload_token", 9999999999.0) mock_resp = MagicMock() mock_resp.json.return_value = {"media_id": "media_123"} self.http_client.post.return_value = mock_resp media_id = await self.client.upload_temp_media(b"fake_data", "test.png", "image") assert media_id == "media_123" @pytest.mark.asyncio async def test_upload_temp_media_failure(self): self.client.set_token("upload_token", 9999999999.0) mock_resp = MagicMock() mock_resp.json.return_value = {"errcode": 40001, "errmsg": "error"} self.http_client.post.return_value = mock_resp with pytest.raises(ChannelAuthenticationError): await self.client.upload_temp_media(b"bad_data", "test.png", "image") class TestWeComClient: def setup_method(self): self.http_client = AsyncMock(spec=httpx.AsyncClient) self.config = {"corp_id": "test_corp", "agent_id": "100001", "corp_secret": "test_secret"} self.client = WeComClient(self.http_client, self.config) def test_corp_id_property(self): assert self.client.corp_id == "test_corp" def test_agent_id_property(self): assert self.client.agent_id == "100001" @pytest.mark.asyncio async def test_get_access_token_success(self): mock_resp = MagicMock() mock_resp.json.return_value = {"access_token": "wecom_token", "expires_in": 7200} self.http_client.get.return_value = mock_resp token = await self.client.get_access_token() assert token == "wecom_token" @pytest.mark.asyncio async def test_get_access_token_cached(self): self.client.set_token("cached_wecom", 9999999999.0) token = await self.client.get_access_token() assert token == "cached_wecom" self.http_client.get.assert_not_called() @pytest.mark.asyncio async def test_get_access_token_auth_error(self): mock_resp = MagicMock() mock_resp.json.return_value = {"errcode": 40001, "errmsg": "invalid"} self.http_client.get.return_value = mock_resp with pytest.raises(ChannelAuthenticationError): await self.client.get_access_token() @pytest.mark.asyncio async def test_get_access_token_exhaust_retries(self): self.http_client.get.side_effect = httpx.TimeoutException("timeout") with pytest.raises(ChannelAuthenticationError): await self.client.get_access_token() def test_invalidate_token(self): self.client.set_token("token", 99999.0) self.client.invalidate_token() assert self.client._access_token is None @pytest.mark.asyncio async def test_upload_media_success(self): self.client.set_token("upload_token", 9999999999.0) mock_resp = MagicMock() mock_resp.json.return_value = {"media_id": "wc_media_456"} self.http_client.post.return_value = mock_resp media_id = await self.client.upload_media(b"data", "file.png", "image") assert media_id == "wc_media_456" @pytest.mark.asyncio async def test_upload_media_failure(self): self.client.set_token("upload_token", 9999999999.0) mock_resp = MagicMock() mock_resp.json.return_value = {"errcode": 40001} self.http_client.post.return_value = mock_resp with pytest.raises(ChannelAuthenticationError): await self.client.upload_media(b"data", "file.png", "image")