test_account_fans_portrait_client.py 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. """M9E:抖音作者画像 client——请求体(account_id=sec_uid + need_*)、限流 bucket、错误上抛。"""
  2. import httpx
  3. import pytest
  4. from content_agent.errors import ContentAgentError
  5. from content_agent.integrations.douyin import (
  6. RAW_AUTHOR_ACCOUNT_KEY,
  7. CrawapiDouyinClient,
  8. RateLimiter,
  9. )
  10. class FakeHttpClient:
  11. def __init__(self, responses):
  12. self.responses = list(responses)
  13. self.requests = []
  14. def post(self, url, json, headers, timeout):
  15. self.requests.append({"url": url, "json": json})
  16. response = self.responses.pop(0)
  17. if isinstance(response, Exception):
  18. raise response
  19. return response
  20. def _response(status_code, data):
  21. return httpx.Response(
  22. status_code, json=data, request=httpx.Request("POST", "http://crawapi.test/p")
  23. )
  24. def _client(responses, rate_limiter=None):
  25. return CrawapiDouyinClient(
  26. base_url="http://crawapi.test",
  27. keyword_path="/crawler/dou_yin/keyword",
  28. portrait_path="/crawler/dou_yin/re_dian_bao/account_fans_portrait",
  29. http_client=FakeHttpClient(responses),
  30. rate_limiter=rate_limiter,
  31. )
  32. def test_portrait_request_body_and_inner_data_unwrap():
  33. fans = {"age": {"data": {"50-": {"percentage": "30%", "preference": "130"}}}}
  34. client = _client([_response(200, {"code": 0, "data": {"data": {"fans": fans}}})])
  35. result = client.fetch_account_fans_portrait("MS4wLjABAAAAsecuid")
  36. req = client.http_client.requests[0]
  37. assert req["url"].endswith("/crawler/dou_yin/re_dian_bao/account_fans_portrait")
  38. assert req["json"][RAW_AUTHOR_ACCOUNT_KEY] == "MS4wLjABAAAAsecuid"
  39. assert req["json"]["need_age"] is True
  40. assert result == {"fans": fans} # 双层 data.data 解包为内层
  41. def test_portrait_does_not_use_rate_limiter_bucket():
  42. limiter = RateLimiter(min_interval_seconds=0)
  43. client = _client([_response(200, {"code": 0, "data": {"data": {}}})], rate_limiter=limiter)
  44. client.fetch_account_fans_portrait("MS4wLjAsec")
  45. assert limiter._last_call_by_bucket == {}
  46. def test_portrait_business_error_raises():
  47. client = _client([_response(200, {"code": 100, "msg": "blocked"})])
  48. with pytest.raises((ContentAgentError, RuntimeError)):
  49. client.fetch_account_fans_portrait("MS4wLjAsec")