Skip to content
This repository was archived by the owner on Mar 22, 2018. It is now read-only.

Commit 52ecfba

Browse files
committed
test: Added basic unittests.
1 parent d612471 commit 52ecfba

12 files changed

+334
-14
lines changed

MANIFEST.in

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
include CONTRIBUTORS
2+
include COPYING
3+
include LICENSE
4+
include MAINTAINERS
5+
include README.md
6+
include requirements.txt
7+
include test-requirements.txt
8+
graft doc
9+
graft example

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ max-complexity=10
66
[nosetests]
77
verbosity=2
88
with-coverage=1
9-
cover-package=commissaire_service
9+
cover-package=commissaire_http
1010
cover-min-percentage=80
1111
cover-html=1
1212
cover-html-dir=../build/cover/

src/commissaire_http/dispatcher/__init__.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ def parse_query_string(qs):
4040
if len(value) == 1:
4141
new_qs[key] = escape(value[0])
4242
else:
43-
new_qs[key] = value
43+
new_value = []
44+
for item in value:
45+
new_value.append(escape(item))
46+
new_qs[key] = new_value
4447
return new_qs
4548

4649

@@ -154,7 +157,7 @@ def dispatch(self, environ, start_response):
154157
self.logger.debug(
155158
'Unable to read "wsgi.input": {}'.format(error))
156159
else:
157-
params = parse_query_string(environ['QUERY_STRING'])
160+
params = parse_query_string(environ.get('QUERY_STRING'))
158161

159162
# method is normally supposed to be the method to be called
160163
# but we hijack it for the method that was used over HTTP

src/commissaire_http/handlers/__init__.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ def create_response(id, result=None, error=None):
3737
if result:
3838
jsonrpc_response['result'] = result
3939
elif error:
40-
jsonrpc_response['error'] = error
40+
jsonrpc_response['error'] = {
41+
'code': -32603, # Default to Internal Error
42+
'message': error,
43+
}
4144
else:
4245
raise TypeError('Either a result or error is required.')
4346
return jsonrpc_response
4447

4548

46-
def hello_world(message):
49+
def hello_world(message): # pragma: no cover
4750
"""
4851
Example function handler that simply says hello. If name is given
4952
in the query string it uses it.
@@ -59,7 +62,7 @@ def hello_world(message):
5962
return create_response(message['id'], response_msg)
6063

6164

62-
def create_world(message):
65+
def create_world(message): # pragma: no cover
6366
"""
6467
Example function handler that simply says hello. If name is given
6568
in the query string it uses it.
@@ -82,7 +85,7 @@ def create_world(message):
8285
raise error
8386

8487

85-
class ClassHandlerExample:
88+
class ClassHandlerExample: # pragma: no cover
8689
"""
8790
Example class based handlers.
8891
"""

src/commissaire_http/router/__init__.py

-6
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,4 @@ def match(self, *args, **kwargs):
4848
args, kwargs))
4949
result = super(Router, self).match(*args, **kwargs)
5050
self.logger.debug('Router result: {}'.format(result))
51-
52-
if result and result.get('topic'):
53-
topic = result['topic'].format(**result)
54-
self.logger.info('Found a topic. Routing to {}'.format(topic))
55-
result['topic'] = topic
56-
self.logger.debug('End result: {}'.format(result))
5751
return result

test-requirements.txt

-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@ tox
22
nose
33
flake8
44
coverage
5-
mock

test/__init__.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright (C) 2016 Red Hat, Inc
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
from unittest import TestCase, mock
17+
18+
19+
class TestCase(TestCase):
20+
"""
21+
Parent class for all unittests.
22+
"""
23+
pass

