예제 #1
0
    def init(self):
        '''This method setup all test cases common dependencies.'''

        from fantastico.contrib.roa_discovery.roa_controller import RoaController

        self._settings_facade = Mock()
        self._resources_registry = Mock()
        self._model_facade = Mock()
        self._conn_manager = Mock()
        self._json_serializer = Mock()
        self._query_parser = Mock()
        self._doc_base = "https://fantastico/html/"

        resources_registry_cls = Mock(return_value=self._resources_registry)
        model_facade_cls = Mock(return_value=self._model_facade)
        self._json_serializer_cls = Mock(return_value=self._json_serializer)

        self._settings_facade.get = self._mock_settings_get

        self._query_parser_cls = Mock(return_value=self._query_parser)

        self._controller = RoaController(
            settings_facade=self._settings_facade,
            resources_registry_cls=resources_registry_cls,
            model_facade_cls=model_facade_cls,
            conn_manager=self._conn_manager,
            json_serializer_cls=self._json_serializer_cls,
            query_parser_cls=self._query_parser_cls)
예제 #2
0
    def init(self):
        '''This method setup all test cases common dependencies.'''

        from fantastico.contrib.roa_discovery.roa_controller import RoaController

        self._settings_facade = Mock()
        self._resources_registry = Mock()
        self._model_facade = Mock()
        self._conn_manager = Mock()
        self._json_serializer = Mock()
        self._query_parser = Mock()
        self._doc_base = "https://fantastico/html/"

        resources_registry_cls = Mock(return_value=self._resources_registry)
        model_facade_cls = Mock(return_value=self._model_facade)
        self._json_serializer_cls = Mock(return_value=self._json_serializer)

        self._settings_facade.get = self._mock_settings_get

        self._query_parser_cls = Mock(return_value=self._query_parser)

        self._controller = RoaController(settings_facade=self._settings_facade,
                                         resources_registry_cls=resources_registry_cls,
                                         model_facade_cls=model_facade_cls,
                                         conn_manager=self._conn_manager,
                                         json_serializer_cls=self._json_serializer_cls,
                                         query_parser_cls=self._query_parser_cls)
