Skip to content

Commit 8f5d943

Browse files
committed
Add regtests for HTTP status codes.
This adds explicit tests for the returned HTTP status codes to interface_rpc.py (for error cases) and the HTTP JSON-RPC client in general for success. PR 15381 brought up discussion about the HTTP status codes in general, and the general opinion was that the current choice may not be ideal but should not be changed to preserve compatibility with existing JSON-RPC clients. Thus it makes sense to actually test the current status to ensure this desired compatibility is not broken accidentally.
1 parent a0d4e79 commit 8f5d943

File tree

2 files changed

+36
-9
lines changed

2 files changed

+36
-9
lines changed

test/functional/interface_rpc.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
#!/usr/bin/env python3
2-
# Copyright (c) 2018 The Bitcoin Core developers
2+
# Copyright (c) 2018-2019 The Bitcoin Core developers
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Tests some generic aspects of the RPC interface."""
66

7+
from test_framework.authproxy import JSONRPCException
78
from test_framework.test_framework import BitcoinTestFramework
89
from test_framework.util import assert_equal, assert_greater_than_or_equal
910

11+
def expect_http_status(expected_http_status, expected_rpc_code,
12+
fcn, *args):
13+
try:
14+
fcn(*args)
15+
raise AssertionError("Expected RPC error %d, got none" % expected_rpc_code)
16+
except JSONRPCException as exc:
17+
assert_equal(exc.error["code"], expected_rpc_code)
18+
assert_equal(exc.http_status, expected_http_status)
19+
1020
class RPCInterfaceTest(BitcoinTestFramework):
1121
def set_test_params(self):
1222
self.num_nodes = 1
@@ -48,9 +58,16 @@ def test_batch_request(self):
4858
assert_equal(result_by_id[3]['error'], None)
4959
assert result_by_id[3]['result'] is not None
5060

61+
def test_http_status_codes(self):
62+
self.log.info("Testing HTTP status codes for JSON-RPC requests...")
63+
64+
expect_http_status(404, -32601, self.nodes[0].invalidmethod)
65+
expect_http_status(500, -8, self.nodes[0].getblockhash, 42)
66+
5167
def run_test(self):
5268
self.test_getrpcinfo()
5369
self.test_batch_request()
70+
self.test_http_status_codes()
5471

5572

5673
if __name__ == '__main__':

test/functional/test_framework/authproxy.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import base64
3737
import decimal
38+
from http import HTTPStatus
3839
import http.client
3940
import json
4041
import logging
@@ -49,13 +50,14 @@
4950
log = logging.getLogger("BitcoinRPC")
5051

5152
class JSONRPCException(Exception):
52-
def __init__(self, rpc_error):
53+
def __init__(self, rpc_error, http_status=None):
5354
try:
5455
errmsg = '%(message)s (%(code)i)' % rpc_error
5556
except (KeyError, TypeError):
5657
errmsg = ''
5758
super().__init__(errmsg)
5859
self.error = rpc_error
60+
self.http_status = http_status
5961

6062

6163
def EncodeDecimal(o):
@@ -131,19 +133,26 @@ def get_request(self, *args, **argsn):
131133

132134
def __call__(self, *args, **argsn):
133135
postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
134-
response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
136+
response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
135137
if response['error'] is not None:
136-
raise JSONRPCException(response['error'])
138+
raise JSONRPCException(response['error'], status)
137139
elif 'result' not in response:
138140
raise JSONRPCException({
139-
'code': -343, 'message': 'missing JSON-RPC result'})
141+
'code': -343, 'message': 'missing JSON-RPC result'}, status)
142+
elif status != HTTPStatus.OK:
143+
raise JSONRPCException({
144+
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
140145
else:
141146
return response['result']
142147

143148
def batch(self, rpc_call_list):
144149
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
145150
log.debug("--> " + postdata)
146-
return self._request('POST', self.__url.path, postdata.encode('utf-8'))
151+
response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
152+
if status != HTTPStatus.OK:
153+
raise JSONRPCException({
154+
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
155+
return response
147156

148157
def _get_response(self):
149158
req_start_time = time.time()
@@ -162,8 +171,9 @@ def _get_response(self):
162171

163172
content_type = http_response.getheader('Content-Type')
164173
if content_type != 'application/json':
165-
raise JSONRPCException({
166-
'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
174+
raise JSONRPCException(
175+
{'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)},
176+
http_response.status)
167177

168178
responsedata = http_response.read().decode('utf8')
169179
response = json.loads(responsedata, parse_float=decimal.Decimal)
@@ -172,7 +182,7 @@ def _get_response(self):
172182
log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
173183
else:
174184
log.debug("<-- [%.6f] %s" % (elapsed, responsedata))
175-
return response
185+
return response, http_response.status
176186

177187
def __truediv__(self, relative_uri):
178188
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)

0 commit comments

Comments
 (0)