Remove dependency on werkzeug

I'm using a patched version of pecan until a new release
is cut with the fix to the test app thing that I submitted
(https://review.openstack.org/#/c/81328/).

There's a couple of changes outside of tests.api - app.py
changes are to fit in better with pecan (specifically my patch),
and the __init__.py change is because I noticed when testing
that there was no `commands` link in the response from /v1.
This commit is contained in:
Jim Rollenhagen 2014-03-18 14:44:38 -07:00
parent 7a8c218e87
commit 74ee4c76b0
4 changed files with 163 additions and 78 deletions
requirements.txt
teeth_agent
api
app.py
controllers/v1
tests

@ -1,8 +1,7 @@
Werkzeug==0.9.4
requests==2.0.0 requests==2.0.0
stevedore==0.14 stevedore==0.14
wsgiref>=0.1.2 wsgiref>=0.1.2
pecan>=0.4.5 -e git+https://github.com/jimrollenhagen/pecan@7c8db9bca2daf33a4616f0308db75a0c0d55d015#egg=pecan
WSME>=0.6 WSME>=0.6
six>=1.5.2 six>=1.5.2
oslo.config==1.2.1 oslo.config==1.2.1

@ -35,7 +35,7 @@ def get_pecan_config():
return pecan.configuration.conf_from_file(filename) return pecan.configuration.conf_from_file(filename)
def setup_app(agent, pecan_config=None, extra_hooks=None): def setup_app(pecan_config=None, extra_hooks=None, agent=None):
app_hooks = [AgentHook(agent)] app_hooks = [AgentHook(agent)]
if not pecan_config: if not pecan_config:
@ -57,7 +57,7 @@ def setup_app(agent, pecan_config=None, extra_hooks=None):
class VersionSelectorApplication(object): class VersionSelectorApplication(object):
def __init__(self, agent): def __init__(self, agent):
pc = get_pecan_config() pc = get_pecan_config()
self.v1 = setup_app(agent, pecan_config=pc) self.v1 = setup_app(pecan_config=pc, agent=agent)
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
return self.v1(environ, start_response) return self.v1(environ, start_response)

@ -74,7 +74,7 @@ class V1(base.APIBase):
bookmark=True, bookmark=True,
type='text/html') type='text/html')
] ]
v1.command = [ v1.commands = [
link.Link.make_link('self', link.Link.make_link('self',
pecan.request.host_url, pecan.request.host_url,
'commands', 'commands',

@ -14,60 +14,167 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
""" """
import json
import mock import mock
import time import time
import unittest import unittest
from werkzeug import test import pecan
from werkzeug import wrappers import pecan.testing
from teeth_agent import agent from teeth_agent import agent
from teeth_agent.api import app
from teeth_agent import base from teeth_agent import base
PATH_PREFIX = '/v1'
class TestTeethAPI(unittest.TestCase): class TestTeethAPI(unittest.TestCase):
def _get_env_builder(self, method, path, data=None, query=None):
if data is not None:
data = json.dumps(data)
return test.EnvironBuilder(method=method, def setUp(self):
path=path, super(TestTeethAPI, self).setUp()
data=data, self.mock_agent = mock.MagicMock()
content_type='application/json', self.app = self._make_app(self.mock_agent)
query_string=query)
def _make_request(self, api, method, path, data=None, query=None): def reset_pecan():
client = test.Client(api, wrappers.BaseResponse) pecan.set_config({}, overwrite=True)
return client.open(self._get_env_builder(method, path, data, query))
self.addCleanup(reset_pecan)
def _make_app(self, enable_acl=False):
self.config = {
'app': {
'root': 'teeth_agent.api.controllers.root.RootController',
'modules': ['teeth_agent.api'],
'static_root': '',
'debug': True,
},
}
return pecan.testing.load_test_app(config=self.config,
agent=self.mock_agent)
def _request_json(self, path, params, expect_errors=False, headers=None,
method="post", extra_environ=None, status=None,
path_prefix=PATH_PREFIX):
"""Sends simulated HTTP request to Pecan test app.
:param path: url path of target service
:param params: content for wsgi.input of request
:param expect_errors: Boolean value; whether an error is expected based
on request
:param headers: a dictionary of headers to send along with the request
:param method: Request method type. Appropriate method function call
should be used rather than passing attribute in.
:param extra_environ: a dictionary of environ variables to send along
with the request
:param status: expected status code of response
:param path_prefix: prefix of the url path
"""
full_path = path_prefix + path
print('%s: %s %s' % (method.upper(), full_path, params))
response = getattr(self.app, "%s_json" % method)(
str(full_path),
params=params,
headers=headers,
status=status,
extra_environ=extra_environ,
expect_errors=expect_errors
)
print('GOT:%s' % response)
return response
def put_json(self, path, params, expect_errors=False, headers=None,
extra_environ=None, status=None):
"""Sends simulated HTTP PUT request to Pecan test app.
:param path: url path of target service
:param params: content for wsgi.input of request
:param expect_errors: Boolean value; whether an error is expected based
on request
:param headers: a dictionary of headers to send along with the request
:param extra_environ: a dictionary of environ variables to send along
with the request
:param status: expected status code of response
"""
return self._request_json(path=path, params=params,
expect_errors=expect_errors,
headers=headers, extra_environ=extra_environ,
status=status, method="put")
def post_json(self, path, params, expect_errors=False, headers=None,
extra_environ=None, status=None):
"""Sends simulated HTTP POST request to Pecan test app.
:param path: url path of target service
:param params: content for wsgi.input of request
:param expect_errors: Boolean value; whether an error is expected based
on request
:param headers: a dictionary of headers to send along with the request
:param extra_environ: a dictionary of environ variables to send along
with the request
:param status: expected status code of response
"""
return self._request_json(path=path, params=params,
expect_errors=expect_errors,
headers=headers, extra_environ=extra_environ,
status=status, method="post")
def get_json(self, path, expect_errors=False, headers=None,
extra_environ=None, q=[], path_prefix=PATH_PREFIX, **params):
"""Sends simulated HTTP GET request to Pecan test app.
:param path: url path of target service
:param expect_errors: Boolean value;whether an error is expected based
on request
:param headers: a dictionary of headers to send along with the request
:param extra_environ: a dictionary of environ variables to send along
with the request
:param q: list of queries consisting of: field, value, op, and type
keys
:param path_prefix: prefix of the url path
:param params: content for wsgi.input of request
"""
full_path = path_prefix + path
query_params = {'q.field': [],
'q.value': [],
'q.op': [],
}
for query in q:
for name in ['field', 'op', 'value']:
query_params['q.%s' % name].append(query.get(name, ''))
all_params = {}
all_params.update(params)
if q:
all_params.update(query_params)
print('GET: %s %r' % (full_path, all_params))
response = self.app.get(full_path,
params=all_params,
headers=headers,
extra_environ=extra_environ,
expect_errors=expect_errors)
print('GOT:%s' % response)
return response
def test_root(self): def test_root(self):
mock_agent = mock.MagicMock() response = self.get_json('/', path_prefix='')
api_server = app.setup_app(mock_agent) data = response.json
self.assertEqual(data['name'], 'OpenStack Ironic Python Agent API')
response = self._make_request(api_server, 'GET', '/')
self.assertEqual(response.status, '200 OK')
def test_v1_root(self): def test_v1_root(self):
mock_agent = mock.MagicMock() response = self.get_json('/v1', path_prefix='')
api_server = app.setup_app(mock_agent) data = response.json
self.assertTrue('status' in data.keys())
response = self._make_request(api_server, 'GET', '/v1') self.assertTrue('commands' in data.keys())
self.assertEqual(response.status, '200 OK')
def test_get_agent_status(self): def test_get_agent_status(self):
status = agent.TeethAgentStatus('TEST_MODE', time.time(), 'v72ac9') status = agent.TeethAgentStatus('TEST_MODE', time.time(), 'v72ac9')
mock_agent = mock.MagicMock() self.mock_agent.get_status.return_value = status
mock_agent.get_status.return_value = status
api_server = app.setup_app(mock_agent)
response = self._make_request(api_server, 'GET', '/v1/status') response = self.get_json('/status')
mock_agent.get_status.assert_called_once_with() self.mock_agent.get_status.assert_called_once_with()
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
data = json.loads(response.data) data = response.json
self.assertEqual(data['mode'], status.mode) self.assertEqual(data['mode'], status.mode)
self.assertEqual(data['started_at'], status.started_at) self.assertEqual(data['started_at'], status.started_at)
self.assertEqual(data['version'], status.version) self.assertEqual(data['version'], status.version)
@ -83,71 +190,55 @@ class TestTeethAPI(unittest.TestCase):
True, True,
{'test': 'result'}) {'test': 'result'})
mock_agent = mock.MagicMock() self.mock_agent.execute_command.return_value = result
mock_agent.execute_command.return_value = result
api_server = app.setup_app(mock_agent)
response = self._make_request(api_server, response = self.post_json('/commands', command)
'POST', self.assertEqual(response.status_code, 200)
'/v1/commands/',
data=command)
self.assertEqual(mock_agent.execute_command.call_count, 1) self.assertEqual(self.mock_agent.execute_command.call_count, 1)
args, kwargs = mock_agent.execute_command.call_args args, kwargs = self.mock_agent.execute_command.call_args
self.assertEqual(args, ('do_things',)) self.assertEqual(args, ('do_things',))
self.assertEqual(kwargs, {'key': 'value'}) self.assertEqual(kwargs, {'key': 'value'})
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
expected_result = result.serialize() expected_result = result.serialize()
data = response.json
self.assertEqual(data, expected_result) self.assertEqual(data, expected_result)
def test_execute_agent_command_validation(self): def test_execute_agent_command_validation(self):
mock_agent = mock.MagicMock()
api_server = app.setup_app(mock_agent)
invalid_command = {} invalid_command = {}
response = self._make_request(api_server, response = self.post_json('/commands',
'POST', invalid_command,
'/v1/commands', expect_errors=True)
data=invalid_command)
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
data = json.loads(response.data) data = response.json
msg = 'Invalid input for field/attribute name.' msg = 'Invalid input for field/attribute name.'
self.assertTrue(msg in data['faultstring']) self.assertTrue(msg in data['faultstring'])
msg = 'Mandatory field missing' msg = 'Mandatory field missing'
self.assertTrue(msg in data['faultstring']) self.assertTrue(msg in data['faultstring'])
def test_execute_agent_command_params_validation(self): def test_execute_agent_command_params_validation(self):
mock_agent = mock.MagicMock()
api_server = app.setup_app(mock_agent)
invalid_command = {'name': 'do_things', 'params': []} invalid_command = {'name': 'do_things', 'params': []}
response = self._make_request(api_server, response = self.post_json('/commands',
'POST', invalid_command,
'/v1/commands', expect_errors=True)
data=invalid_command)
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
data = json.loads(response.data) data = response.json
# this message is actually much longer, but I'm ok with this # this message is actually much longer, but I'm ok with this
msg = 'Invalid input for field/attribute params.' msg = 'Invalid input for field/attribute params.'
self.assertTrue(msg in data['faultstring']) self.assertTrue(msg in data['faultstring'])
def test_list_command_results(self): def test_list_command_results(self):
self.maxDiff = 10000
cmd_result = base.SyncCommandResult(u'do_things', cmd_result = base.SyncCommandResult(u'do_things',
{u'key': u'value'}, {u'key': u'value'},
True, True,
{u'test': u'result'}) {u'test': u'result'})
mock_agent = mock.create_autospec(agent.TeethAgent) self.mock_agent.list_command_results.return_value = [
mock_agent.list_command_results.return_value = [
cmd_result, cmd_result,
] ]
api_server = app.setup_app(mock_agent) response = self.get_json('/commands')
response = self._make_request(api_server, 'GET', '/v1/commands')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(json.loads(response.data), { self.assertEqual(response.json, {
u'commands': [ u'commands': [
cmd_result.serialize(), cmd_result.serialize(),
], ],
@ -158,16 +249,11 @@ class TestTeethAPI(unittest.TestCase):
{'key': 'value'}, {'key': 'value'},
True, True,
{'test': 'result'}) {'test': 'result'})
serialized_cmd_result = cmd_result.serialize() serialized_cmd_result = cmd_result.serialize()
mock_agent = mock.create_autospec(agent.TeethAgent) self.mock_agent.get_command_result.return_value = cmd_result
mock_agent.get_command_result.return_value = cmd_result
api_server = app.setup_app(mock_agent) response = self.get_json('/commands/abc123')
response = self._make_request(api_server,
'GET',
'/v1/commands/abc123')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
data = json.loads(response.data) data = response.json
self.assertEqual(data, serialized_cmd_result) self.assertEqual(data, serialized_cmd_result)