test/test_dispatcher.py

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Copyright (C) 2016 Red Hat, Inc
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
"""
16+
Test for commissaire_http.dispatcher
17+
"""
18+
19+
import json
20+
21+
from io import BytesIO
22+
23+
from . import TestCase, mock
24+
25+
from commissaire_http.dispatcher import Dispatcher
26+
from commissaire_http.router import Router
27+
28+
29+
class TestDispatcher(TestCase):
30+
"""
31+
Test for the Dispatcher class.
32+
"""
33+
34+
def setUp(self):
35+
"""
36+
Creates a new instance to test with per test.
37+
"""
38+
self.router_instance = Router()
39+
self.router_instance.connect(
40+
'/path/',
41+
controller='commissaire_http.handlers.hello_world',
42+
conditions={'method': 'GET'})
43+
self.router_instance.connect(
44+
'/world/',
45+
controller='commissaire_http.handlers.create_world',
46+
conditions={'method': 'PUT'})
47+
self.dispatcher_instance = Dispatcher(
48+
self.router_instance,
49+
handler_packages=['commissaire_http.handlers']
50+
)
51+
52+
def test_dispatcher_initialization(self):
53+
"""
54+
Verify the Dispatcher initializes properly.
55+
"""
56+
self.assertEquals(
57+
self.router_instance, self.dispatcher_instance._router)
58+
self.assertTrue(self.dispatcher_instance._handler_map)
59+
60+
def test_dispatcher_reload_handlers(self):
61+
"""
62+
Verify the Dispatcher.reload_handlers actually loads handlers.
63+
"""
64+
self.dispatcher_instance._handler_map = {}
65+
self.assertFalse(self.dispatcher_instance._handler_map)
66+
self.dispatcher_instance.reload_handlers()
67+
self.assertTrue(self.dispatcher_instance._handler_map)
68+
69+
def test_dispatcher_dispatch_with_valid_path(self):
70+
"""
71+
Verify the Dispatcher.dispatch works with valid paths.
72+
"""
73+
environ = {
74+
'PATH_INFO': '/path/',
75+
'REQUEST_METHOD': 'GET',
76+
}
77+
start_response = mock.MagicMock()
78+
result = self.dispatcher_instance.dispatch(environ, start_response)
79+
start_response.assert_called_once_with('200 OK', mock.ANY)
80+
self.assertEquals('{"Hello": "there"}', result[0].decode())
81+
82+
def test_dispatcher_dispatch_with_valid_path_and_params(self):
83+
"""
84+
Verify the Dispatcher.dispatch works with valid paths and params.
85+
"""
86+
environ = {
87+
'PATH_INFO': '/path/',
88+
'REQUEST_METHOD': 'GET',
89+
'QUERY_STRING': 'name=bob'
90+
}
91+
start_response = mock.MagicMock()
92+
result = self.dispatcher_instance.dispatch(environ, start_response)
93+
start_response.assert_called_once_with('200 OK', mock.ANY)
94+
self.assertEquals('{"Hello": "bob"}', result[0].decode())
95+
96+
def test_dispatcher_dispatch_with_valid_path_with_wsgi_input(self):
97+
"""
98+
Verify the Dispatcher.dispatch works when wsgi_input is in use.
99+
"""
100+
environ = {
101+
'PATH_INFO': '/world/',
102+
'REQUEST_METHOD': 'PUT', # PUT uses wsgi.input
103+
'wsgi.input': BytesIO(b'{"name": "world"}'),
104+
'CONTENT_LENGTH': b'18',
105+
}
106+
start_response = mock.MagicMock()
107+
result = self.dispatcher_instance.dispatch(environ, start_response)
108+
start_response.assert_called_once_with('200 OK', mock.ANY)
109+
self.assertEquals('world', json.loads(result[0].decode())['name'])
110+
111+
def test_dispatcher_dispatch_with_invalid_path(self):
112+
"""
113+
Verify the Dispatcher.dispatch works with invalid paths.
114+
"""
115+
environ = {
116+
'PATH_INFO': '/idonotexist/',
117+
'REQUEST_METHOD': 'GET',
118+
}
119+
start_response = mock.MagicMock()
120+
result = self.dispatcher_instance.dispatch(environ, start_response)
121+
start_response.assert_called_once_with('404 Not Found', mock.ANY)
122+
self.assertEquals('Not Found', result[0].decode())
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright (C) 2016 Red Hat, Inc
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
"""
16+
Test for commissaire_http.dispatcher.parse_query_string
17+
"""
18+
19+
from . import TestCase
20+
from commissaire_http.dispatcher import parse_query_string
21+
22+
23+
class Test_parse_query_string(TestCase):
24+
"""
25+
Test for the parse_query_string helper function.
26+
"""
27+
28+
def Test_parse_query_string_with_unescaped_data(self):
29+
"""
30+
Verify parse_query_string works when no data has to be escaped.
31+
"""
32+
self.assertEquals(
33+
{'test': 'ok'},
34+
parse_query_string('test=ok'))
35+
self.assertEquals(
36+
{'test': 'ok', 'second': 'item'},
37+
parse_query_string('test=ok&second=item'))
38+
self.assertEquals(
39+
{'test': ['ok', 'another'], 'second': 'item'},
40+
parse_query_string('test=ok&second=item&test=another'))
41+
42+
43+
def Test_parse_query_string_with_escaped_data(self):
44+
"""
45+
Verify parse_query_string works when data has to be escaped.
46+
"""
47+
self.assertEquals(
48+
{'test': '&quot;ok&quot;'},
49+
parse_query_string('test="ok"'))
50+
self.assertEquals(
51+
{'test': '&quot;ok&quot;', 'second': 'item'},
52+
parse_query_string('test="ok"&second=item'))
53+
self.assertEquals(
54+
{'test': ['&quot;ok&quot;', 'another'], 'second': 'item'},
55+
parse_query_string('test="ok"&second=item&test=another'))

