blitz_api/tests/test_bitcoin_rpc_warmup.py
fusion44 3980ce0c6b
fix(bitcoind): handle JSON-RPC errors returned with HTTP 200
Bitcoin Core returns the -28 warmup error ('Loading block index',
'Verifying blocks', 'Starting network threads') as an HTTP 200 response
with a JSON-RPC error body. _process_response only classified errors on
the non-200 path and passed a 200 body through unchanged, so the result
had an 'error' key but no 'status' key. Callers doing
'raise HTTPException(result["status"], ...)' then crashed with
KeyError: 'status' instead of HTTPException(425), so the startup
warmup-retry loop in initialize_bitcoin_repo never engaged and the API
(and login) failed while bitcoind was still warming up.

Same root cause, different -28 warmup message.

Fixes #287
Fixes #285

Normalize any JSON-RPC error to an {error, status} dict regardless of the
HTTP status, mapping the warmup messages to 425 TOO_EARLY as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 13:30:04 +02:00

57 lines
1.5 KiB
Python

"""
Regression test for blitz_api#287.
Bitcoin Core returns the -28 "Loading block index" warmup error as an
HTTP 200 with a JSON-RPC error body. _process_response returned that body
unchanged, so it had an "error" key but no "status" key, and callers doing
`raise HTTPException(result["status"], ...)` crashed with KeyError: 'status'
instead of the HTTPException(425) the startup warmup loop expects.
"""
import pytest
from starlette import status
from app.bitcoind.utils import _process_response
class _FakeResp:
def __init__(self, http_status, body, reason="OK"):
self.status = http_status
self._body = body
self.reason = reason
async def json(self):
return self._body
@pytest.mark.parametrize(
"message",
[
"Loading block index…", # blitz_api#287
"Verifying blocks…", # blitz_api#285
"Starting network threads…",
],
)
async def test_200_warmup_error_is_normalized_to_too_early(message):
resp = _FakeResp(
status.HTTP_200_OK,
{
"jsonrpc": "2.0",
"error": {"code": -28, "message": message},
"id": 6,
},
)
out = await _process_response(resp)
assert out["status"] == status.HTTP_425_TOO_EARLY
assert out["error"] # a human-readable message, not the raw dict
async def test_200_success_body_is_passed_through():
body = {"jsonrpc": "2.0", "result": {"blocks": 42}, "error": None, "id": 1}
resp = _FakeResp(status.HTTP_200_OK, body)
out = await _process_response(resp)
assert out == body