# 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)