Skip to content

Commit 599e7b2

Browse files
committed
Merge pull request #2 from mat128/master
Return 404 on missing modules
2 parents a9dd94b + 7c69f9a commit 599e7b2

6 files changed

Lines changed: 67 additions & 33 deletions

File tree

tests/test_api.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,21 @@ def setUp(self):
2626
self.app = Flask('test_app')
2727
self.api_client = self.app.test_client()
2828
self.router = mock.Mock()
29+
self.module1 = mock.Mock()
30+
self.module2 = mock.Mock()
31+
self.modules = {'module1': self.module1,
32+
'module2': self.module2}
2933

30-
self.api = Api(self.app, self.router)
34+
self.api = Api(self.modules, self.app, self.router)
35+
36+
def generate_module_path(self, module_name):
37+
return '/{0}/'.format(module_name)
3138

3239
def test_list_implemented_methods(self):
3340
self.router.list_implemented_methods.return_value = ['abcd', 'efgh']
3441

35-
output = self.api_client.get('/module1/')
36-
self.router.list_implemented_methods.assert_called_with('module1')
42+
output = self.api_client.get(self.generate_module_path('module1'))
43+
self.router.list_implemented_methods.assert_called_with(self.module1)
3744

3845
assert_that(json.loads(output.data.decode(output.charset)), is_({
3946
"implemented_methods": [
@@ -44,7 +51,7 @@ def test_list_implemented_methods(self):
4451

4552
def test_execute_method_returns_string(self):
4653
self.router.invoke_method.return_value = 'simple string'
47-
output = self.api_client.post('/module2/',
54+
output = self.api_client.post(self.generate_module_path('module2'),
4855
headers={'Content-Type': 'application/json'},
4956
data=json.dumps(
5057
{
@@ -57,12 +64,12 @@ def test_execute_method_returns_string(self):
5764
}
5865
))
5966

60-
self.router.invoke_method.assert_called_with(module_name='module2', method='remote_method', params=[], env={'variable1': 'value1'}, callback={})
67+
self.router.invoke_method.assert_called_with(module=self.module2, method='remote_method', params=[], env={'variable1': 'value1'}, callback={})
6168
assert_that(json.loads(output.data.decode(output.charset)), is_('simple string'))
6269

6370
def test_execute_method_returns_list(self):
6471
self.router.invoke_method.return_value = ['a', 'b', 'c']
65-
output = self.api_client.post('/module2/',
72+
output = self.api_client.post(self.generate_module_path('module2'),
6673
headers={'Content-Type': 'application/json'},
6774
data=json.dumps(
6875
{
@@ -75,6 +82,30 @@ def test_execute_method_returns_list(self):
7582
}
7683
))
7784

78-
self.router.invoke_method.assert_called_with(module_name='module2', method='remote_method', params=[], env={'variable1': 'value1'}, callback={})
85+
self.router.invoke_method.assert_called_with(module=self.module2, method='remote_method', params=[], env={'variable1': 'value1'}, callback={})
7986
assert_that(json.loads(output.data.decode(output.charset)), is_(['a', 'b', 'c']))
8087

88+
def test_invoking_unknown_module_returns_a_404(self):
89+
output = self.api_client.post(self.generate_module_path('new_module'),
90+
headers={'Content-Type': 'application/json'},
91+
data=json.dumps(
92+
{
93+
"method": "remote_method",
94+
"params": [],
95+
"env": {
96+
"variable1": "value1"
97+
},
98+
"callback": {}
99+
}
100+
))
101+
102+
assert_that(output.status_code, is_(404))
103+
104+
def test_listing_unknown_module_returns_a_404(self):
105+
output = self.api_client.get(self.generate_module_path('new_module'))
106+
107+
assert_that(output.status_code, is_(404))
108+
109+
class NoTrailingSlashApiTest(ApiTest):
110+
def generate_module_path(self, module_name):
111+
return '/{0}'.format(module_name)

tests/test_router.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,18 @@ def setUp(self):
4545
'module2': self.module2,
4646
'module3': self.module3}
4747

48-
self.basic_router = Router(self.modules, env_as_kwarg=False)
49-
self.router = Router(self.modules)
48+
self.basic_router = Router(env_as_kwarg=False)
49+
self.router = Router()
5050

5151
def test_basic_mode(self):
52-
result = self.basic_router.invoke_method(module_name='module1', method='my_method_name',
52+
result = self.basic_router.invoke_method(module=self.module1, method='my_method_name',
5353
params=['value1', 'value2'])
5454

5555
self.module1.my_method_name.assert_called_with('value1', 'value2')
5656
assert_that(result, is_('yes'))
5757

5858
def test_env_is_optionally_passed_as_keyword_argument_in_every_call(self):
59-
result = self.router.invoke_method(module_name='module2', method='hello',
59+
result = self.router.invoke_method(module=self.module2, method='hello',
6060
params=['value1', 'value2'],
6161
env={'local_variable1': 'value1', 'local_variable2': 'value2'})
6262

@@ -65,10 +65,10 @@ def test_env_is_optionally_passed_as_keyword_argument_in_every_call(self):
6565
assert_that(result, is_('world'))
6666

6767
def test_list_implemented_methods(self):
68-
result = self.router.list_implemented_methods('module3')
68+
result = self.router.list_implemented_methods(self.module3)
6969

7070
assert_that(result, is_(['ab', 'cd']))
7171

7272
def test_accept_callback_as_kwarg(self):
73-
self.router.invoke_method(module_name='module1', method='hello', params=[], env={},
73+
self.router.invoke_method(module=self.module1, method='hello', params=[], env={},
7474
callback={'url': 'http://example.net', 'params': {'k1': 'v1', 'k2': 'v2'}})

tests/test_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ def test_starting_a_server(self, api_mock, router_mock, flask_mock):
3434
s = server.Server(modules)
3535
s.run()
3636

37-
router_mock.assert_called_with(modules)
38-
api_mock.assert_called_with(flask_instance, router_instance)
37+
router_mock.assert_called_with()
38+
api_mock.assert_called_with(modules, flask_instance, router_instance)
3939
flask_instance.run.assert_called_with()
4040

4141
def test_run_passes_parameters_to_flask(self, api_mock, router_mock, flask_mock):

ubersmith_remote_module_server/api.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,36 @@
1414

1515
import json
1616
from flask import request, current_app
17+
import functools
1718

1819

1920
class Api(object):
20-
def __init__(self, app, router):
21+
def __init__(self, modules, app, router):
2122
self.app = app
2223
self.router = router
24+
self.app.url_map.strict_slashes = False
2325

24-
app.add_url_rule('/<module_name>/', view_func=self.list_implemented_methods, methods=['GET'])
25-
app.add_url_rule('/<module_name>/', view_func=self.handle_remote_invocation, methods=['POST'])
26+
for module_name, module in modules.items():
27+
list_endpoint = functools.partial(self.list_implemented_methods, module)
28+
list_endpoint.__name__ = "list_" + module_name
29+
app.add_url_rule('/{}'.format(module_name), view_func=list_endpoint, methods=['GET'])
2630

31+
handle_endpoint = functools.partial(self.handle_remote_invocation, module)
32+
handle_endpoint.__name__ = "handle_" + module_name
33+
app.add_url_rule('/{}'.format(module_name), view_func=handle_endpoint, methods=['POST'])
2734

28-
def list_implemented_methods(self, module_name):
29-
return json_response({
30-
'implemented_methods': self.router.list_implemented_methods(module_name)
31-
}, 200)
35+
def list_implemented_methods(self, module):
36+
methods = self.router.list_implemented_methods(module)
37+
return json_response({'implemented_methods': methods}, 200)
3238

33-
def handle_remote_invocation(self, module_name):
39+
def handle_remote_invocation(self, module):
3440
data = request.get_json()
35-
output = self.router.invoke_method(module_name=module_name, **data)
41+
output = self.router.invoke_method(module=module, **data)
3642
return json_response(output, 200)
3743

3844
def json_response(data, code):
3945
json_data = json.dumps(data, indent=None)
4046
response = current_app.response_class(json_data, mimetype='application/json; charset=UTF-8')
4147
response.status_code = code
4248

43-
return response
49+
return response

ubersmith_remote_module_server/router.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
# limitations under the License.
1414

1515
class Router(object):
16-
def __init__(self, modules, env_as_kwarg=True):
17-
self.modules = modules
16+
def __init__(self, env_as_kwarg=True):
1817
self.env_as_kwarg = env_as_kwarg
1918

20-
def invoke_method(self, module_name, method, params=None, env=None, callback=None):
19+
def invoke_method(self, module, method, params=None, env=None, callback=None):
2120
if params is None:
2221
params = []
2322
if env is None:
@@ -28,9 +27,7 @@ def invoke_method(self, module_name, method, params=None, env=None, callback=Non
2827
else:
2928
additional_kwargs = {}
3029

31-
module = self.modules[module_name]
3230
return getattr(module, method)(*params, **additional_kwargs)
3331

34-
def list_implemented_methods(self, module_name):
35-
module = self.modules[module_name]
32+
def list_implemented_methods(self, module):
3633
return [method for method in dir(module) if callable(getattr(module, method)) and not method.startswith('_')]

ubersmith_remote_module_server/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818

1919
class Server(object):
2020
def __init__(self, modules):
21-
self.router = router.Router(modules)
21+
self.router = router.Router()
2222
self.app = Flask(__name__)
23-
self.api = api.Api(self.app, self.router)
23+
self.api = api.Api(modules, self.app, self.router)
2424

2525
def run(self, *args, **kwargs):
2626
self.app.run(*args, **kwargs)

0 commit comments

Comments
 (0)