test/test_handlers_create_response.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright (C) 2016 Red Hat, Inc
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
"""
16+
Test for commissaire_http.handlers.create_response
17+
"""
18+
19+
from . import TestCase
20+
from commissaire_http.handlers import create_response
21+
22+
UID = '123'
23+
24+
25+
class Test_create_response(TestCase):
26+
"""
27+
Test for the create_response helper function.
28+
"""
29+
30+
def test_create_response_without_result_or_error(self):
31+
"""
32+
Verify create_response requires a result or error.
33+
"""
34+
self.assertRaises(TypeError, create_response, UID)
35+
36+
def test_create_response_with_result(self):
37+
"""
38+
Verify create_response creates the proper result jsonrpc structure.
39+
"""
40+
response = create_response(UID, result={'test': 'data'})
41+
self.assertEquals('2.0', response['jsonrpc'])
42+
self.assertEquals(UID, response['id'])
43+
self.assertEquals({'test': 'data'}, response['result'])
44+
45+
def test_create_response_with_error(self):
46+
"""
47+
Verify create_response creates the proper error jsonrpc structure.
48+
"""
49+
response = create_response(UID, error='test')
50+
self.assertEquals('2.0', response['jsonrpc'])
51+
self.assertEquals(UID, response['id'])
52+
self.assertEquals('test', response['error']['message'])

test/test_router.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright (C) 2016 Red Hat, Inc
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
"""
16+
Test for commissaire_http.router
17+
"""
18+
19+
from . import TestCase
20+
from commissaire_http.router import Router
21+
22+
class TestRouter(TestCase):
23+
"""
24+
Test for the Router class.
25+
"""
26+
27+
def setUp(self):
28+
"""
29+
Creates a new instance to test with per test.
30+
"""
31+
self.router_instance = Router()
32+
self.router_instance.connect(
33+
'/path/',
34+
controller='controller',
35+
conditions={'method': 'GET'})
36+
37+
def test_router_successful_match(self):
38+
"""
39+
Verify the Router finds connected paths.
40+
"""
41+
self.assertTrue(self.router_instance.match('/path/'))
42+
43+
def test_router_missing_match(self):
44+
"""
45+
Verify the Router returns None on unsuccessful match.
46+
"""
47+
self.assertIsNone(self.router_instance.match('/idonotexist/'))

0 commit comments

Comments
 (0)