예제 #3
0
class RoaControllerTests(FantasticoUnitTestsCase):
    '''This class provides the test cases for roa controller.'''

    _settings_facade = None
    _resources_registry = None
    _model_facade = None
    _conn_manager = None
    _json_serializer_cls = None
    _json_serializer = None
    _query_parser_cls = None
    _query_parser = None
    _controller = None
    _doc_base = None

    def init(self):
        '''This method setup all test cases common dependencies.'''

        from fantastico.contrib.roa_discovery.roa_controller import RoaController

        self._settings_facade = Mock()
        self._resources_registry = Mock()
        self._model_facade = Mock()
        self._conn_manager = Mock()
        self._json_serializer = Mock()
        self._query_parser = Mock()
        self._doc_base = "https://fantastico/html/"

        resources_registry_cls = Mock(return_value=self._resources_registry)
        model_facade_cls = Mock(return_value=self._model_facade)
        self._json_serializer_cls = Mock(return_value=self._json_serializer)

        self._settings_facade.get = self._mock_settings_get

        self._query_parser_cls = Mock(return_value=self._query_parser)

        self._controller = RoaController(
            settings_facade=self._settings_facade,
            resources_registry_cls=resources_registry_cls,
            model_facade_cls=model_facade_cls,
            conn_manager=self._conn_manager,
            json_serializer_cls=self._json_serializer_cls,
            query_parser_cls=self._query_parser_cls)

    def _mock_settings_get(self, setting_name):
        if setting_name == "doc_base":
            return self._doc_base

        if setting_name == "roa_api":
            return "/api"

        raise Exception("Unexpected setting %s." % setting_name)

    def _mock_model_facade(self, records, records_count):
        '''This method mocks the current model facade object in order to return the specified values.'''

        self._model_facade.get_records_paged = Mock(return_value=records)
        self._model_facade.count_records = Mock(return_value=records_count)

    def _assert_get_collection_response(self,
                                        request,
                                        response,
                                        records,
                                        records_count,
                                        offset,
                                        limit,
                                        expected_filter=None,
                                        expected_sort=None):
        '''This test case assert the given response against expected values.'''

        self.assertIsNotNone(response)
        self.assertEqual(200, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)

        self.assertIsNotNone(response.body)

        body = json.loads(response.body.decode())

        self.assertEqual(len(records), len(body["items"]))
        self.assertEqual(records_count, body["totalItems"])

        self._model_facade.get_records_paged.assert_called_once_with(
            start_record=offset,
            end_record=limit,
            filter_expr=expected_filter,
            sort_expr=expected_sort)
        self._model_facade.count_records.assert_called_once_with(
            filter_expr=expected_filter)
        self._controller.validate_security_context.assert_called_once_with(
            request, "read")

    def _assert_cors_headers(self, response):
        '''This method checks response for cors headers.'''

        self.assertEqual("*", response.headers["Access-Control-Allow-Origin"])
        self.assertEqual("OPTIONS,GET,POST,PUT,DELETE",
                         response.headers["Access-Control-Allow-Methods"])

    def test_get_collection_default_values_emptyresult(self):
        '''This test case ensures get collection works as expected without any query parameters passed. It ensures
        empty items returns correct '''

        self._controller.validate_security_context = Mock(return_value=None)

        expected_records = []
        expected_records_count = 0

        version = "1.0"
        resource_url = "/sample-resources"

        request = Mock()
        request.params = {}

        resource = Mock()
        resource.user_dependent = False
        resource.model = Mock()

        self._mock_model_facade(records=expected_records,
                                records_count=expected_records_count)

        self._resources_registry.find_by_url = Mock(return_value=resource)

        response = self._controller.get_collection(request, version,
                                                   resource_url)

        self._assert_get_collection_response(
            request,
            response,
            records=expected_records,
            records_count=expected_records_count,
            offset=self._controller.OFFSET_DEFAULT,
            limit=self._controller.LIMIT_DEFAULT)

        self._resources_registry.find_by_url.assert_called_once_with(
            resource_url, float(version))
        self._json_serializer_cls.assert_called_once_with(resource)

    def test_get_collection_first_page(self):
        '''This test case ensures get collection can return first page populated with items. In addition it ensures
        filtering and sorting is supported.'''

        self._controller.validate_security_context = Mock(return_value=None)

        expected_fields = "name,description"
        expected_records = [{
            "name": "Resource 1",
            "description": ""
        }, {
            "name": "Resource 2",
            "description": ""
        }]
        expected_records_count = 3
        expected_filter = Mock()
        expected_sort = Mock()

        version = "latest"
        resource_url = "/sample-resources"

        request = Mock()
        request.params = {
            "offset": "0",
            "limit": "2",
            "filter": "like(name, \"resource 1\")",
            "order": "asc(name)",
            "fields": expected_fields
        }

        resource = Mock()
        resource.user_dependent = False
        resource.model = Mock()

        self._query_parser.parse_filter = Mock(return_value=expected_filter)
        self._query_parser.parse_sort = Mock(return_value=expected_sort)

        self._mock_model_facade(records=expected_records,
                                records_count=expected_records_count)
        self._model_facade.get_records_paged = Mock(
            return_value=expected_records)
        self._model_facade.count_records = Mock(
            return_value=expected_records_count)

        self._resources_registry.find_by_url = Mock(return_value=resource)

        def mock_serialize(model, fields):
            self.assertEqual(expected_fields, fields)

            return model

        self._json_serializer.serialize = mock_serialize

        response = self._controller.get_collection(request, version,
                                                   resource_url)

        self._assert_get_collection_response(
            request,
            response,
            records=expected_records,
            records_count=expected_records_count,
            offset=0,
            limit=2,
            expected_filter=expected_filter,
            expected_sort=expected_sort)

        self._resources_registry.find_by_url.assert_called_once_with(
            resource_url, version)
        self._json_serializer_cls.assert_called_once_with(resource)
        self._query_parser.parse_filter.assert_called_once_with(
            request.params["filter"], resource.model)
        self._query_parser.parse_sort.assert_called_once_with(
            [request.params["order"]], resource.model)

    def _assert_resource_error(self, response, http_code, error_code, version,
                               url):
        '''This method asserts a given error response against expected resource error format.'''

        self.assertIsNotNone(response)
        self.assertEqual(http_code, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)

        self.assertIsNotNone(response.body)

        body = json.loads(response.body.decode())

        self.assertEqual(error_code, body["error_code"])
        self.assertTrue(body["error_description"].find(version) > -1)
        self.assertTrue(body["error_description"].find(url) > -1)
        self.assertEqual(
            "%sfeatures/roa/errors/error_%s.html" %
            (self._doc_base, error_code), body["error_details"])

    def test_get_collection_resource_notfound(self):
        '''This test case ensures 404 is returned if we try to access a resource which does not exist.'''

        url = "/resource-not-found"
        version = "1.0"

        request = Mock()
        request.params = {}

        self._settings_facade.get = Mock(return_value=self._doc_base)

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.get_collection(request, version, url)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))

    def _test_roa_cors_template(self, url):
        '''This method provides a template for testing ROA cors support.'''

        request = Mock()
        request.headers = {"Access-Control-Request-Headers": "header1,header2"}

        response = self._controller.handle_resource_options_latest(
            request, url)

        self.assertIsNotNone(response)
        self.assertEqual(200, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self.assertEqual(0, response.content_length)

        self.assertEqual("private", response.headers["Cache-Control"])
        self.assertEqual("*", response.headers["Access-Control-Allow-Origin"])
        self.assertEqual("OPTIONS,GET,POST,PUT,DELETE",
                         response.headers["Access-Control-Allow-Methods"])
        self.assertEqual(request.headers["Access-Control-Request-Headers"],
                         response.headers["Access-Control-Allow-Headers"])

        self.assertEqual(0, len(response.body))

    def test_roa_cors_support(self):
        '''This test case ensures CORS is enabled on all ROA dynamic generated apis.'''

        url = "/simple-resource"

        self._test_roa_cors_template(url)

    def test_roa_cors_support_resourcenotfound(self):
        '''This test case ensures no error is returned if an options http request is done for a resource which is not
        registered.'''

        url = "/resource-not-found/213"

        self._test_roa_cors_template(url)

    def test_create_item_noresourcefound(self):
        '''This test case ensures we can not add items to an inexistent resource.'''

        url = "/sample-resource"
        version = "1.0"

        request = Mock()

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.create_item(request, version, url)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))

    def test_create_item_empty_body(self):
        '''This test case ensures an item can not be created if no body is given.'''

        url = "/sample-resources"
        version = "1.0"

        resource = Mock()
        resource.url = url
        resource.version = version

        request = Mock()
        request.body = None

        self._resources_registry.find_by_url = Mock(return_value=resource)

        response = self._controller.create_item(request, version, url)

        self._assert_resource_error(response, 400, 10020, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))

    def test_create_item_invalidresource(self):
        '''This test case ensures an item can not be created if it's invalid.'''

        resource = Resource(name="Mock Simple Resource",
                            url="/mock-simple-resources",
                            version=1.0,
                            validator=MockSimpleResourceValidator)
        resource(MockSimpleResourceRoa, self._resources_registry)

        request_body = {"description": "Simple resource description."}

        request = Mock()
        request.body = json.dumps(request_body).encode()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(
            return_value=MockSimpleResourceRoa(
                description=request_body.get("description")))

        response = self._controller.create_item(request,
                                                version=str(resource.version),
                                                resource_url=resource.url)

        self._assert_resource_error(response, 400, 10010,
                                    str(resource.version), resource.url)

        self._resources_registry.find_by_url.assert_called_once_with(
            resource.url, resource.version)
        self._json_serializer.deserialize.assert_called_once_with(
            request.body.decode())

    def test_create_item_ok(self):
        '''This test case ensures a valid resource can be created correctly.'''

        self._controller.validate_security_context = Mock(return_value=None)

        resource = Resource(name="Mock Simple Resource",
                            url="/mock-simple-resources",
                            version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        request_body = {
            "name": "simple-resource1",
            "description": "Simple resource description."
        }

        expected_model = MockSimpleResourceRoa(
            name=request_body.get("name"),
            description=request_body.get("description"))

        expected_id = 123

        request = Mock()
        request.body = json.dumps(request_body).encode()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=expected_model)
        self._model_facade.create = Mock(return_value=[expected_id])

        response = self._controller.create_item(request, str(resource.version),
                                                resource.url)

        self.assertIsNotNone(resource)
        self.assertEqual(201, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)
        self.assertEqual("0", response.headers["Content-Length"])
        self.assertEqual(
            "/api/%s%s/%s" % (resource.version, resource.url, expected_id),
            response.headers["Location"])

        self._resources_registry.find_by_url.assert_called_once_with(
            resource.url, resource.version)
        self._json_serializer.deserialize.assert_called_once_with(
            request.body.decode())
        self._model_facade.create.assert_called_once_with(expected_model)

        self._controller.validate_security_context.assert_called_once_with(
            request, "create")

    def test_create_item_dbexception(self):
        '''This test case ensures an error response is received if an unexpected db error occurs when creating the resource.'''

        resource = Resource(name="Mock Simple Resource",
                            url="/mock-simple-resources",
                            version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        request_body = {
            "name": "simple-resource1",
            "description": "Simple resource description."
        }

        expected_model = MockSimpleResourceRoa(
            name=request_body.get("name"),
            description=request_body.get("description"))

        request = Mock()
        request.body = json.dumps(request_body).encode()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=expected_model)
        self._model_facade.create = Mock(
            side_effect=FantasticoDbError("Unexpected db error."))

        response = self._controller.create_item_latest(request, resource.url)

        self._assert_resource_error(response, 400, 10030,
                                    str(resource.version), resource.url)

        self._resources_registry.find_by_url.assert_called_once_with(
            resource.url, "latest")
        self._json_serializer.deserialize.assert_called_once_with(
            request.body.decode())
        self._model_facade.create.assert_called_once_with(expected_model)

    def test_get_item_resource_notsupported(self):
        '''This test case covers the scenario for retrieving an item from a resource collection which is not supported.'''

        url = "/simple-settings"
        version = "latest"
        resource_id = 1986

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.get_item(Mock(), version, url, resource_id)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, version)

    def test_get_item_inexistent_resource(self):
        '''This test case covers the scenario for retrieving an inexisten item from an existing collection.'''

        url = "/simple-resources"
        version = "1.0"

        resource = Resource(name="Mock Simple Resource",
                            url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        resource_id = 1986

        self._resources_registry.find_by_url = Mock(return_value=resource)

        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(return_value=None)

        response = self._controller.get_item(Mock(), version, url, resource_id)

        self._assert_resource_error(response, 404, 10040, version, url)

        self.assertTrue(
            resource_id,
            json.loads(response.body.decode())["error_description"])

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with(
            {MockSimpleResourceRoa.id: resource_id})

    def test_get_item_ok(self):
        '''This test case ensures an item can be correctly retrieved from collection.'''

        self._controller.validate_security_context = Mock(return_value=None)

        url = "/simple-resources"
        version = "1.0"
        fields = "id,name,description"

        model = Mock()

        resource = Resource(name="Mock Simple Resource",
                            url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        request = Mock()
        request.params = {"fields": fields}

        resource_id = 1986

        expected_body = {
            "id": resource_id,
            "name": "Test resource",
            "description": "Simple description."
        }

        self._resources_registry.find_by_url = Mock(return_value=resource)

        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(return_value=model)

        self._json_serializer.serialize = Mock(return_value=expected_body)

        response = self._controller.get_item(request, version, url,
                                             resource_id)

        self.assertIsNotNone(response)
        self.assertEqual(200, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)

        self.assertIsNotNone(response.body)
        body = json.loads(response.body.decode())

        self.assertEqual(expected_body, body)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with(
            {MockSimpleResourceRoa.id: resource_id})
        self._json_serializer_cls.assert_called_once_with(resource)
        self._json_serializer.serialize(model, fields)
        self._controller.validate_security_context.assert_called_once_with(
            request, "read")

    def test_get_item_unexpected_dbex(self):
        '''This test case ensures an exception response is received whenever a database exception occurs.'''

        url = "/simple-resources"
        version = "1.0"

        resource = Resource(name="Mock Simple Resource",
                            url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        resource_id = 1986

        self._resources_registry.find_by_url = Mock(return_value=resource)

        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(
            side_effect=FantasticoDbError("Unexpected db error."))

        response = self._controller.get_item(Mock(), version, url, resource_id)

        self._assert_resource_error(response, 400, 10030, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with(
            {MockSimpleResourceRoa.id: resource_id})

    def test_update_item_resource_unknown(self):
        '''This test case ensures an item can not be updated if the resource collection specified is not found.'''

        url = "/simple-resources"
        version = "1.0"
        resource_id = 1986

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.update_item(Mock(), version, url,
                                                resource_id)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))

    def test_update_item_resource_invalid(self):
        '''This test case ensures an item is not updated if it fails validation.'''

        expected_body = {}

        url = "/simple-resources"
        version = "1.0"

        resource = Resource(name="Mock Simple Resource",
                            url=url,
                            version=float(version),
                            validator=MockSimpleResourceValidator)
        resource(MockSimpleResourceRoa, self._resources_registry)

        resource_id = 1986

        request = Mock()
        request.body = json.dumps(expected_body).encode()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(
            return_value=MockSimpleResourceRoa())

        response = self._controller.update_item(request, version, url,
                                                resource_id)

        self._assert_resource_error(response, 400, 10010, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))
        self._json_serializer.deserialize.assert_called_once_with(
            json.dumps(expected_body))

    def test_update_item_nobody(self):
        '''This test case ensures an item can not be updated without passing a body.'''

        url = "/simple-resources"
        version = "1.0"

        resource = Resource(name="Mock Simple Resource",
                            url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        resource_id = 1986

        request = Mock()
        request.body = None

        self._resources_registry.find_by_url = Mock(return_value=resource)

        response = self._controller.update_item(request, version, url,
                                                resource_id)

        self._assert_resource_error(response, 400, 10020, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))

    def test_update_item_dbex(self):
        '''This test case covers scenario when an item can not be updated because of a db exception.'''

        expected_body = {
            "name": "cool name",
            "description": "incredible simple description"
        }
        url = "/simple-resources"
        version = "1.0"
        resource_id = "12345"

        request = Mock()
        request.body = json.dumps(expected_body).encode()

        resource = Resource(name="Mock Simple Resource",
                            url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        pk_col = Mock()
        pk_col.name = "id"

        model = Mock()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=model)
        self._model_facade.update = Mock(
            side_effect=FantasticoDbError("Unexpected exception"))
        self._model_facade.model_pk_cols = [pk_col]

        response = self._controller.update_item(request, version, url,
                                                resource_id)

        self._assert_resource_error(response, 400, 10030, version, url)

        self.assertEqual(resource_id, model.id)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))
        self._json_serializer_cls.assert_called_once_with(resource)
        self._json_serializer.deserialize.assert_called_once_with(
            json.dumps(expected_body))

    def test_update_item_itemnotfound(self):
        '''This test case covers scenario when we want to update an item which does not exist.'''

        expected_body = {
            "name": "cool name",
            "description": "incredible simple description"
        }
        url = "/simple-resources"
        version = "1.0"
        resource_id = "12345"

        request = Mock()
        request.body = json.dumps(expected_body).encode()

        resource = Resource(name="Mock Simple Resource",
                            url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        pk_col = MockSimpleResourceRoa.id

        model = Mock()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=model)
        self._model_facade.find_by_pk = Mock(return_value=None)
        self._model_facade.model_pk_cols = [pk_col]

        response = self._controller.update_item(request, version, url,
                                                resource_id)

        self._assert_resource_error(response, 404, 10040, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with(
            {MockSimpleResourceRoa.id: resource_id})
        self._json_serializer_cls.assert_called_once_with(resource)
        self._json_serializer.deserialize.assert_called_once_with(
            json.dumps(expected_body))

    def test_update_item_ok(self):
        '''This test case covers scenario when an item can be updated successfully.'''

        self._controller.validate_security_context = Mock(return_value=None)

        expected_body = {
            "name": "cool name",
            "description": "incredible simple description"
        }
        url = "/simple-resources"
        version = "latest"
        resource_id = "12345"

        request = Mock()
        request.body = json.dumps(expected_body).encode()

        resource = Resource(name="Mock Simple Resource", url=url, version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        pk_col = MockSimpleResourceRoa.id

        model = Mock()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=model)
        self._model_facade.find_by_pk = Mock(return_value=model)
        self._model_facade.update = Mock(return_value=None)
        self._model_facade.model_pk_cols = [pk_col]

        response = self._controller.update_item_latest(request, url,
                                                       resource_id)

        self.assertIsNotNone(response)
        self.assertEqual(204, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)
        self.assertEqual("0", response.headers["Content-Length"])

        self.assertEqual(0, len(response.body))

        self.assertEqual(resource_id, model.id)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, version)
        self._model_facade.find_by_pk.assert_called_once_with(
            {MockSimpleResourceRoa.id: resource_id})
        self._json_serializer_cls.assert_called_once_with(resource)
        self._json_serializer.deserialize.assert_called_once_with(
            json.dumps(expected_body))
        self._model_facade.update.assert_called_once_with(model)
        self._controller.validate_security_context.assert_called_once_with(
            request, "update")

    def test_delete_item_resource_unknown(self):
        '''This test case ensures an existing item can not be deleted from an unknown collection.'''

        url = "/simple-resources"
        version = "latest"
        resource_id = "12345"

        request = Mock()

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.delete_item_latest(request, url,
                                                       resource_id)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, version)

    def test_delete_item_dbex(self):
        '''This test case ensures a delete operation which fails because of a database exception raises a concrete exception.'''

        url = "/simple-resources"
        version = "latest"
        resource_id = "12345"

        request = Mock()

        resource = Resource(name="Mock Simple Resource", url=url, version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(
            side_effect=FantasticoDbError("Unexpected db exception"))

        response = self._controller.delete_item_latest(request, url,
                                                       resource_id)

        self._assert_resource_error(response, 400, 10030, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, version)
        self._model_facade.find_by_pk.assert_called_once_with(
            {MockSimpleResourceRoa.id: resource_id})

    def test_delete_item_itemnotfound(self):
        '''This test case ensures an item which does not belong to a collection returns a concrete exception response.'''

        url = "/simple-resources"
        version = "latest"
        resource_id = "12345"

        request = Mock()

        resource = Resource(name="Mock Simple Resource", url=url, version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(return_value=None)

        response = self._controller.delete_item_latest(request, url,
                                                       resource_id)

        self._assert_resource_error(response, 404, 10040, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(
            url, version)
        self._model_facade.find_by_pk.assert_called_once_with(
            {MockSimpleResourceRoa.id: resource_id})

    def test_delete_item_ok(self):
        '''This test case ensures an existing item can be deleted successfully.'''

        self._controller.validate_security_context = Mock(return_value=None)

        url = "/simple-resources"
        version = "1.0"
        resource_id = "12345"

        request = Mock()

        resource = Resource(name="Mock Simple Resource", url=url, version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        model = Mock()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(return_value=model)

        response = self._controller.delete_item(request, version, url,
                                                resource_id)

        self.assertIsNotNone(response)
        self.assertEqual(204, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)
        self.assertEqual("0", response.headers["Content-Length"])

        self.assertEqual(0, len(response.body))

        self._resources_registry.find_by_url.assert_called_once_with(
            url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with(
            {MockSimpleResourceRoa.id: resource_id})
        self._model_facade.delete.assert_called_once_with(model)
        self._controller.validate_security_context.assert_called_once_with(
            request, "delete")

    def test_validate_security_context_ok(self):
        '''This test case ensures that security context validation works as expected.'''

        self._test_validate_security_context_template(valid=True)

    def test_validate_security_context_invalid(self):
        '''This test case ensures that security context validation raises an unauthorized exception if context is not valid.'''

        with self.assertRaises(OAuth2UnauthorizedError):
            self._test_validate_security_context_template(valid=False)

    def test_validate_security_context_oauth2ex(self):
        '''This test case ensures that security context validation oauth2 errors are bubbled up.'''

        ex = OAuth2Error(error_code=-1)

        with self.assertRaises(OAuth2Error) as ctx:
            self._test_validate_security_context_template(side_effect=ex)

        self.assertEqual(ex, ctx.exception)

    def test_validate_security_context_ex(self):
        '''This test case ensures security context validation unexpected exceptions are converted to oauth2
        unauthorized errors.'''

        ex = Exception("Unexpected error.")

        with self.assertRaises(OAuth2Error):
            self._test_validate_security_context_template(side_effect=ex)

    def _test_validate_security_context_template(self,
                                                 valid=None,
                                                 side_effect=None):
        '''This method provides a template for checking various behaviors of validate_security_context method.'''

        access_token = Token({})

        kwargs = {}

        if side_effect:
            kwargs["side_effect"] = side_effect
        else:
            kwargs["return_value"] = valid

        request = Mock()
        request.context = Mock()
        request.context.security = Mock()
        request.context.security.access_token = access_token
        request.context.security.validate_context = Mock(**kwargs)

        self.assertEqual(
            access_token,
            self._controller.validate_security_context(request,
                                                       attr_scope="scopes"))

        request.context.security.validate_context.assert_called_once_with(
            "scopes")
예제 #4
0
class RoaControllerTests(FantasticoUnitTestsCase):
    '''This class provides the test cases for roa controller.'''

    _settings_facade = None
    _resources_registry = None
    _model_facade = None
    _conn_manager = None
    _json_serializer_cls = None
    _json_serializer = None
    _query_parser_cls = None
    _query_parser = None
    _controller = None
    _doc_base = None

    def init(self):
        '''This method setup all test cases common dependencies.'''

        from fantastico.contrib.roa_discovery.roa_controller import RoaController

        self._settings_facade = Mock()
        self._resources_registry = Mock()
        self._model_facade = Mock()
        self._conn_manager = Mock()
        self._json_serializer = Mock()
        self._query_parser = Mock()
        self._doc_base = "https://fantastico/html/"

        resources_registry_cls = Mock(return_value=self._resources_registry)
        model_facade_cls = Mock(return_value=self._model_facade)
        self._json_serializer_cls = Mock(return_value=self._json_serializer)

        self._settings_facade.get = self._mock_settings_get

        self._query_parser_cls = Mock(return_value=self._query_parser)

        self._controller = RoaController(settings_facade=self._settings_facade,
                                         resources_registry_cls=resources_registry_cls,
                                         model_facade_cls=model_facade_cls,
                                         conn_manager=self._conn_manager,
                                         json_serializer_cls=self._json_serializer_cls,
                                         query_parser_cls=self._query_parser_cls)

    def _mock_settings_get(self, setting_name):
        if setting_name == "doc_base":
            return self._doc_base

        if setting_name == "roa_api":
            return "/api"

        raise Exception("Unexpected setting %s." % setting_name)

    def _mock_model_facade(self, records, records_count):
        '''This method mocks the current model facade object in order to return the specified values.'''

        self._model_facade.get_records_paged = Mock(return_value=records)
        self._model_facade.count_records = Mock(return_value=records_count)

    def _assert_get_collection_response(self, request, response, records, records_count, offset, limit,
                                        expected_filter=None,
                                        expected_sort=None):
        '''This test case assert the given response against expected values.'''

        self.assertIsNotNone(response)
        self.assertEqual(200, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)

        self.assertIsNotNone(response.body)

        body = json.loads(response.body.decode())

        self.assertEqual(len(records), len(body["items"]))
        self.assertEqual(records_count, body["totalItems"])

        self._model_facade.get_records_paged.assert_called_once_with(start_record=offset, end_record=limit,
                                                                     filter_expr=expected_filter,
                                                                     sort_expr=expected_sort)
        self._model_facade.count_records.assert_called_once_with(filter_expr=expected_filter)
        self._controller.validate_security_context.assert_called_once_with(request, "read")

    def _assert_cors_headers(self, response):
        '''This method checks response for cors headers.'''

        self.assertEqual("*", response.headers["Access-Control-Allow-Origin"])
        self.assertEqual("OPTIONS,GET,POST,PUT,DELETE", response.headers["Access-Control-Allow-Methods"])

    def test_get_collection_default_values_emptyresult(self):
        '''This test case ensures get collection works as expected without any query parameters passed. It ensures
        empty items returns correct '''

        self._controller.validate_security_context = Mock(return_value=None)

        expected_records = []
        expected_records_count = 0

        version = "1.0"
        resource_url = "/sample-resources"

        request = Mock()
        request.params = {}

        resource = Mock()
        resource.user_dependent = False
        resource.model = Mock()

        self._mock_model_facade(records=expected_records, records_count=expected_records_count)

        self._resources_registry.find_by_url = Mock(return_value=resource)

        response = self._controller.get_collection(request, version, resource_url)

        self._assert_get_collection_response(request, response,
                                             records=expected_records,
                                             records_count=expected_records_count,
                                             offset=self._controller.OFFSET_DEFAULT,
                                             limit=self._controller.LIMIT_DEFAULT)

        self._resources_registry.find_by_url.assert_called_once_with(resource_url, float(version))
        self._json_serializer_cls.assert_called_once_with(resource)

    def test_get_collection_first_page(self):
        '''This test case ensures get collection can return first page populated with items. In addition it ensures
        filtering and sorting is supported.'''

        self._controller.validate_security_context = Mock(return_value=None)

        expected_fields = "name,description"
        expected_records = [{"name": "Resource 1", "description": ""},
                            {"name": "Resource 2", "description": ""}]
        expected_records_count = 3
        expected_filter = Mock()
        expected_sort = Mock()

        version = "latest"
        resource_url = "/sample-resources"

        request = Mock()
        request.params = {"offset": "0", "limit": "2",
                          "filter": "like(name, \"resource 1\")",
                          "order": "asc(name)",
                          "fields": expected_fields}

        resource = Mock()
        resource.user_dependent = False
        resource.model = Mock()

        self._query_parser.parse_filter = Mock(return_value=expected_filter)
        self._query_parser.parse_sort = Mock(return_value=expected_sort)

        self._mock_model_facade(records=expected_records, records_count=expected_records_count)
        self._model_facade.get_records_paged = Mock(return_value=expected_records)
        self._model_facade.count_records = Mock(return_value=expected_records_count)

        self._resources_registry.find_by_url = Mock(return_value=resource)

        def mock_serialize(model, fields):
            self.assertEqual(expected_fields, fields)

            return model

        self._json_serializer.serialize = mock_serialize

        response = self._controller.get_collection(request, version, resource_url)

        self._assert_get_collection_response(request, response,
                                             records=expected_records,
                                             records_count=expected_records_count,
                                             offset=0,
                                             limit=2,
                                             expected_filter=expected_filter,
                                             expected_sort=expected_sort)

        self._resources_registry.find_by_url.assert_called_once_with(resource_url, version)
        self._json_serializer_cls.assert_called_once_with(resource)
        self._query_parser.parse_filter.assert_called_once_with(request.params["filter"], resource.model)
        self._query_parser.parse_sort.assert_called_once_with([request.params["order"]], resource.model)

    def _assert_resource_error(self, response, http_code, error_code, version, url):
        '''This method asserts a given error response against expected resource error format.'''

        self.assertIsNotNone(response)
        self.assertEqual(http_code, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)

        self.assertIsNotNone(response.body)

        body = json.loads(response.body.decode())

        self.assertEqual(error_code, body["error_code"])
        self.assertTrue(body["error_description"].find(version) > -1)
        self.assertTrue(body["error_description"].find(url) > -1)
        self.assertEqual("%sfeatures/roa/errors/error_%s.html" % (self._doc_base, error_code), body["error_details"])

    def test_get_collection_resource_notfound(self):
        '''This test case ensures 404 is returned if we try to access a resource which does not exist.'''

        url = "/resource-not-found"
        version = "1.0"

        request = Mock()
        request.params = {}

        self._settings_facade.get = Mock(return_value=self._doc_base)

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.get_collection(request, version, url)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))

    def _test_roa_cors_template(self, url):
        '''This method provides a template for testing ROA cors support.'''

        request = Mock()
        request.headers = {"Access-Control-Request-Headers": "header1,header2"}

        response = self._controller.handle_resource_options_latest(request, url)

        self.assertIsNotNone(response)
        self.assertEqual(200, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self.assertEqual(0, response.content_length)

        self.assertEqual("private", response.headers["Cache-Control"])
        self.assertEqual("*", response.headers["Access-Control-Allow-Origin"])
        self.assertEqual("OPTIONS,GET,POST,PUT,DELETE", response.headers["Access-Control-Allow-Methods"])
        self.assertEqual(request.headers["Access-Control-Request-Headers"], response.headers["Access-Control-Allow-Headers"])

        self.assertEqual(0, len(response.body))

    def test_roa_cors_support(self):
        '''This test case ensures CORS is enabled on all ROA dynamic generated apis.'''

        url = "/simple-resource"

        self._test_roa_cors_template(url)

    def test_roa_cors_support_resourcenotfound(self):
        '''This test case ensures no error is returned if an options http request is done for a resource which is not
        registered.'''

        url = "/resource-not-found/213"

        self._test_roa_cors_template(url)

    def test_create_item_noresourcefound(self):
        '''This test case ensures we can not add items to an inexistent resource.'''

        url = "/sample-resource"
        version = "1.0"

        request = Mock()

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.create_item(request, version, url)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))

    def test_create_item_empty_body(self):
        '''This test case ensures an item can not be created if no body is given.'''

        url = "/sample-resources"
        version = "1.0"

        resource = Mock()
        resource.url = url
        resource.version = version

        request = Mock()
        request.body = None

        self._resources_registry.find_by_url = Mock(return_value=resource)

        response = self._controller.create_item(request, version, url)

        self._assert_resource_error(response, 400, 10020, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))

    def test_create_item_invalidresource(self):
        '''This test case ensures an item can not be created if it's invalid.'''

        resource = Resource(name="Mock Simple Resource", url="/mock-simple-resources",
                            version=1.0,
                            validator=MockSimpleResourceValidator)
        resource(MockSimpleResourceRoa, self._resources_registry)

        request_body = {"description": "Simple resource description."}

        request = Mock()
        request.body = json.dumps(request_body).encode()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=MockSimpleResourceRoa(description=request_body.get("description")))

        response = self._controller.create_item(request, version=str(resource.version), resource_url=resource.url)

        self._assert_resource_error(response, 400, 10010, str(resource.version), resource.url)

        self._resources_registry.find_by_url.assert_called_once_with(resource.url, resource.version)
        self._json_serializer.deserialize.assert_called_once_with(request.body.decode())

    def test_create_item_ok(self):
        '''This test case ensures a valid resource can be created correctly.'''

        self._controller.validate_security_context = Mock(return_value=None)

        resource = Resource(name="Mock Simple Resource", url="/mock-simple-resources",
                            version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        request_body = {"name": "simple-resource1",
                        "description": "Simple resource description."}

        expected_model = MockSimpleResourceRoa(name=request_body.get("name"),
                                               description=request_body.get("description"))

        expected_id = 123

        request = Mock()
        request.body = json.dumps(request_body).encode()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=expected_model)
        self._model_facade.create = Mock(return_value=[expected_id])

        response = self._controller.create_item(request, str(resource.version), resource.url)

        self.assertIsNotNone(resource)
        self.assertEqual(201, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)
        self.assertEqual("0", response.headers["Content-Length"])
        self.assertEqual("/api/%s%s/%s" % (resource.version, resource.url, expected_id),
                         response.headers["Location"])

        self._resources_registry.find_by_url.assert_called_once_with(resource.url, resource.version)
        self._json_serializer.deserialize.assert_called_once_with(request.body.decode())
        self._model_facade.create.assert_called_once_with(expected_model)

        self._controller.validate_security_context.assert_called_once_with(request, "create")

    def test_create_item_dbexception(self):
        '''This test case ensures an error response is received if an unexpected db error occurs when creating the resource.'''

        resource = Resource(name="Mock Simple Resource", url="/mock-simple-resources",
                            version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        request_body = {"name": "simple-resource1",
                        "description": "Simple resource description."}

        expected_model = MockSimpleResourceRoa(name=request_body.get("name"),
                                               description=request_body.get("description"))

        request = Mock()
        request.body = json.dumps(request_body).encode()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=expected_model)
        self._model_facade.create = Mock(side_effect=FantasticoDbError("Unexpected db error."))

        response = self._controller.create_item_latest(request, resource.url)

        self._assert_resource_error(response, 400, 10030, str(resource.version), resource.url)

        self._resources_registry.find_by_url.assert_called_once_with(resource.url, "latest")
        self._json_serializer.deserialize.assert_called_once_with(request.body.decode())
        self._model_facade.create.assert_called_once_with(expected_model)

    def test_get_item_resource_notsupported(self):
        '''This test case covers the scenario for retrieving an item from a resource collection which is not supported.'''

        url = "/simple-settings"
        version = "latest"
        resource_id = 1986

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.get_item(Mock(), version, url, resource_id)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, version)

    def test_get_item_inexistent_resource(self):
        '''This test case covers the scenario for retrieving an inexisten item from an existing collection.'''

        url = "/simple-resources"
        version = "1.0"

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        resource_id = 1986

        self._resources_registry.find_by_url = Mock(return_value=resource)

        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(return_value=None)

        response = self._controller.get_item(Mock(), version, url, resource_id)

        self._assert_resource_error(response, 404, 10040, version, url)

        self.assertTrue(resource_id, json.loads(response.body.decode())["error_description"])

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with({MockSimpleResourceRoa.id: resource_id})

    def test_get_item_ok(self):
        '''This test case ensures an item can be correctly retrieved from collection.'''

        self._controller.validate_security_context = Mock(return_value=None)

        url = "/simple-resources"
        version = "1.0"
        fields = "id,name,description"

        model = Mock()

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        request = Mock()
        request.params = {"fields": fields}

        resource_id = 1986

        expected_body = {"id": resource_id,
                         "name": "Test resource",
                         "description": "Simple description."}

        self._resources_registry.find_by_url = Mock(return_value=resource)

        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(return_value=model)

        self._json_serializer.serialize = Mock(return_value=expected_body)

        response = self._controller.get_item(request, version, url, resource_id)

        self.assertIsNotNone(response)
        self.assertEqual(200, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)

        self.assertIsNotNone(response.body)
        body = json.loads(response.body.decode())

        self.assertEqual(expected_body, body)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with({MockSimpleResourceRoa.id: resource_id})
        self._json_serializer_cls.assert_called_once_with(resource)
        self._json_serializer.serialize(model, fields)
        self._controller.validate_security_context.assert_called_once_with(request, "read")

    def test_get_item_unexpected_dbex(self):
        '''This test case ensures an exception response is received whenever a database exception occurs.'''

        url = "/simple-resources"
        version = "1.0"

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        resource_id = 1986

        self._resources_registry.find_by_url = Mock(return_value=resource)

        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(side_effect=FantasticoDbError("Unexpected db error."))

        response = self._controller.get_item(Mock(), version, url, resource_id)

        self._assert_resource_error(response, 400, 10030, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with({MockSimpleResourceRoa.id: resource_id})

    def test_update_item_resource_unknown(self):
        '''This test case ensures an item can not be updated if the resource collection specified is not found.'''

        url = "/simple-resources"
        version = "1.0"
        resource_id = 1986

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.update_item(Mock(), version, url, resource_id)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))

    def test_update_item_resource_invalid(self):
        '''This test case ensures an item is not updated if it fails validation.'''

        expected_body = {}

        url = "/simple-resources"
        version = "1.0"

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=float(version),
                            validator=MockSimpleResourceValidator)
        resource(MockSimpleResourceRoa, self._resources_registry)

        resource_id = 1986

        request = Mock()
        request.body = json.dumps(expected_body).encode()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=MockSimpleResourceRoa())

        response = self._controller.update_item(request, version, url, resource_id)

        self._assert_resource_error(response, 400, 10010, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))
        self._json_serializer.deserialize.assert_called_once_with(json.dumps(expected_body))

    def test_update_item_nobody(self):
        '''This test case ensures an item can not be updated without passing a body.'''

        url = "/simple-resources"
        version = "1.0"

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        resource_id = 1986

        request = Mock()
        request.body = None

        self._resources_registry.find_by_url = Mock(return_value=resource)

        response = self._controller.update_item(request, version, url, resource_id)

        self._assert_resource_error(response, 400, 10020, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))

    def test_update_item_dbex(self):
        '''This test case covers scenario when an item can not be updated because of a db exception.'''

        expected_body = {"name": "cool name",
                         "description": "incredible simple description"}
        url = "/simple-resources"
        version = "1.0"
        resource_id = "12345"

        request = Mock()
        request.body = json.dumps(expected_body).encode()

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        pk_col = Mock()
        pk_col.name = "id"

        model = Mock()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=model)
        self._model_facade.update = Mock(side_effect=FantasticoDbError("Unexpected exception"))
        self._model_facade.model_pk_cols = [pk_col]

        response = self._controller.update_item(request, version, url, resource_id)

        self._assert_resource_error(response, 400, 10030, version, url)

        self.assertEqual(resource_id, model.id)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))
        self._json_serializer_cls.assert_called_once_with(resource)
        self._json_serializer.deserialize.assert_called_once_with(json.dumps(expected_body))

    def test_update_item_itemnotfound(self):
        '''This test case covers scenario when we want to update an item which does not exist.'''

        expected_body = {"name": "cool name",
                         "description": "incredible simple description"}
        url = "/simple-resources"
        version = "1.0"
        resource_id = "12345"

        request = Mock()
        request.body = json.dumps(expected_body).encode()

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=float(version))
        resource(MockSimpleResourceRoa, self._resources_registry)

        pk_col = MockSimpleResourceRoa.id

        model = Mock()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=model)
        self._model_facade.find_by_pk = Mock(return_value=None)
        self._model_facade.model_pk_cols = [pk_col]

        response = self._controller.update_item(request, version, url, resource_id)

        self._assert_resource_error(response, 404, 10040, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with({MockSimpleResourceRoa.id: resource_id})
        self._json_serializer_cls.assert_called_once_with(resource)
        self._json_serializer.deserialize.assert_called_once_with(json.dumps(expected_body))

    def test_update_item_ok(self):
        '''This test case covers scenario when an item can be updated successfully.'''

        self._controller.validate_security_context = Mock(return_value=None)

        expected_body = {"name": "cool name",
                         "description": "incredible simple description"}
        url = "/simple-resources"
        version = "latest"
        resource_id = "12345"

        request = Mock()
        request.body = json.dumps(expected_body).encode()

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        pk_col = MockSimpleResourceRoa.id

        model = Mock()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._json_serializer.deserialize = Mock(return_value=model)
        self._model_facade.find_by_pk = Mock(return_value=model)
        self._model_facade.update = Mock(return_value=None)
        self._model_facade.model_pk_cols = [pk_col]

        response = self._controller.update_item_latest(request, url, resource_id)

        self.assertIsNotNone(response)
        self.assertEqual(204, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)
        self.assertEqual("0", response.headers["Content-Length"])

        self.assertEqual(0, len(response.body))

        self.assertEqual(resource_id, model.id)

        self._resources_registry.find_by_url.assert_called_once_with(url, version)
        self._model_facade.find_by_pk.assert_called_once_with({MockSimpleResourceRoa.id: resource_id})
        self._json_serializer_cls.assert_called_once_with(resource)
        self._json_serializer.deserialize.assert_called_once_with(json.dumps(expected_body))
        self._model_facade.update.assert_called_once_with(model)
        self._controller.validate_security_context.assert_called_once_with(request, "update")

    def test_delete_item_resource_unknown(self):
        '''This test case ensures an existing item can not be deleted from an unknown collection.'''

        url = "/simple-resources"
        version = "latest"
        resource_id = "12345"

        request = Mock()

        self._resources_registry.find_by_url = Mock(return_value=None)

        response = self._controller.delete_item_latest(request, url, resource_id)

        self._assert_resource_error(response, 404, 10000, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, version)

    def test_delete_item_dbex(self):
        '''This test case ensures a delete operation which fails because of a database exception raises a concrete exception.'''

        url = "/simple-resources"
        version = "latest"
        resource_id = "12345"

        request = Mock()

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(side_effect=FantasticoDbError("Unexpected db exception"))

        response = self._controller.delete_item_latest(request, url, resource_id)

        self._assert_resource_error(response, 400, 10030, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, version)
        self._model_facade.find_by_pk.assert_called_once_with({MockSimpleResourceRoa.id: resource_id})

    def test_delete_item_itemnotfound(self):
        '''This test case ensures an item which does not belong to a collection returns a concrete exception response.'''

        url = "/simple-resources"
        version = "latest"
        resource_id = "12345"

        request = Mock()

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(return_value=None)

        response = self._controller.delete_item_latest(request, url, resource_id)

        self._assert_resource_error(response, 404, 10040, version, url)

        self._resources_registry.find_by_url.assert_called_once_with(url, version)
        self._model_facade.find_by_pk.assert_called_once_with({MockSimpleResourceRoa.id: resource_id})

    def test_delete_item_ok(self):
        '''This test case ensures an existing item can be deleted successfully.'''

        self._controller.validate_security_context = Mock(return_value=None)

        url = "/simple-resources"
        version = "1.0"
        resource_id = "12345"

        request = Mock()

        resource = Resource(name="Mock Simple Resource", url=url,
                            version=1.0)
        resource(MockSimpleResourceRoa, self._resources_registry)

        model = Mock()

        self._resources_registry.find_by_url = Mock(return_value=resource)
        self._model_facade.model_pk_cols = [MockSimpleResourceRoa.id]
        self._model_facade.find_by_pk = Mock(return_value=model)

        response = self._controller.delete_item(request, version, url, resource_id)

        self.assertIsNotNone(response)
        self.assertEqual(204, response.status_code)
        self.assertEqual("application/json", response.content_type)
        self._assert_cors_headers(response)
        self.assertEqual("0", response.headers["Content-Length"])

        self.assertEqual(0, len(response.body))

        self._resources_registry.find_by_url.assert_called_once_with(url, float(version))
        self._model_facade.find_by_pk.assert_called_once_with({MockSimpleResourceRoa.id: resource_id})
        self._model_facade.delete.assert_called_once_with(model)
        self._controller.validate_security_context.assert_called_once_with(request, "delete")

    def test_validate_security_context_ok(self):
        '''This test case ensures that security context validation works as expected.'''

        self._test_validate_security_context_template(valid=True)

    def test_validate_security_context_invalid(self):
        '''This test case ensures that security context validation raises an unauthorized exception if context is not valid.'''

        with self.assertRaises(OAuth2UnauthorizedError):
            self._test_validate_security_context_template(valid=False)

    def test_validate_security_context_oauth2ex(self):
        '''This test case ensures that security context validation oauth2 errors are bubbled up.'''

        ex = OAuth2Error(error_code= -1)

        with self.assertRaises(OAuth2Error) as ctx:
            self._test_validate_security_context_template(side_effect=ex)

        self.assertEqual(ex, ctx.exception)

    def test_validate_security_context_ex(self):
        '''This test case ensures security context validation unexpected exceptions are converted to oauth2
        unauthorized errors.'''

        ex = Exception("Unexpected error.")

        with self.assertRaises(OAuth2Error):
            self._test_validate_security_context_template(side_effect=ex)

    def _test_validate_security_context_template(self, valid=None, side_effect=None):
        '''This method provides a template for checking various behaviors of validate_security_context method.'''

        access_token = Token({})

        kwargs = {}

        if side_effect:
            kwargs["side_effect"] = side_effect
        else:
            kwargs["return_value"] = valid

        request = Mock()
        request.context = Mock()
        request.context.security = Mock()
        request.context.security.access_token = access_token
        request.context.security.validate_context = Mock(**kwargs)

        self.assertEqual(access_token, self._controller.validate_security_context(request, attr_scope="scopes"))

        request.context.security.validate_context.assert_called_once_with("scopes")