Exemple #1
0
def test_parse_simple_subquery_fail():
    """Test a simple subquery fails with invalid input."""
    query_params = {"tracks._subquery_": 5}
    parser = ModelQueryParamParser(query_params)
    with raises(FilterParseError) as excinfo:
        parser.parse_subfilters()
    assert excinfo.value.code == "invalid_complex_filters"
Exemple #2
0
def test_suboffset_parser_bad_value_fail():
    """Ensure basic suboffset parsing fails appropriately."""
    query_params = {"album.tracks._offset_": "test"}
    parser = ModelQueryParamParser(query_params)
    with raises(OffsetLimitParseError) as excinfo:
        parser.parse_subfilters()
    assert excinfo.value.code == "invalid_suboffset_value"
Exemple #3
0
def test_invalid_subresource_path_fail():
    """Ensure a bad subresource path fails."""
    query_params = {"album.tracks._sorts_.failhere": "track_id,-name"}
    parser = ModelQueryParamParser(query_params)
    with raises(ParseError) as excinfo:
        parser.parse_subfilters()
    assert excinfo.value.code == "invalid_subresource_path"
Exemple #4
0
def test_parse_complex_json_non_dict_fail():
    """Ensure non dictionary json complex filters fail."""
    query_params = {"query": "[]"}
    parser = ModelQueryParamParser(query_params)
    with raises(FilterParseError) as excinfo:
        parser.parse_filters(Album)
    assert excinfo.value.code == "invalid_complex_filters"
Exemple #5
0
def test_subfilter_parser():
    """Ensure basic subfilter parsing works."""
    query_params = {"album.tracks._subquery_": '{"track_id": 5}'}
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_subfilters()
    expected_result = {"$and": [{"track_id": 5}]}
    assert expected_result == result["album.tracks"].filters
Exemple #6
0
def test_parse_simple_subquery():
    """Test a simple subquery is handled properly."""
    query_params = {"tracks._subquery_.playlists.playlist_id": 5}
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_subfilters()
    filters = result["tracks"].filters
    assert filters["$and"][0]["playlists.playlist_id"]["$eq"] == 5
Exemple #7
0
def test_parse_complex_subquery():
    """Test a complex subquery is handled properly."""
    query_params = {
        "tracks._subquery_.playlists": json.dumps({"playlist_id": 5})
    }
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_subfilters()
    filters = result["tracks"].filters
    assert filters["$and"][0]["playlists"]["playlist_id"] == 5
Exemple #8
0
def test_parse_filters_ignore_subresource():
    """Ensure filter parsing ignores any subresource paths."""
    query_params = {
        "query": json.dumps({"title": "Big Ones"}),
        "tracks._sorts_": "name"
    }
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_filters(Album)
    assert result["$and"][0]["title"] == "Big Ones"
Exemple #9
0
 def test_get_resources_ordered(db_session):
     """Test simple get_resources sort functionality."""
     query_params = {"sort": "-album_id,title"}
     parser = ModelQueryParamParser(query_params)
     album_resource = AlbumResource(session=db_session)
     result = album_resource.get_collection(filters=parser.parse_filters(
         album_resource.model),
                                            sorts=parser.parse_sorts())
     assert len(result) == 347
     assert result[0]["album_id"] == 347
Exemple #10
0
def test_subsorts_parser():
    """Ensure basic subsorts parsing works."""
    query_params = {"album.tracks._sorts_": "track_id,-name"}
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_subfilters()
    assert len(result["album.tracks"].sorts) == 2
    assert result["album.tracks"].sorts[0].attr == "track_id"
    assert result["album.tracks"].sorts[0].direction == "ASC"
    assert result["album.tracks"].sorts[1].attr == "name"
    assert result["album.tracks"].sorts[1].direction == "DESC"
Exemple #11
0
def test_parse_multiple_complex_filters():
    """Ensure multiple complex filters are treated properly."""
    query_params = {
        "query":
        [json.dumps({"title": "Big Ones"}),
         json.dumps({"title": "Big Ones"})]
    }
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_filters(Album)
    assert result["$and"][0]["title"] == "Big Ones"
    assert result["$and"][1]["title"] == "Big Ones"
Exemple #12
0
 def test_limit(db_session):
     """Make sure providing a limit query_param works."""
     query_params = {"limit": "1"}
     parser = ModelQueryParamParser(query_params)
     album_resource = AlbumResource(session=db_session)
     offset_limit_info = parser.parse_offset_limit(page_max_size=30)
     offset = offset_limit_info.offset
     limit = offset_limit_info.limit
     result = album_resource.get_collection(filters=parser.parse_filters(
         album_resource.model),
                                            sorts=parser.parse_sorts(),
                                            limit=limit,
                                            offset=offset)
     assert len(result) == 1
Exemple #13
0
 def test_get_second_page(db_session):
     """Test that we can get the second page of a set of objects."""
     query_params = {"sort": "album_id", "page": "2"}
     parser = ModelQueryParamParser(query_params)
     album_resource = AlbumResource(session=db_session)
     offset_limit_info = parser.parse_offset_limit(page_max_size=30)
     offset = offset_limit_info.offset
     limit = offset_limit_info.limit
     result = album_resource.get_collection(filters=parser.parse_filters(
         album_resource.model),
                                            sorts=parser.parse_sorts(),
                                            limit=limit,
                                            offset=offset)
     assert len(result) == 30
     assert result[0]["album_id"] == 31
