congress/congress/tests/api/test_webservice.py

329 lines
14 KiB
Python

# Copyright (c) 2014 VMware, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import http.client as httplib
import json
from unittest import mock
from oslo_utils import uuidutils
import webob
from congress.api import webservice
from congress.tests import base
class TestSimpleDataModel(base.TestCase):
# if random ID matches, go to Vegas or file a uuid library bug
UNADDED_ID = uuidutils.generate_uuid()
CONTEXTS = [None, {'a': 'ctxt1'}, {'b': 'ctxt2', 'c': 'ctxt3'}]
def test_get_items(self):
"""Test API DataModel get_items functionality."""
model = webservice.SimpleDataModel("test")
for context in self.CONTEXTS:
ret = model.get_items({}, context=context)
self.assertEqual(
ret.keys(), {'results': None}.keys(),
"get_items returns dict with single 'results' key")
self.assertEqual(
tuple(ret['results']), tuple(),
"get_items of empty model returns empty results list")
items = [{"i1": "%s/foo" % context}, {"i2": "%s/bar" % context}]
for item in items:
model.add_item(item, {}, context=context)
ret2 = model.get_items({}, context=context)
self.assertEqual(sorted(list(ret2['results']),
key=(lambda x: str(type(x)) + repr(x))),
sorted(items,
key=(lambda x: str(type(x)) + repr(x))),
"get_items() returns all items added to model")
def test_add_item(self):
"""Test API DataModel add_item functionality."""
model = webservice.SimpleDataModel("test")
assigned_ids = set()
for context in self.CONTEXTS:
items = ["%s/foo" % context, "%s/bar" % context]
ret = model.add_item(items[0], {}, context=context)
self.assertIsInstance(ret, tuple, "add_item returns a tuple")
self.assertEqual(len(ret), 2,
"add_item returns tuple of length 2")
self.assertNotIn(ret[0], assigned_ids,
"add_item assigned unique ID")
assigned_ids.add(ret[0])
self.assertEqual(ret[1], items[0], "add_item returned added item")
ret = model.add_item(items[1], {}, 'myid', context=context)
self.assertEqual(ret[0], 'myid',
"add_item returned provided ID")
self.assertEqual(ret[1], items[1], "add_item returned added item")
def test_get_item(self):
"""Test API DataModel get_item functionality."""
model = webservice.SimpleDataModel("test")
for context in self.CONTEXTS:
items = ["%s/foo" % context, "%s/bar" % context]
id_ = model.add_item(items[0], {}, context=context)[0]
ret = model.get_item(id_, {}, context=context)
self.assertEqual(ret, items[0],
"get_item(assigned_id) returns proper item")
id_ = 'myid'
ret = model.get_item(id_, {}, context=context)
self.assertIsNone(ret,
"get_item(unadded_provided_id) returns None")
model.add_item(items[1], {}, id_, context=context)
ret = model.get_item(id_, {}, context=context)
self.assertEqual(ret, items[1],
"get_item(provided_id) returned added item")
ret = model.get_item(self.UNADDED_ID, {}, context=context)
self.assertIsNone(ret, "get_item(unadded_id) returns None")
def test_update_item(self):
"""Test API DataModel update_item functionality."""
model = webservice.SimpleDataModel("test")
for context in self.CONTEXTS:
items = ["%s/foo%d" % (context, i) for i in [0, 1, 2]]
id_, item = model.add_item(items[0], {}, context=context)
self.assertNotEqual(item, items[1], "item not yet updated")
ret = model.update_item(id_, items[1], {}, context=context)
self.assertEqual(ret, items[1],
"update_item returned updated item")
ret = model.get_item(id_, {}, context=context)
self.assertEqual(ret, items[1],
"get_item(updated_item_id) returns updated item")
self.assertNotEqual(item, items[2], "item not yet reupdated")
ret = model.update_item(id_, items[2], {}, context=context)
self.assertEqual(ret, items[2],
"update_item returned reupdated item")
ret = model.get_item(id_, {}, context=context)
self.assertEqual(
ret, items[2],
"get_item(reupdated_item_id) returns reupdated item")
self.assertRaises(KeyError, model.update_item,
self.UNADDED_ID, 'blah', {}, context)
def test_delete_item(self):
"""Test API DataModel delete_item functionality."""
model = webservice.SimpleDataModel("test")
for context in self.CONTEXTS:
item_ids = []
items = ["%s/foo%d" % (context, i) for i in [0, 1, 2]]
for i in range(len(items)):
id_, item = model.add_item(items[i], {}, context=context)
item_ids.append(id_)
for i in range(len(items)):
ret = model.delete_item(item_ids[i], {}, context=context)
self.assertEqual(ret, items[i],
"delete_item returned deleted item")
self.assertRaises(KeyError, model.delete_item, item_ids[i],
{}, context)
self.assertEqual(
len(model.get_items({}, context=context)['results']), 0,
"all items deleted")
self.assertRaises(KeyError, model.delete_item, self.UNADDED_ID,
{}, context)
class TestAbstractHandler(base.TestCase):
def test_parse_json_body(self):
abstract_handler = webservice.AbstractApiHandler(r'/')
request = mock.MagicMock()
data = {"some": ["simple", "json"]}
serialized_data = json.dumps({"some": ["simple", "json"]})
invalid_json = 'this is not valid JSON'
# correctly assume application/json when no content-type header
request = webob.Request.blank('/')
self.assertEqual(request.content_type, '')
request.body = serialized_data.encode('utf-8')
ret = abstract_handler._parse_json_body(request)
self.assertEqual(ret, data)
# correctly validate valid content-type headers
for ct in ['application/json',
'Application/jSoN',
'application/json; charset=utf-8',
'apPLICAtion/JSOn; charset=UtF-8',
'apPLICAtion/JSOn; CHARset=utf-8; IGnored=c',
'application/json; ignored_param=a; ignored2=b']:
request = webob.Request.blank('/', content_type=ct)
request.body = serialized_data.encode('utf-8')
try:
ret = abstract_handler._parse_json_body(request)
except Exception:
self.fail("accepts content type '%s'" % ct)
self.assertEqual(ret, data, "Accepts content type '%s'" % ct)
# correctly fail on invalid content-type headers
request = webob.Request.blank('/', content_type='text/json')
request.body = serialized_data.encode('utf-8')
self.assertRaises(webservice.DataModelException,
abstract_handler._parse_json_body, request)
# enforce unspecified or utf-8 charset
# valid charset checked above, just need to check invalid
request = webob.Request.blank(
'/', content_type='application/json; charset=utf-16')
request.body = serialized_data.encode('utf-8')
self.assertRaises(webservice.DataModelException,
abstract_handler._parse_json_body, request)
# raise DataModelException on non-JSON body
request = webob.Request.blank(
'/', content_type='application/json; charset=utf-8')
request.body = invalid_json.encode('utf-8')
self.assertRaises(webservice.DataModelException,
abstract_handler._parse_json_body, request)
class TestElementHandler(base.TestCase):
def test_read(self):
# TODO(pballand): write tests
pass
def test_action(self):
element_handler = webservice.ElementHandler(r'/', '')
element_handler.model = webservice.SimpleDataModel("test")
request = mock.MagicMock()
request.path = "/"
response = element_handler.action(request)
self.assertEqual(400, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertEqual(json.loads(
response.body.decode('utf-8'))['error']['message'],
"Missing required action parameter.")
request.params = mock.MagicMock()
request.params.getall.return_value = ['do_test']
request.params["action"] = "do_test"
request.path = "/"
response = element_handler.action(request)
self.assertEqual(501, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertEqual(json.loads(
response.body.decode('utf-8'))['error']['message'],
"Method not supported")
# test action impl returning python primitives
simple_data = [1, 2]
element_handler.model.do_test_action = lambda *a, **kwa: simple_data
response = element_handler.action(request)
self.assertEqual(200, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertEqual(json.loads(response.body.decode('utf-8')),
simple_data)
# test action impl returning custom webob response
custom_data = webob.Response(body="test".encode('utf-8'), status=599,
content_type="custom/test")
element_handler.model.do_test_action = lambda *a, **kwa: custom_data
response = element_handler.action(request)
self.assertEqual(599, response.status_code)
self.assertEqual('custom/test', response.content_type)
self.assertEqual(response.body.decode('utf-8'), "test")
def test_replace(self):
# TODO(pballand): write tests
pass
def test_update(self):
# TODO(pballand): write tests
pass
def test_delete(self):
# TODO(pballand): write tests
pass
class TestCollectionHandler(base.TestCase):
def test_get_action_type(self):
collection_handler = webservice.CollectionHandler(r'/', '')
self.assertEqual('get',
collection_handler._get_action_type("GET"))
self.assertEqual('create',
collection_handler._get_action_type("POST"))
self.assertEqual('delete',
collection_handler._get_action_type("DELETE"))
self.assertEqual('update',
collection_handler._get_action_type("PATCH"))
self.assertEqual('update',
collection_handler._get_action_type("PUT"))
self.assertRaises(TypeError, collection_handler._get_action_type,
'Wah!')
def test_create_member(self):
collection_handler = webservice.CollectionHandler(r'/', '')
collection_handler.model = webservice.SimpleDataModel("test")
request = webob.Request.blank('/')
request.content_type = 'application/json'
request.body = '{"key": "value"}'.encode('utf-8')
response = collection_handler.create_member(request, id_='123')
self.assertEqual('application/json', response.content_type)
self.assertEqual(
str(int(httplib.CREATED)) + " Created", response.status)
self.assertEqual("%s/%s" % (request.path, '123'), response.location)
actual_response = json.loads(response.body.decode('utf-8'))
actual_id = actual_response.get("id")
actual_value = actual_response.get("key")
self.assertEqual('123', actual_id)
self.assertEqual('value', actual_value)
def test_list_members(self):
collection_handler = webservice.CollectionHandler(r'/', '')
collection_handler.model = webservice.SimpleDataModel("test")
request = mock.MagicMock()
request.body = '{"key": "value"}'
request.params = mock.MagicMock()
request.path = "/"
response = collection_handler.list_members(request)
items = collection_handler.model.get_items(
request.params,
context=collection_handler._get_context(request))
expected_body = ("%s\n" % json.dumps(items, indent=2)).encode('utf-8')
self.assertEqual('application/json', response.content_type)
self.assertEqual(expected_body, response.body)
self.assertEqual('application/json', response.content_type)
self.assertEqual(str(int(httplib.OK)) + " OK", response.status)
def test_replace_members(self):
collection_handler = webservice.CollectionHandler(r'/', '')
collection_handler.model = webservice.SimpleDataModel('test')
request = webob.Request.blank('/')
request.content_type = 'application/json'
request.body = '{"key1": "value1", "key2": "value2"}'.encode('utf-8')
response = collection_handler.replace_members(request)
self.assertEqual('application/json', response.content_type)
self.assertEqual(str(int(httplib.OK)) + " OK", response.status)
expected_items = {
"key1": "value1",
"key2": "value2",
}
self.assertEqual(expected_items, collection_handler.model.items)