Skip to content

Commit f57bae0

Browse files
authored
Merge pull request #6 from fbouliane/allow-calls-to-ubersmith-core
Allow calls to legacy ubersmith with UbersmithCore
2 parents 767bf58 + 1b84708 commit f57bae0

File tree

12 files changed

+414
-4
lines changed

12 files changed

+414
-4
lines changed

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
flask==0.10.1
1+
flask==0.10.1
2+
python-ubersmithclient>=2.0.6
3+
six

test-requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
nose>=1.2.1
22
mock>=1.0.1
3-
pyhamcrest>=1.6
3+
pyhamcrest>=1.6
4+
flexmock>=0.10.2

tests/Integration/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2016 Internap.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
class ContextMatcher(object):
17+
def __init__(self, callback_url, module_id, device_id=None, service_id=None):
18+
self.callback_url = callback_url
19+
self.module_id = module_id
20+
self.device_id = device_id
21+
self.service_id = service_id
22+
23+
def __eq__(self, other):
24+
return self.callback_url == other.callback_url and \
25+
self.module_id == other.module_id and \
26+
((self.device_id and self.device_id == other.device_id) or
27+
(self.service_id and self.service_id == other.service_id))
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Copyright 2016 Internap.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
17+
from flexmock import flexmock, flexmock_teardown
18+
19+
from . import ContextMatcher
20+
from ubersmith_remote_module_server import ubersmith_core
21+
from ubersmith_remote_module_server.objects import RequestContext
22+
from ubersmith_remote_module_server.router import Router
23+
from ubersmith_remote_module_server.ubersmith_core import UbersmithCore
24+
25+
26+
class FakeModule(object):
27+
def call_uber_core(self):
28+
UbersmithCore.test()
29+
30+
31+
class RouterUbersmithCoreTest(unittest.TestCase):
32+
def test_a_module_can_call_ubersmith_core_without_context(self):
33+
fake_module = FakeModule()
34+
remote_executor_mock = flexmock()
35+
36+
flexmock(ubersmith_core).should_receive('RemoteExecutor').\
37+
with_args(context=RequestContext).and_return(remote_executor_mock)
38+
39+
remote_executor_mock.should_receive('invoke_global').with_args('test', args={}).once()
40+
41+
self.basic_router = Router(env_as_kwarg=False)
42+
self.basic_router.invoke_method(module=fake_module, method='call_uber_core')
43+
44+
def test_module_context_is_injected_when_service_module(self):
45+
fake_module = FakeModule()
46+
remote_executor_mock = flexmock()
47+
48+
example_callback = {'params': {'module_id': '44', 'service_id': '1260'},
49+
'url': 'http://user:[email protected]/'
50+
'api/2.0/?method=service.module_call'}
51+
52+
flexmock(ubersmith_core).should_receive('RemoteExecutor'). \
53+
with_args(context=ContextMatcher(callback_url=example_callback['url'],
54+
module_id='44',
55+
service_id='1260'))\
56+
.and_return(remote_executor_mock)
57+
58+
remote_executor_mock.should_receive('invoke_global')
59+
60+
self.basic_router = Router(env_as_kwarg=False)
61+
self.basic_router.invoke_method(module=fake_module,
62+
method='call_uber_core',
63+
callback=example_callback)
64+
65+
def test_module_context_is_injected_when_device_module(self):
66+
fake_module = FakeModule()
67+
68+
example_callback = {'params': {'module_id': '173', 'device_id': '200025'},
69+
'url': 'http://user:[email protected]/'
70+
'api/2.0/?method=device.module_call'}
71+
72+
remote_executor_mock = flexmock()
73+
flexmock(ubersmith_core).should_receive('RemoteExecutor'). \
74+
with_args(context=ContextMatcher(callback_url=example_callback['url'],
75+
module_id='173',
76+
device_id='200025'))\
77+
.and_return(remote_executor_mock)
78+
79+
remote_executor_mock.should_receive('invoke_global')
80+
81+
self.basic_router = Router(env_as_kwarg=False)
82+
self.basic_router.invoke_method(module=fake_module,
83+
method='call_uber_core',
84+
callback=example_callback)
85+
86+
def tearDown(self):
87+
flexmock_teardown()
88+
super(RouterUbersmithCoreTest, self).tearDown()

tests/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,19 @@
1010
# distributed under the License is distributed on an "AS IS" BASIS,
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
13-
# limitations under the License.
13+
# limitations under the License.
14+
15+
from flexmock import flexmock
16+
17+
18+
def mock_ubersmith_client():
19+
ubersmith_client = flexmock()
20+
ubersmith_client.api = flexmock()
21+
return ubersmith_client
22+
23+
24+
def mock_ubersmith_api():
25+
ubersmith_api = flexmock()
26+
ubersmith_api.device = flexmock()
27+
ubersmith_api.client = flexmock()
28+
return ubersmith_api