Exemple #14
0
 def test_subresource_nested_query(db_session):
     """Ensure a simple subresource query works."""
     query_params = {
         "tracks._subquery_.track_id-gte": 5,
         "tracks.playlists._subquery_.playlist_id-lte": 5
     }
     parser = ModelQueryParamParser(query_params)
     album_resource = AlbumResource(session=db_session)
     result = album_resource.get_collection(
         subfilters=parser.parse_subfilters(), embeds=parser.parse_embeds())
     success = False
     for album in result:
         if album["album_id"] == 3:
             assert len(album["tracks"]) == 1
             assert album["tracks"][0]["track_id"] == 5
             success = True
     assert success
Exemple #15
0
def test_parse_filters_convert_key_names():
    """Ensure parsing filters works with key name conversion."""
    def convert_key_names(key):
        if key == "titleTest":
            return "title"
        raise AttributeError

    query_params = {"titleTest": "Big Ones", "badkey": "test"}
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_filters(Album,
                                  convert_key_names_func=convert_key_names)
    # Note that this is still titleTest
    # convert_key_names job here is only to be used
    # to verify that an attribute exists in the
    # provided model. Since titleTest is converted
    # to title, and title is an attribute in Album,
    # titleTest is part of the query, unlike badkey.
    assert result["$and"][0]["titleTest"]["$eq"] == "Big Ones"
Exemple #16
0
def test_sublimit_parser_bad_value_ignore():
    """Ensure non strict basic sublimit parsing ignores errors."""
    query_params = {"album.tracks._limit_": "test", "album.tracks._offset_": 5}
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_subfilters(strict=False)
    assert result["album.tracks"].offset == 5
Exemple #17
0
def test_invalid_subresource_path_ignore():
    """Ensure silent failure on subresource path when not strict."""
    query_params = {"album.tracks._sorts_.failhere": "track_id,-name"}
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_subfilters(strict=False)
    assert len(result) == 0
Exemple #18
0
def test_root_complex_filters_parser():
    """Ensure basic root complex parsing works."""
    query_params = {"query": json.dumps({"title": "Big Ones"})}
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_filters(Album)
    assert result["$and"][0]["title"] == "Big Ones"
Exemple #19
0
    def delete(self, path, query_params=None):
        """Generic API router for DELETE requests.

        :param str path: The resource path specified. This should not
            include the root ``/api`` or any versioning info.
        :param query_params: Dictionary of query parameters, likely
            provided as part of a request. Defaults to an empty dict.
        :type query_params: dict or None
        :return: ``None`` if successful.
        :raise ResourceNotFoundError: If no resource can be found at
            the provided path.
        :raise BadRequestError: Invalid filters, sorts, fields,
            embeds, offset, or limit as defined in the provided query
            params will result in a raised exception if strict is set
            to ``True``.
        :raise MethodNotAllowedError: If deleting the resource at the
            supplied path is not allowed.

        """
        if self.resource is None:
            self._deduce_resource(path)
        path_objs = self._get_path_objects(path)
        resource = path_objs.get("resource", None)
        parent_resource = path_objs.get("parent_resource", None)
        path_part = path_objs.get("path_part", None)
        query_session = path_objs.get("query_session", None)
        ident = path_objs.get("ident", None)
        parser = ModelQueryParamParser(query_params, context=self.context)
        # last path_part determines what type of request this is
        if isinstance(path_part, Field) and not isinstance(
                path_part, NestedPermissibleABC):
            # Simple property, such as album_id
            # set the value
            field_name = path_part.data_key or path_part.name
            data = {field_name: None}
            result = resource.patch(ident=ident, data=data)
            if result is not None and field_name in result:
                return result[field_name]
            # failsafe, should be caught by _get_path_objects
            raise self.make_error("resource_not_found",
                                  path=path)  # pragma: no cover
        elif isinstance(path_part, NestedPermissibleABC):
            # subresource
            # Delete contents of the relationship
            if path_part.many:
                return self._subfield_update(method="delete",
                                             data=[],
                                             parent_resource=parent_resource,
                                             resource=resource,
                                             path_part=path_part,
                                             ident=ident,
                                             path=path)
            else:
                return self._subfield_update(method="put",
                                             data=None,
                                             parent_resource=parent_resource,
                                             resource=resource,
                                             path_part=path_part,
                                             ident=ident,
                                             path=path)
        elif isinstance(path_part, BaseModelResource):
            # resource collection
            # any subresource field would already have been handled
            filters = parser.parse_filters(
                resource.model,
                convert_key_names_func=resource.convert_key_name)
            return resource.delete_collection(filters=filters,
                                              session=query_session)
        elif isinstance(path_part, tuple):
            # path part is a resource identifier
            # individual instance
            return resource.delete(ident=path_part)
        raise self.make_error("resource_not_found",
                              path=path)  # pragma: no cover
