mattrguerra/meridian-crm-mcpmainPrivate on GitHub, published here in full

test_bitrix_call.py

Language Python3.8 KB103 lines

tests/test_bitrix_call.py

  1. """bitrix_call against an httpx.MockTransport: success, Bitrix's in-body
  2. errors, rate-limit retries, and that the webhook URL never escapes."""
  3. import asyncio
  4. import json
  5. import httpx
  6. import pytest
  7. import server
  8. run = asyncio.run
  9. @pytest.fixture
  10. def transport(monkeypatch):
  11. sleeps: list[float] = []
  12. async def no_sleep(seconds):
  13. sleeps.append(seconds)
  14. monkeypatch.setattr(server.asyncio, "sleep", no_sleep)
  15. def install(handler):
  16. monkeypatch.setattr(server, "_HTTP", httpx.AsyncClient(transport=httpx.MockTransport(handler)))
  17. return sleeps
  18. return install
  19. def test_posts_json_to_the_method_url(transport):
  20. seen = []
  21. def handler(request):
  22. seen.append((request.method, str(request.url), json.loads(request.content)))
  23. return httpx.Response(200, json={"result": {"item": {"id": 1}}, "total": 1})
  24. transport(handler)
  25. assert run(server.bitrix_call("crm.item.get", {"id": 1})) == {"item": {"id": 1}}
  26. assert run(server.bitrix_call("crm.item.get", {"id": 1}, envelope=True)) == {
  27. "result": {"item": {"id": 1}}, "total": 1}
  28. assert seen[0] == ("POST", f"{server.WEBHOOK_URL}/crm.item.get.json", {"id": 1})
  29. run(server.bitrix_call("user.current"))
  30. assert seen[-1][2] == {}
  31. def test_error_in_body_raises_with_url_redacted(transport):
  32. def handler(request):
  33. return httpx.Response(400, json={"error": "NOT_FOUND",
  34. "error_description": f"see {server.WEBHOOK_URL}/x"})
  35. transport(handler)
  36. with pytest.raises(server.BitrixError) as exc:
  37. run(server.bitrix_call("crm.item.get", {"id": 1}))
  38. assert str(exc.value) == "NOT_FOUND: see [webhook]/x"
  39. @pytest.mark.parametrize("status, body, message", [
  40. (500, b"<html>oops</html>", "HTTP 500 from m (no parseable body)"),
  41. (200, b"not json", "Unparseable response from m"),
  42. (403, b'{"result": null}', "HTTP 403 from m"),
  43. ])
  44. def test_unusable_responses(transport, status, body, message):
  45. transport(lambda request: httpx.Response(status, content=body))
  46. with pytest.raises(server.BitrixError) as exc:
  47. run(server.bitrix_call("m"))
  48. assert str(exc.value) == message
  49. def test_rate_limit_is_retried(transport):
  50. answers = [{"error": "QUERY_LIMIT_EXCEEDED"}, {"result": 7}]
  51. sleeps = transport(lambda request: httpx.Response(200, json=answers.pop(0)))
  52. assert run(server.bitrix_call("m")) == 7
  53. assert sleeps == [1]
  54. def test_rate_limit_gives_up_after_three_attempts(transport):
  55. sleeps = transport(lambda request: httpx.Response(200, json={"error": "QUERY_LIMIT_EXCEEDED"}))
  56. with pytest.raises(server.BitrixError) as exc:
  57. run(server.bitrix_call("m"))
  58. assert str(exc.value) == "QUERY_LIMIT_EXCEEDED after 3 retries"
  59. assert sleeps == [1, 2, 3]
  60. def test_transport_errors_never_leak_the_webhook(transport):
  61. def handler(request):
  62. raise httpx.ConnectError(f"cannot reach {request.url}", request=request)
  63. transport(handler)
  64. with pytest.raises(server.BitrixError) as exc:
  65. run(server.bitrix_call("m"))
  66. assert str(exc.value) == ("transport failure after 3 attempts calling m: "
  67. "ConnectError: cannot reach [webhook]/m.json")
  68. assert "example.invalid" not in str(exc.value)
  69. def test_batch_encodes_commands(fake_bitrix):
  70. bx = fake_bitrix({"batch": {"result": {"result": {"a": []}, "result_total": {"a": 3}}}})
  71. out = run(server.bitrix_batch({"a": ("crm.item.list", {"filter": {"stageId": "NEW"}})}))
  72. assert out == {"result": {"a": []}, "result_total": {"a": 3}}
  73. assert bx.calls == [("batch", {"halt": 0, "cmd": {"a": "crm.item.list?filter%5BstageId%5D=NEW"}})]
  74. assert run(server.bitrix_batch({})) == {}
  75. with pytest.raises(server.BitrixError):
  76. run(server.bitrix_batch({str(i): ("m", {}) for i in range(51)}))