test_bitrix_call.py
Language Python3.8 KB103 lines
"""bitrix_call against an httpx.MockTransport: success, Bitrix's in-bodyerrors, rate-limit retries, and that the webhook URL never escapes."""import asyncioimport jsonimport httpximport pytestimport serverrun = asyncio.run@pytest.fixturedef transport(monkeypatch):sleeps: list[float] = []async def no_sleep(seconds):sleeps.append(seconds)monkeypatch.setattr(server.asyncio, "sleep", no_sleep)def install(handler):monkeypatch.setattr(server, "_HTTP", httpx.AsyncClient(transport=httpx.MockTransport(handler)))return sleepsreturn installdef test_posts_json_to_the_method_url(transport):seen = []def handler(request):seen.append((request.method, str(request.url), json.loads(request.content)))return httpx.Response(200, json={"result": {"item": {"id": 1}}, "total": 1})transport(handler)assert run(server.bitrix_call("crm.item.get", {"id": 1})) == {"item": {"id": 1}}assert run(server.bitrix_call("crm.item.get", {"id": 1}, envelope=True)) == {"result": {"item": {"id": 1}}, "total": 1}assert seen[0] == ("POST", f"{server.WEBHOOK_URL}/crm.item.get.json", {"id": 1})run(server.bitrix_call("user.current"))assert seen[-1][2] == {}def test_error_in_body_raises_with_url_redacted(transport):def handler(request):return httpx.Response(400, json={"error": "NOT_FOUND","error_description": f"see {server.WEBHOOK_URL}/x"})transport(handler)with pytest.raises(server.BitrixError) as exc:run(server.bitrix_call("crm.item.get", {"id": 1}))assert str(exc.value) == "NOT_FOUND: see [webhook]/x"@pytest.mark.parametrize("status, body, message", [(500, b"<html>oops</html>", "HTTP 500 from m (no parseable body)"),(200, b"not json", "Unparseable response from m"),(403, b'{"result": null}', "HTTP 403 from m"),])def test_unusable_responses(transport, status, body, message):transport(lambda request: httpx.Response(status, content=body))with pytest.raises(server.BitrixError) as exc:run(server.bitrix_call("m"))assert str(exc.value) == messagedef test_rate_limit_is_retried(transport):answers = [{"error": "QUERY_LIMIT_EXCEEDED"}, {"result": 7}]sleeps = transport(lambda request: httpx.Response(200, json=answers.pop(0)))assert run(server.bitrix_call("m")) == 7assert sleeps == [1]def test_rate_limit_gives_up_after_three_attempts(transport):sleeps = transport(lambda request: httpx.Response(200, json={"error": "QUERY_LIMIT_EXCEEDED"}))with pytest.raises(server.BitrixError) as exc:run(server.bitrix_call("m"))assert str(exc.value) == "QUERY_LIMIT_EXCEEDED after 3 retries"assert sleeps == [1, 2, 3]def test_transport_errors_never_leak_the_webhook(transport):def handler(request):raise httpx.ConnectError(f"cannot reach {request.url}", request=request)transport(handler)with pytest.raises(server.BitrixError) as exc:run(server.bitrix_call("m"))assert str(exc.value) == ("transport failure after 3 attempts calling m: ""ConnectError: cannot reach [webhook]/m.json")assert "example.invalid" not in str(exc.value)def test_batch_encodes_commands(fake_bitrix):bx = fake_bitrix({"batch": {"result": {"result": {"a": []}, "result_total": {"a": 3}}}})out = run(server.bitrix_batch({"a": ("crm.item.list", {"filter": {"stageId": "NEW"}})}))assert out == {"result": {"a": []}, "result_total": {"a": 3}}assert bx.calls == [("batch", {"halt": 0, "cmd": {"a": "crm.item.list?filter%5BstageId%5D=NEW"}})]assert run(server.bitrix_batch({})) == {}with pytest.raises(server.BitrixError):run(server.bitrix_batch({str(i): ("m", {}) for i in range(51)}))