1
0
Fork 0
bthlabs-jsonrpc/packages/bthlabs-jsonrpc-core/tests/executor/test_Executor.py

777 lines
20 KiB
Python

# -*- coding: utf-8 -*-
import json
from unittest import mock
import pytest
from bthlabs_jsonrpc_core import exceptions, executor
from bthlabs_jsonrpc_core.registry import MethodRegistry
from bthlabs_jsonrpc_core.serializer import JSONRPCSerializer
@pytest.fixture
def single_call():
return {
'jsonrpc': '2.0',
'id': 'test',
'method': 'test',
}
@pytest.fixture
def batch_calls():
return [
{
'jsonrpc': '2.0',
'id': 'test',
'method': 'test',
},
{
'jsonrpc': '2.0',
'id': 'test2',
'method': 'test',
},
]
@pytest.fixture
def jsonrpc_error():
return exceptions.BaseJSONRPCError('I HAZ FIAL')
@pytest.fixture
def execute_context():
return executor.Executor.ExecuteContext([])
def test_CallContext_invalid_context():
# When
result = executor.Executor.CallContext.invalid_context()
# Then
assert result.method is None
assert result.handler is None
assert result.args is None
assert result.kwargs is None
def test_CallContext_is_valid_method_none(fake_handler):
# When
call_context = executor.Executor.CallContext(None, fake_handler, [], {})
# Then
assert call_context.is_valid is False
def test_CallContext_is_valid_handler_none():
# When
call_context = executor.Executor.CallContext('test', None, [], {})
# Then
assert call_context.is_valid is False
def test_CallContext_is_valid_args_none(fake_handler):
# When
call_context = executor.Executor.CallContext(
'test', fake_handler, None, {},
)
# Then
assert call_context.is_valid is False
def test_CallContext_is_valid_kwargs_none(fake_handler):
# When
call_context = executor.Executor.CallContext(
'test', fake_handler, [], None,
)
# Then
assert call_context.is_valid is False
def test_CallContext_is_valid(fake_handler):
# When
call_context = executor.Executor.CallContext('test', fake_handler, [], {})
# Then
assert call_context.is_valid is True
def test_init_default_namespace():
# When
result = executor.Executor()
# Then
assert result.namespace == MethodRegistry.DEFAULT_NAMESPACE
def test_init_custom_namespace():
# When
result = executor.Executor(namespace='testing')
# Then
assert result.namespace == 'testing'
@mock.patch.object(executor.MethodRegistry, 'shared_registry')
def test_list_methods(mock_shared_registry, fake_method_registry):
# Given
fake_method_registry.get_methods.return_value = ['test']
mock_shared_registry.return_value = fake_method_registry
the_executor = executor.Executor()
# When
result = the_executor.list_methods()
# Then
assert result == ['system.list_methods', 'test']
assert mock_shared_registry.called is True
fake_method_registry.get_methods.assert_called_with(the_executor.namespace)
def test_get_internal_handler_list_methods():
# Given
the_executor = executor.Executor()
# When
result = the_executor.get_internal_handler('system.list_methods')
# Then
assert result == the_executor.list_methods
def test_get_internal_handler_method_not_found():
# Given
the_executor = executor.Executor()
# When
try:
_ = the_executor.get_internal_handler('test')
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCMethodNotFoundError)
else:
assert False, 'No exception raised?'
def test_deserialize_data():
# Given
the_executor = executor.Executor()
# When
result = the_executor.deserialize_data('"spam"')
# Then
assert result == 'spam'
def test_deserialize_data_error():
# Given
the_executor = executor.Executor()
# When
try:
_ = the_executor.deserialize_data(None)
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCParseError)
else:
assert False, 'No exception raised?'
def test_get_calls_batch(batch_calls):
# Given
the_executor = executor.Executor()
# When
result = the_executor.get_calls(batch_calls)
# Then
assert result == batch_calls
def test_get_calls_single(single_call):
# Given
the_executor = executor.Executor()
# When
result = the_executor.get_calls(single_call)
# Then
assert result == [single_call]
def test_get_calls_empty():
# Given
the_executor = executor.Executor()
# When
try:
_ = the_executor.get_calls([])
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCInvalidRequestError)
else:
assert False, 'No exception raised?'
def test_get_call_spec_not_dict():
# Given
the_executor = executor.Executor()
# When
try:
_ = the_executor.get_call_spec(None)
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCInvalidRequestError)
else:
assert False, 'No exception raised?'
def test_get_call_spec_wihtout_jsonrpc(single_call):
# Given
single_call.pop('jsonrpc')
the_executor = executor.Executor()
# When
try:
_ = the_executor.get_call_spec(single_call)
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCInvalidRequestError)
else:
assert False, 'No exception raised?'
def test_get_call_spec_invalid_jsonrpc(single_call):
# Given
single_call['jsonrpc'] = 'test'
the_executor = executor.Executor()
# When
try:
_ = the_executor.get_call_spec(single_call)
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCInvalidRequestError)
else:
assert False, 'No exception raised?'
def test_get_call_spec_wihtout_method(single_call):
# Given
single_call.pop('method')
the_executor = executor.Executor()
# When
try:
_ = the_executor.get_call_spec(single_call)
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCInvalidRequestError)
else:
assert False, 'No exception raised?'
@mock.patch.object(executor.MethodRegistry, 'shared_registry')
def test_get_call_spec_internal_method(mock_shared_registry,
single_call,
fake_handler):
# Given
single_call['method'] = 'system.list_methods'
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'get_internal_handler') as mock_get_internal_handler:
mock_get_internal_handler.return_value = fake_handler
# When
result = the_executor.get_call_spec(single_call)
# Then
assert result[0] == 'system.list_methods'
assert result[1] is fake_handler
mock_get_internal_handler.assert_called_with('system.list_methods')
assert mock_shared_registry.called is False
@mock.patch.object(executor.MethodRegistry, 'shared_registry')
def test_get_call_spec_registry_method(mock_shared_registry,
single_call,
fake_method_registry,
fake_handler):
# Given
fake_method_registry.get_handler.return_value = fake_handler
mock_shared_registry.return_value = fake_method_registry
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'get_internal_handler') as mock_get_internal_handler:
# When
result = the_executor.get_call_spec(single_call)
# Then
assert result[0] == 'test'
assert result[1] is fake_handler
assert mock_get_internal_handler.called is False
fake_method_registry.get_handler.assert_called_with(
the_executor.namespace, 'test',
)
@mock.patch.object(executor.MethodRegistry, 'shared_registry')
def test_get_call_spec_method_not_found(mock_shared_registry,
single_call,
fake_method_registry):
# Given
fake_method_registry.get_handler.return_value = None
mock_shared_registry.return_value = fake_method_registry
the_executor = executor.Executor()
# When
try:
_ = the_executor.get_call_spec(single_call)
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCMethodNotFoundError)
else:
assert False, 'No exception raised?'
@mock.patch.object(executor.MethodRegistry, 'shared_registry')
def test_get_call_spec_invalid_params(mock_shared_registry,
single_call,
fake_method_registry,
fake_handler):
# Given
single_call['params'] = 'spam'
fake_method_registry.get_handler.return_value = fake_handler
mock_shared_registry.return_value = fake_method_registry
the_executor = executor.Executor()
# When
try:
_ = the_executor.get_call_spec(single_call)
except Exception as exception:
# Then
assert isinstance(exception, exceptions.JSONRPCInvalidParamsError)
else:
assert False, 'No exception raised?'
@mock.patch.object(executor.MethodRegistry, 'shared_registry')
def test_get_call_spec_with_args(mock_shared_registry,
single_call,
fake_method_registry,
fake_handler):
# Given
single_call['params'] = ['spam']
fake_method_registry.get_handler.return_value = fake_handler
mock_shared_registry.return_value = fake_method_registry
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'enrich_args') as mock_enrich_args:
mock_enrich_args.return_value = ['spam', 'eggs']
# When
result = the_executor.get_call_spec(single_call)
# Then
assert result[2] == ['spam', 'eggs']
assert result[3] == {}
mock_enrich_args.assert_called_with(['spam'])
@mock.patch.object(executor.MethodRegistry, 'shared_registry')
def test_get_call_spec_with_kwargs(mock_shared_registry,
single_call,
fake_method_registry,
fake_handler):
# Given
single_call['params'] = {'spam': True}
fake_method_registry.get_handler.return_value = fake_handler
mock_shared_registry.return_value = fake_method_registry
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'enrich_kwargs') as mock_enrich_kwargs:
mock_enrich_kwargs.return_value = {'spam': True, 'eggs': False}
# When
result = the_executor.get_call_spec(single_call)
# Then
assert result[2] == []
assert result[3] == {'spam': True, 'eggs': False}
mock_enrich_kwargs.assert_called_with({'spam': True})
def test_process_results(batch_calls, single_call, jsonrpc_error):
# Given
call_without_id = {**single_call}
call_without_id.pop('id')
call_results = [
(batch_calls[0], 'OK'),
(batch_calls[1], jsonrpc_error),
(call_without_id, '???'),
(call_without_id, jsonrpc_error),
]
the_executor = executor.Executor()
# When
result = the_executor.process_results(call_results)
# Then
assert isinstance(result, list)
assert len(result) == 2
first_response, second_response = result
expected_first_response = {
'jsonrpc': '2.0',
'id': batch_calls[0]['id'],
'result': 'OK',
}
assert first_response == expected_first_response
expected_second_response = {
'jsonrpc': '2.0',
'id': batch_calls[1]['id'],
'error': jsonrpc_error,
}
assert second_response == expected_second_response
def test_process_results_single_call(single_call):
# Given
call_results = [
(single_call, 'OK'),
]
the_executor = executor.Executor()
# When
result = the_executor.process_results(call_results)
# Then
expected_result = {
'jsonrpc': '2.0',
'id': single_call['id'],
'result': 'OK',
}
assert result == expected_result
def test_process_results_top_level_error(jsonrpc_error):
# Given
call_results = [
(None, jsonrpc_error),
]
the_executor = executor.Executor()
# When
result = the_executor.process_results(call_results)
# Then
expected_result = {
'jsonrpc': '2.0',
'id': None,
'error': jsonrpc_error,
}
assert result == expected_result
def test_process_results_empty(single_call):
# Given
single_call.pop('id')
call_results = [
(single_call, 'OK'),
]
the_executor = executor.Executor()
# When
result = the_executor.process_results(call_results)
# Then
assert result is None
def test_call_context_invalid_context(jsonrpc_error,
execute_context,
single_call):
# Given
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'get_call_spec') as mock_get_call_spec:
mock_get_call_spec.side_effect = jsonrpc_error
# When
with the_executor.call_context(execute_context, single_call) as result:
pass
# Then
assert result.is_valid is False
def test_call_context_handle_jsonrpc_error(fake_handler,
jsonrpc_error,
execute_context,
single_call):
# Given
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'get_call_spec') as mock_get_call_spec:
mock_get_call_spec.return_value = (
'test', fake_handler, [], {},
)
# When
with the_executor.call_context(execute_context, single_call) as _:
raise jsonrpc_error
# Then
assert len(execute_context.results) == 1
call_result = execute_context.results[0]
assert call_result[1] == jsonrpc_error
def test_call_context_handle_exception(fake_handler,
execute_context,
single_call):
# Given
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'get_call_spec') as mock_get_call_spec:
mock_get_call_spec.return_value = (
'test', fake_handler, [], {},
)
# When
with the_executor.call_context(execute_context, single_call) as _:
raise RuntimeError('I HAZ FIAL')
# Then
assert len(execute_context.results) == 1
call_result = execute_context.results[0]
assert isinstance(call_result[1], exceptions.JSONRPCInternalError)
assert call_result[1].data == 'I HAZ FIAL'
def test_call_context(fake_handler, execute_context, single_call):
# Given
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'get_call_spec') as mock_get_call_spec:
mock_get_call_spec.return_value = (
'test', fake_handler, [], {},
)
# When
with the_executor.call_context(execute_context, single_call) as result:
result.result = 'OK'
# Then
assert result.method == 'test'
assert result.handler is fake_handler
assert result.args == []
assert result.kwargs == {}
assert len(execute_context.results) == 1
expected_call_result = (single_call, 'OK')
assert execute_context.results[0] == expected_call_result
def test_execute_context_handle_jsonrpc_error(jsonrpc_error):
# Given
the_executor = executor.Executor()
# When
with the_executor.execute_context() as result:
raise jsonrpc_error
# Then
assert result.results == [(None, jsonrpc_error)]
def test_execute_context_handle_exception():
# Given
error = RuntimeError('I HAZ FIAL')
the_executor = executor.Executor()
# When
try:
with the_executor.execute_context() as result:
raise error
except Exception as exception:
assert exception is error
assert result.serializer is None
def test_execute_context_handle_empty_results(single_call):
# Given
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'process_results') as mock_process_results:
with mock.patch.object(the_executor, 'serializer') as mock_serializer:
mock_process_results.return_value = None
# When
with the_executor.execute_context() as result:
result.results.append((single_call, 'OK'))
# Then
assert result.serializer is None
assert mock_serializer.called is False
def test_execute_context(fake_rpc_serializer, single_call):
# Given
fake_responses = {
'jsonrpc': '2.0',
'id': 'test',
'result': 'OK',
}
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'process_results') as mock_process_results:
with mock.patch.object(the_executor, 'serializer') as mock_serializer:
mock_process_results.return_value = fake_responses
mock_serializer.return_value = fake_rpc_serializer
# When
with the_executor.execute_context() as result:
result.results.append((single_call, 'OK'))
# Then
assert result.serializer is fake_rpc_serializer
mock_process_results.assert_called_with([(single_call, 'OK')])
mock_serializer.assert_called_with(fake_responses)
def test_enrich_args():
# Given
the_executor = executor.Executor()
# When
result = the_executor.enrich_args(['spam', 'eggs'])
# Then
assert result == ['spam', 'eggs']
def test_enrich_kwargs():
# Given
the_executor = executor.Executor()
# When
result = the_executor.enrich_kwargs({'spam': True, 'eggs': False})
# Then
assert result == {'spam': True, 'eggs': False}
@pytest.mark.skip('NOOP')
def test_before_call():
pass
@mock.patch.object(executor.MethodRegistry, 'shared_registry')
def test_execute(mock_shared_registry, fake_method_registry):
# Given
fake_method_registry.get_handler.return_value = None
fake_method_registry.get_methods.return_value = []
mock_shared_registry.return_value = fake_method_registry
batch = [
{
'jsonrpc': '2.0',
'id': 'call_1',
'method': 'system.list_methods',
'params': ['spam'],
},
{
'jsonrpc': '2.0',
'id': 'call_2',
'method': 'idontexist',
},
{
'jsonrpc': '2.0',
'method': 'system.list_methods',
'params': {'spam': True},
},
]
payload = json.dumps(batch)
the_executor = executor.Executor()
with mock.patch.object(the_executor, 'before_call') as mock_before_call:
# When
result = the_executor.execute(payload)
# Then
assert isinstance(result, JSONRPCSerializer)
expected_result_data = [
{
'jsonrpc': '2.0',
'id': 'call_1',
'result': ['system.list_methods'],
},
{
'jsonrpc': '2.0',
'id': 'call_2',
'error': {
'code': exceptions.JSONRPCMethodNotFoundError.ERROR_CODE,
'message': exceptions.JSONRPCMethodNotFoundError.ERROR_MESSAGE,
},
},
]
assert result.data == expected_result_data
fake_method_registry.get_handler.assert_called_with(
'jsonrpc', 'idontexist',
)
assert mock_before_call.call_count == 2
mock_before_call.assert_any_call('system.list_methods', ['spam'], {})
mock_before_call.assert_any_call(
'system.list_methods', [], {'spam': True},
)