Exemple #20
0
def test_invalid_complex_subfilters():
    """Test that bad complex filters fail properly."""
    parser = ModelQueryParamParser(query_params={"tracks._subfilter_": "{"})
    with raises(FilterParseError) as excinfo:
        parser.parse_filters(Album)
    assert excinfo.value.code == "invalid_complex_filters"
Exemple #21
0
def test_missing_error_message_fail():
    """Test that failing with a bad error message is handled."""
    parser = ModelQueryParamParser(query_params={})
    with raises(AssertionError):
        parser.make_error(key="test")
Exemple #22
0
def test_suboffset_parser():
    """Ensure basic suboffset parsing works."""
    query_params = {"album.tracks._offset_": 5}
    parser = ModelQueryParamParser(query_params)
    result = parser.parse_subfilters()
    assert result["album.tracks"].offset == 5
Exemple #23
0
    def get(self, path, query_params=None, strict=True, head=False):
        """Generic API router for GET requests.

        :param str path: The resource path specified. This should not
            include the root ``/api`` or any versioning info.
        :param query_params: Dictionary of query parameters, likely
            provided as part of a request. Defaults to an empty dict.
        :type query_params: dict or None
        :param bool strict: If ``True``, bad query params will raise
            non fatal errors rather than ignoring them.
        :param bool head: ``True`` if this was a HEAD request.
        :return: If this is a single entity query, an individual
            resource or ``None``. If this is a collection query, a
            list of resources. If it's an instance field query, the
            raw field value.
        :raise ResourceNotFoundError: If no resource can be found at
            the provided path.
        :raise BadRequestError: Invalid filters, sorts, fields,
            embeds, offset, or limit as defined in the provided query
            params will result in a raised exception if strict is set
            to ``True``.

        """
        if self.resource is None:
            self._deduce_resource(path)
        path_objs = self._get_path_objects(path)
        resource = path_objs.get("resource", None)
        path_part = path_objs.get("path_part", None)
        query_session = path_objs.get("query_session", None)
        ident = path_objs.get("ident", None)
        parser = ModelQueryParamParser(query_params, context=self.context)
        fields = parser.parse_fields()
        embeds = parser.parse_embeds()
        try:
            subfilters = parser.parse_subfilters(strict=strict)
        except ParseError as exc:
            if strict:
                raise BadRequestError(code=exc.code,
                                      message=exc.message,
                                      **exc.kwargs)
            subfilters = None
        # last path_part determines what type of request this is
        if isinstance(path_part, Field) and not isinstance(
                path_part, NestedPermissibleABC):
            # Simple property, such as album_id
            # return only the value
            field_name = path_part.data_key or path_part.name
            result = resource.get(ident=ident,
                                  fields=[field_name],
                                  strict=strict,
                                  session=query_session,
                                  head=head)
            if result is not None and field_name in result:
                return result[field_name]
            raise self.make_error("resource_not_found",
                                  path=path)  # pragma: no cover
        if isinstance(path_part, Field) or isinstance(path_part,
                                                      BaseModelResource):
            # resource collection
            # any non subresource field would already have been handled
            try:
                filters = parser.parse_filters(
                    resource.model,
                    convert_key_names_func=resource.convert_key_name)
            except FilterParseError as e:
                if strict:
                    raise BadRequestError(code=e.code,
                                          message=e.message,
                                          **e.kwargs)
                filters = None
            if not (isinstance(path_part, Nested) and not path_part.many):
                try:
                    offset_limit_info = parser.parse_offset_limit(
                        resource.page_max_size)
                    offset = offset_limit_info.offset
                    limit = offset_limit_info.limit
                except OffsetLimitParseError as e:
                    if strict:
                        raise BadRequestError(code=e.code,
                                              message=e.message,
                                              **e.kwargs)
                    offset, limit = None, None
                sorts = parser.parse_sorts()
                results = resource.get_collection(filters=filters,
                                                  subfilters=subfilters,
                                                  fields=fields,
                                                  embeds=embeds,
                                                  sorts=sorts,
                                                  offset=offset,
                                                  limit=limit,
                                                  session=query_session,
                                                  strict=strict,
                                                  head=head)
                if query_params.get("page") is not None or not offset:
                    results.current_page = int(query_params.get("page") or 1)
                    results.page_size = limit or resource.page_max_size
                return results
            else:
                result = resource.get_collection(fields=fields,
                                                 embeds=embeds,
                                                 subfilters=subfilters,
                                                 session=query_session,
                                                 strict=strict,
                                                 head=head)
                if len(result) != 1:  # pragma: no cover
                    # failsafe, _get_path_objects will catch this first.
                    raise self.make_error("resource_not_found", path=path)
                return result[0]
        elif isinstance(path_part, tuple):
            # path part is a resource identifier
            # individual instance
            return resource.get(ident=path_part,
                                fields=fields,
                                embeds=embeds,
                                subfilters=subfilters,
                                strict=strict,
                                session=query_session,
                                head=head)
        raise self.make_error("resource_not_found",
                              path=path)  # pragma: no cover