Add generic JSON-RPC 2.0 object wrapper
This can wrap multiple objects, which can be both plain objects and Pykka actors. To my knowledge, everything in the spec is supported.
This commit is contained in:
parent
d153fa6b59
commit
b038c4c2db
236
mopidy/utils/jsonrpc.py
Normal file
236
mopidy/utils/jsonrpc.py
Normal file
@ -0,0 +1,236 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import traceback
|
||||
|
||||
|
||||
class JsonRpcWrapper(object):
|
||||
"""
|
||||
Wraps objects and make them accessible through JSON-RPC 2.0 messaging.
|
||||
|
||||
This class takes responsibility of communicating with the objects and
|
||||
processing of JSON-RPC 2.0 messages. The transport of the messages over
|
||||
HTTP, WebSocket, TCP, or whatever is of no concern to this class.
|
||||
|
||||
The objects can either be Pykka actors or plain objects. Only their public
|
||||
methods will be exposed, not any attributes.
|
||||
|
||||
If a method returns an object with a ``get()`` method, it is assumed to be
|
||||
a future object. Any futures is completed and their value unwrapped before
|
||||
the JSON RPC wrapper returns the response.
|
||||
|
||||
For further details on the JSON-RPC 2.0 spec, see
|
||||
http://www.jsonrpc.org/specification
|
||||
|
||||
:param objects: dict of names mapped to objects to be exposed
|
||||
"""
|
||||
|
||||
def __init__(self, objects, decoders=None, encoders=None):
|
||||
self.objects = objects
|
||||
self.decoder = get_combined_json_decoder(decoders or [])
|
||||
self.encoder = get_combined_json_encoder(encoders or [])
|
||||
|
||||
def handle_json(self, request):
|
||||
"""
|
||||
Handles an incoming request encoded as a JSON string.
|
||||
|
||||
Returns a response as a JSON string for commands, and :class:`None` for
|
||||
notifications.
|
||||
|
||||
:param request: the serialized JSON-RPC request
|
||||
:type request: string
|
||||
:rtype: string or :class:`None`
|
||||
"""
|
||||
try:
|
||||
request = json.loads(request, object_hook=self.decoder)
|
||||
except ValueError:
|
||||
response = JsonRpcParseError().get_response()
|
||||
else:
|
||||
response = self.handle_data(request)
|
||||
if response is None:
|
||||
return None
|
||||
return json.dumps(response, cls=self.encoder)
|
||||
|
||||
def handle_data(self, request):
|
||||
"""
|
||||
Handles an incoming request in the form of a Python data structure.
|
||||
|
||||
Returns a Python data structure for commands, or a :class:`None` for
|
||||
notifications.
|
||||
|
||||
:param request: the unserialized JSON-RPC request
|
||||
:type request: dict
|
||||
:rtype: dict, list, or :class:`None`
|
||||
"""
|
||||
if isinstance(request, list):
|
||||
return self._handle_batch(request)
|
||||
else:
|
||||
return self._handle_single_request(request)
|
||||
|
||||
def _handle_batch(self, requests):
|
||||
if not requests:
|
||||
return JsonRpcInvalidRequestError(
|
||||
data='Batch list cannot be empty').get_response()
|
||||
|
||||
responses = []
|
||||
for request in requests:
|
||||
response = self._handle_single_request(request)
|
||||
if response:
|
||||
responses.append(response)
|
||||
|
||||
return responses or None
|
||||
|
||||
def _handle_single_request(self, request):
|
||||
try:
|
||||
self._validate_request(request)
|
||||
args, kwargs = self._get_params(request)
|
||||
except JsonRpcInvalidRequestError as error:
|
||||
return error.get_response()
|
||||
|
||||
try:
|
||||
method = self._get_method(request['method'])
|
||||
|
||||
try:
|
||||
result = method(*args, **kwargs)
|
||||
|
||||
if self._is_notification(request):
|
||||
return None
|
||||
|
||||
if self._is_future(result):
|
||||
result = result.get()
|
||||
|
||||
return {
|
||||
'jsonrpc': '2.0',
|
||||
'id': request['id'],
|
||||
'result': result,
|
||||
}
|
||||
except TypeError as error:
|
||||
raise JsonRpcInvalidParamsError(data={
|
||||
'type': error.__class__.__name__,
|
||||
'message': unicode(error),
|
||||
'traceback': traceback.format_exc(),
|
||||
})
|
||||
except Exception as error:
|
||||
raise JsonRpcApplicationError(data={
|
||||
'type': error.__class__.__name__,
|
||||
'message': unicode(error),
|
||||
'traceback': traceback.format_exc(),
|
||||
})
|
||||
except JsonRpcError as error:
|
||||
if self._is_notification(request):
|
||||
return None
|
||||
return error.get_response(request['id'])
|
||||
|
||||
def _validate_request(self, request):
|
||||
if not isinstance(request, dict):
|
||||
raise JsonRpcInvalidRequestError(
|
||||
data='Request must be an object')
|
||||
if not 'jsonrpc' in request:
|
||||
raise JsonRpcInvalidRequestError(
|
||||
data='"jsonrpc" member must be included')
|
||||
if request['jsonrpc'] != '2.0':
|
||||
raise JsonRpcInvalidRequestError(
|
||||
data='"jsonrpc" value must be "2.0"')
|
||||
if not 'method' in request:
|
||||
raise JsonRpcInvalidRequestError(
|
||||
data='"method" member must be included')
|
||||
if not isinstance(request['method'], unicode):
|
||||
raise JsonRpcInvalidRequestError(
|
||||
data='"method" must be a string')
|
||||
|
||||
def _get_params(self, request):
|
||||
if not 'params' in request:
|
||||
return [], {}
|
||||
params = request['params']
|
||||
if isinstance(params, list):
|
||||
return params, {}
|
||||
elif isinstance(params, dict):
|
||||
return [], params
|
||||
else:
|
||||
raise JsonRpcInvalidRequestError(
|
||||
data='"params", if given, must be an array or an object')
|
||||
|
||||
def _get_method(self, name):
|
||||
try:
|
||||
path = name.split('.')
|
||||
root = path.pop(0)
|
||||
this = self.objects[root]
|
||||
for part in path:
|
||||
if part.startswith('_'):
|
||||
raise AttributeError
|
||||
this = getattr(this, part)
|
||||
return this
|
||||
except (AttributeError, KeyError):
|
||||
raise JsonRpcMethodNotFoundError()
|
||||
|
||||
def _is_notification(self, request):
|
||||
return 'id' not in request
|
||||
|
||||
def _is_future(self, result):
|
||||
return callable(getattr(result, 'get', None))
|
||||
|
||||
|
||||
class JsonRpcError(Exception):
|
||||
code = -32000
|
||||
message = 'Unspecified server error'
|
||||
|
||||
def __init__(self, data=None):
|
||||
self.data = data
|
||||
|
||||
def get_response(self, request_id=None):
|
||||
response = {
|
||||
'jsonrpc': '2.0',
|
||||
'id': request_id,
|
||||
'error': {
|
||||
'code': self.code,
|
||||
'message': self.message,
|
||||
},
|
||||
}
|
||||
if self.data:
|
||||
response['error']['data'] = self.data
|
||||
return response
|
||||
|
||||
|
||||
class JsonRpcParseError(JsonRpcError):
|
||||
code = -32700
|
||||
message = 'Parse error'
|
||||
|
||||
|
||||
class JsonRpcInvalidRequestError(JsonRpcError):
|
||||
code = -32600
|
||||
message = 'Invalid Request'
|
||||
|
||||
|
||||
class JsonRpcMethodNotFoundError(JsonRpcError):
|
||||
code = -32601
|
||||
message = 'Method not found'
|
||||
|
||||
|
||||
class JsonRpcInvalidParamsError(JsonRpcError):
|
||||
code = -32602
|
||||
message = 'Invalid params'
|
||||
|
||||
|
||||
class JsonRpcApplicationError(JsonRpcError):
|
||||
code = 0
|
||||
message = 'Application error'
|
||||
|
||||
|
||||
def get_combined_json_decoder(decoders):
|
||||
def decode(dct):
|
||||
for decoder in decoders:
|
||||
dct = decoder(dct)
|
||||
return dct
|
||||
return decode
|
||||
|
||||
|
||||
def get_combined_json_encoder(encoders):
|
||||
class JsonRpcEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
for encoder in encoders:
|
||||
try:
|
||||
return encoder().default(obj)
|
||||
except TypeError:
|
||||
pass # Try next encoder
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
return JsonRpcEncoder
|
||||
453
tests/utils/jsonrpc_test.py
Normal file
453
tests/utils/jsonrpc_test.py
Normal file
@ -0,0 +1,453 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
import pykka
|
||||
import mock
|
||||
|
||||
from mopidy import core, models
|
||||
from mopidy.backends import dummy
|
||||
from mopidy.utils import jsonrpc
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class Calculator(object):
|
||||
def model(self):
|
||||
return 'TI83'
|
||||
|
||||
def add(self, a, b):
|
||||
return a + b
|
||||
|
||||
def sub(self, a, b):
|
||||
return a - b
|
||||
|
||||
def _secret(self):
|
||||
return 'Grand Unified Theory'
|
||||
|
||||
|
||||
class JsonRpcTestBase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.backend = dummy.DummyBackend.start(audio=None).proxy()
|
||||
self.core = core.Core.start(backends=[self.backend]).proxy()
|
||||
self.jrw = jsonrpc.JsonRpcWrapper(
|
||||
objects={
|
||||
'core': self.core,
|
||||
'calculator': Calculator(),
|
||||
},
|
||||
encoders=[models.ModelJSONEncoder],
|
||||
decoders=[models.model_json_decoder])
|
||||
|
||||
def tearDown(self):
|
||||
pykka.ActorRegistry.stop_all()
|
||||
|
||||
|
||||
class JsonRpcSerializationTest(JsonRpcTestBase):
|
||||
def test_handle_json_converts_from_and_to_json(self):
|
||||
self.jrw.handle_data = mock.Mock()
|
||||
self.jrw.handle_data.return_value = {'foo': 'response'}
|
||||
|
||||
request = '{"foo": "request"}'
|
||||
response = self.jrw.handle_json(request)
|
||||
|
||||
self.jrw.handle_data.assert_called_once_with({'foo': 'request'})
|
||||
self.assertEqual(response, '{"foo": "response"}')
|
||||
|
||||
def test_handle_json_decodes_mopidy_models(self):
|
||||
self.jrw.handle_data = mock.Mock()
|
||||
self.jrw.handle_data.return_value = []
|
||||
|
||||
request = '{"foo": {"__model__": "Artist", "name": "bar"}}'
|
||||
self.jrw.handle_json(request)
|
||||
|
||||
self.jrw.handle_data.assert_called_once_with(
|
||||
{'foo': models.Artist(name='bar')})
|
||||
|
||||
def test_handle_json_encodes_mopidy_models(self):
|
||||
self.jrw.handle_data = mock.Mock()
|
||||
self.jrw.handle_data.return_value = {'foo': models.Artist(name='bar')}
|
||||
|
||||
request = '[]'
|
||||
response = self.jrw.handle_json(request)
|
||||
|
||||
self.assertEqual(
|
||||
response, '{"foo": {"__model__": "Artist", "name": "bar"}}')
|
||||
|
||||
def test_handle_json_returns_nothing_for_notices(self):
|
||||
request = '{"jsonrpc": "2.0", "method": "core.get_uri_schemes"}'
|
||||
response = self.jrw.handle_json(request)
|
||||
|
||||
self.assertEqual(response, None)
|
||||
|
||||
def test_invalid_json_command_causes_parse_error(self):
|
||||
request = (
|
||||
'{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]')
|
||||
response = self.jrw.handle_json(request)
|
||||
response = json.loads(response)
|
||||
|
||||
self.assertEqual(response['jsonrpc'], '2.0')
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32700)
|
||||
self.assertEqual(error['message'], 'Parse error')
|
||||
|
||||
def test_invalid_json_batch_causes_parse_error(self):
|
||||
request = """[
|
||||
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
|
||||
{"jsonrpc": "2.0", "method"
|
||||
]"""
|
||||
response = self.jrw.handle_json(request)
|
||||
response = json.loads(response)
|
||||
|
||||
self.assertEqual(response['jsonrpc'], '2.0')
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32700)
|
||||
self.assertEqual(error['message'], 'Parse error')
|
||||
|
||||
|
||||
class JsonRpcSingleCommandTest(JsonRpcTestBase):
|
||||
def test_call_method_on_plain_object(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'calculator.model',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(response['jsonrpc'], '2.0')
|
||||
self.assertEqual(response['id'], 1)
|
||||
self.assertNotIn('error', response)
|
||||
self.assertEqual(response['result'], 'TI83')
|
||||
|
||||
def test_call_method_on_actor_root(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.get_uri_schemes',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(response['jsonrpc'], '2.0')
|
||||
self.assertEqual(response['id'], 1)
|
||||
self.assertNotIn('error', response)
|
||||
self.assertEqual(response['result'], ['dummy'])
|
||||
|
||||
def test_call_method_on_actor_member(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.playback.get_volume',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(response['result'], None)
|
||||
|
||||
def test_call_method_with_positional_params(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.playback.set_volume',
|
||||
'params': [37],
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(response['result'], None)
|
||||
self.assertEqual(self.core.playback.get_volume().get(), 37)
|
||||
|
||||
def test_call_methods_with_named_params(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.playback.set_volume',
|
||||
'params': {'volume': 37},
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(response['result'], None)
|
||||
self.assertEqual(self.core.playback.get_volume().get(), 37)
|
||||
|
||||
|
||||
class JsonRpcSingleNotificationTest(JsonRpcTestBase):
|
||||
def test_notification_does_not_return_a_result(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.get_uri_schemes',
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response)
|
||||
|
||||
def test_notification_makes_an_observable_change(self):
|
||||
self.assertEqual(self.core.playback.get_volume().get(), None)
|
||||
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.playback.set_volume',
|
||||
'params': [37],
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response)
|
||||
self.assertEqual(self.core.playback.get_volume().get(), 37)
|
||||
|
||||
def test_notification_unknown_method_returns_nothing(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'bogus',
|
||||
'params': ['bogus'],
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response)
|
||||
|
||||
|
||||
class JsonRpcBatchTest(JsonRpcTestBase):
|
||||
def test_batch_of_only_commands_returns_all(self):
|
||||
self.core.playback.set_random(True).get()
|
||||
|
||||
request = [
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_repeat', 'id': 1},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_random', 'id': 2},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_single', 'id': 3},
|
||||
]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(len(response), 3)
|
||||
|
||||
response = {row['id']: row for row in response}
|
||||
self.assertEqual(response[1]['result'], False)
|
||||
self.assertEqual(response[2]['result'], True)
|
||||
self.assertEqual(response[3]['result'], False)
|
||||
|
||||
def test_batch_of_commands_and_notifications_returns_some(self):
|
||||
self.core.playback.set_random(True).get()
|
||||
|
||||
request = [
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_repeat'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_random', 'id': 2},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_single', 'id': 3},
|
||||
]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(len(response), 2)
|
||||
|
||||
response = {row['id']: row for row in response}
|
||||
self.assertNotIn(1, response)
|
||||
self.assertEqual(response[2]['result'], True)
|
||||
self.assertEqual(response[3]['result'], False)
|
||||
|
||||
def test_batch_of_only_notifications_returns_nothing(self):
|
||||
self.core.playback.set_random(True).get()
|
||||
|
||||
request = [
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_repeat'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_random'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_single'},
|
||||
]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response)
|
||||
|
||||
|
||||
class JsonRpcSingleCommandErrorTest(JsonRpcTestBase):
|
||||
def test_application_error_response(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.tracklist.index',
|
||||
'params': ['bogus'],
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertNotIn('result', response)
|
||||
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], 0)
|
||||
self.assertEqual(error['message'], 'Application error')
|
||||
|
||||
data = error['data']
|
||||
self.assertEqual(data['type'], 'ValueError')
|
||||
self.assertEqual(data['message'], "u'bogus' is not in list")
|
||||
self.assertIn('traceback', data)
|
||||
self.assertIn('Traceback (most recent call last):', data['traceback'])
|
||||
|
||||
def test_missing_jsonrpc_member_causes_invalid_request_error(self):
|
||||
request = {
|
||||
'method': 'core.get_uri_schemes',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response['id'])
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32600)
|
||||
self.assertEqual(error['message'], 'Invalid Request')
|
||||
self.assertEqual(error['data'], '"jsonrpc" member must be included')
|
||||
|
||||
def test_wrong_jsonrpc_version_causes_invalid_request_error(self):
|
||||
request = {
|
||||
'jsonrpc': '3.0',
|
||||
'method': 'core.get_uri_schemes',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response['id'])
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32600)
|
||||
self.assertEqual(error['message'], 'Invalid Request')
|
||||
self.assertEqual(error['data'], '"jsonrpc" value must be "2.0"')
|
||||
|
||||
def test_missing_method_member_causes_invalid_request_error(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response['id'])
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32600)
|
||||
self.assertEqual(error['message'], 'Invalid Request')
|
||||
self.assertEqual(error['data'], '"method" member must be included')
|
||||
|
||||
def test_invalid_method_value_causes_invalid_request_error(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 1,
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response['id'])
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32600)
|
||||
self.assertEqual(error['message'], 'Invalid Request')
|
||||
self.assertEqual(error['data'], '"method" must be a string')
|
||||
|
||||
def test_invalid_params_value_causes_invalid_request_error(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.get_uri_schemes',
|
||||
'params': 'foobar',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response['id'])
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32600)
|
||||
self.assertEqual(error['message'], 'Invalid Request')
|
||||
self.assertEqual(
|
||||
error['data'], '"params", if given, must be an array or an object')
|
||||
|
||||
def test_unknown_method_causes_unknown_method_error(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'bogus',
|
||||
'params': ['bogus'],
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32601)
|
||||
self.assertEqual(error['message'], 'Method not found')
|
||||
|
||||
def test_private_method_causes_unknown_method_error(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'calculator._secret',
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32601)
|
||||
self.assertEqual(error['message'], 'Method not found')
|
||||
|
||||
def test_invalid_params_causes_invalid_params_error(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.get_uri_schemes',
|
||||
'params': ['bogus'],
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32602)
|
||||
self.assertEqual(error['message'], 'Invalid params')
|
||||
|
||||
data = error['data']
|
||||
self.assertEqual(data['type'], 'TypeError')
|
||||
self.assertEqual(
|
||||
data['message'],
|
||||
'get_uri_schemes() takes exactly 1 argument (2 given)')
|
||||
self.assertIn('traceback', data)
|
||||
self.assertIn('Traceback (most recent call last):', data['traceback'])
|
||||
|
||||
|
||||
class JsonRpcBatchErrorTest(JsonRpcTestBase):
|
||||
def test_empty_batch_list_causes_invalid_request_error(self):
|
||||
request = []
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertIsNone(response['id'])
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32600)
|
||||
self.assertEqual(error['message'], 'Invalid Request')
|
||||
self.assertEqual(error['data'], 'Batch list cannot be empty')
|
||||
|
||||
def test_batch_with_invalid_command_causes_invalid_request_error(self):
|
||||
request = [1]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(len(response), 1)
|
||||
response = response[0]
|
||||
self.assertIsNone(response['id'])
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32600)
|
||||
self.assertEqual(error['message'], 'Invalid Request')
|
||||
self.assertEqual(error['data'], 'Request must be an object')
|
||||
|
||||
def test_batch_with_invalid_commands_causes_invalid_request_error(self):
|
||||
request = [1, 2, 3]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(len(response), 3)
|
||||
response = response[2]
|
||||
self.assertIsNone(response['id'])
|
||||
error = response['error']
|
||||
self.assertEqual(error['code'], -32600)
|
||||
self.assertEqual(error['message'], 'Invalid Request')
|
||||
self.assertEqual(error['data'], 'Request must be an object')
|
||||
|
||||
def test_batch_of_both_successfull_and_failing_requests(self):
|
||||
request = [
|
||||
# Call with positional params
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.set_volume',
|
||||
'params': [47], 'id': '1'},
|
||||
# Notification
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.set_consume',
|
||||
'params': [True]},
|
||||
# Call with positional params
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.set_repeat',
|
||||
'params': [False], 'id': '2'},
|
||||
# Invalid request
|
||||
{'foo': 'boo'},
|
||||
# Unknown method
|
||||
{'jsonrpc': '2.0', 'method': 'foo.get',
|
||||
'params': {'name': 'myself'}, 'id': '5'},
|
||||
# Call without params
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_random',
|
||||
'id': '9'},
|
||||
]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(len(response), 5)
|
||||
response = {row['id']: row for row in response}
|
||||
self.assertEqual(response['1']['result'], None)
|
||||
self.assertEqual(response['2']['result'], None)
|
||||
self.assertEqual(response[None]['error']['code'], -32600)
|
||||
self.assertEqual(response['5']['error']['code'], -32601)
|
||||
self.assertEqual(response['9']['result'], False)
|
||||
Loading…
Reference in New Issue
Block a user