mopidy/tests/utils/jsonrpc_test.py
Stein Magnus Jodal b038c4c2db 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.
2012-11-21 23:51:05 +01:00

454 lines
15 KiB
Python

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)