tests/test_remote_executor.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright 2016 Internap.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
17+
from flexmock import flexmock, flexmock_teardown
18+
19+
from ubersmith_remote_module_server import _remote_executor
20+
from ubersmith_remote_module_server._remote_executor import RemoteExecutor
21+
from ubersmith_remote_module_server.objects import RequestContext
22+
from . import mock_ubersmith_client, mock_ubersmith_api
23+
24+
25+
class RemoteExecutorTest(unittest.TestCase):
26+
def test_invoke_global_from_device_module(self):
27+
ubersmith_client = mock_ubersmith_client()
28+
ubersmith_api = mock_ubersmith_api()
29+
30+
flexmock(_remote_executor).should_receive('ubersmith_client').and_return(ubersmith_client)
31+
32+
ubersmith_client.api.should_receive('init')\
33+
.with_args(url='http://ubersmith.example/url',
34+
password='pass',
35+
user='user')\
36+
.and_return(ubersmith_api)
37+
38+
ubersmith_api.device.should_receive('module_call').\
39+
with_args(device_id=20025,
40+
function='_web_hook_invoke_global',
41+
module_id=173,
42+
module_params=['logevent', [dict(action='hello from python')]])
43+
44+
remote_executor = RemoteExecutor(
45+
context=RequestContext(callback_url='http://user:[email protected]/url',
46+
module_id=173, device_id=20025))
47+
remote_executor.invoke_global('logevent', dict(action='hello from python'))
48+
49+
def test_invoke_global_from_service_module(self):
50+
ubersmith_client = mock_ubersmith_client()
51+
ubersmith_api = mock_ubersmith_api()
52+
53+
flexmock(_remote_executor).should_receive('ubersmith_client').and_return(ubersmith_client)
54+
55+
ubersmith_client.api.should_receive('init')\
56+
.with_args(url='http://ubersmith.example/url',
57+
password='pass',
58+
user='user')\
59+
.and_return(ubersmith_api)
60+
61+
ubersmith_api.client.should_receive('service_module_call').\
62+
with_args(service_id=1260,
63+
function='_web_hook_invoke_global',
64+
module_id=44,
65+
module_params=['logevent', [dict(action='hello from python')]])
66+
67+
remote_executor = RemoteExecutor(
68+
context=RequestContext(callback_url='http://user:[email protected]/'
69+
'url?method=method',
70+
module_id=44,
71+
service_id=1260))
72+
remote_executor.invoke_global('logevent', dict(action='hello from python'))
73+
74+
def tearDown(self):
75+
flexmock_teardown()
76+
super(RemoteExecutorTest, self).tearDown()
77+
78+

tests/test_ubersmith_core.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2016 Internap.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
17+
from flexmock import flexmock, flexmock_teardown
18+
19+
from ubersmith_remote_module_server import ubersmith_core
20+
from ubersmith_remote_module_server.exceptions import NamedArgumentsOnly
21+
from ubersmith_remote_module_server.ubersmith_core import NoRequestContext, UbersmithCore, \
22+
ConfiguredRequestContext
23+
24+
25+
class UbersmithCoreTest(unittest.TestCase):
26+
def test_raises_when_out_of_context(self):
27+
remote_executor_mock = flexmock()
28+
flexmock(ubersmith_core).should_receive('RemoteExecutor').and_return(remote_executor_mock)
29+
with self.assertRaises(NoRequestContext):
30+
UbersmithCore.test()
31+
32+
def test_raises_when_args(self):
33+
remote_executor_mock = flexmock()
34+
flexmock(ubersmith_core).should_receive('RemoteExecutor').and_return(remote_executor_mock)
35+
with self.assertRaises(NamedArgumentsOnly):
36+
UbersmithCore.test('hello')
37+
38+
def test_calls_executor_with_context_when_called(self):
39+
remote_executor_mock = flexmock()
40+
flexmock(ubersmith_core).should_receive('RemoteExecutor').\
41+
with_args(context='context').and_return(remote_executor_mock).once()
42+
remote_executor_mock.should_receive('invoke_global').with_args('test', args={}).once()
43+
44+
with ConfiguredRequestContext(context='context'):
45+
UbersmithCore.test()
46+
47+
def tearDown(self):
48+
flexmock_teardown()
49+
super(UbersmithCoreTest, self).tearDown()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2016 Internap.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import ubersmith_client
16+
from six.moves.urllib.parse import urlparse
17+
18+
19+
class RemoteExecutor(object):
20+
def __init__(self, context):
21+
self.context = context
22+
23+
def invoke_global(self, func_name, args):
24+
url = urlparse(self.context.callback_url)
25+
ubersmith_url = "{0}://{1}{2}".format(url.scheme, url.hostname, url.path)
26+
ubersmith_api = ubersmith_client.api.init(url=ubersmith_url,
27+
user=url.username,
28+
password=url.password)
29+
if self.context.device_id:
30+
ubersmith_api.device.module_call(device_id=self.context.device_id,
31+
module_id=self.context.module_id,
32+
function='_web_hook_invoke_global',
33+
module_params=[func_name, [args]])
34+
else:
35+
ubersmith_api.client.service_module_call(service_id=self.context.service_id,
36+
module_id=self.context.module_id,
37+
function='_web_hook_invoke_global',
38+
module_params=[func_name, [args]])
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2016 Internap.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
class NoRequestContext(Exception):
17+
def __init__(self, msg="UbersmithCore was called without a context, "
18+
"you can't save this object"):
19+
super(NoRequestContext, self).__init__(msg)
20+
21+
22+
class NamedArgumentsOnly(Exception):
23+
def __init__(self, msg="UbersmithCore was called with non-named arguments, "
24+
"you MUST use named arguments (kwargs)"):
25+
super(NamedArgumentsOnly, self).__init__(msg)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2016 Internap.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
class RequestContext(object):
17+
def __init__(self, callback_url='', module_id='', device_id=None, service_id=None):
18+
self.callback_url = callback_url
19+
self.module_id = module_id
20+
self.device_id = device_id
21+
self.service_id = service_id

0 commit comments

Comments
 (0)