170 lines
6.5 KiB
Python
170 lines
6.5 KiB
Python
|
|
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")
|