Example #1
0
    def __init__(self, response_schema: dict, response_data: Any,
                 **kwargs) -> None:
        """
        Iterates through both a response schema and an actual API response to check that they match.

        :param response_schema: Response OpenAPI schema section
        :param response_data: API response data
        raises: django_swagger_tester.exceptions.SwaggerDocumentationError or ImproperlyConfigured
        """
        if '$ref' in str(response_schema):
            # `$ref`s should be replaced inplace before the schema is passed to this class
            raise ImproperlyConfigured(
                'Received invalid schema. You must replace $ref sections before passing a schema for validation.'
            )

        if read_type(response_schema) == 'object':
            logger.debug('init --> dict')
            self.test_dict(schema=response_schema,
                           data=response_data,
                           reference='init',
                           **kwargs)
        elif read_type(response_schema) == 'array':
            logger.debug('init --> list')
            self.test_list(schema=response_schema,
                           data=response_data,
                           reference='init',
                           **kwargs)
        # this should always be third, as list_types also contains `array` and `object`
        elif read_type(response_schema) in list_types():
            logger.debug('init --> item')
            self.test_item(schema=response_schema,
                           data=response_data,
                           reference='init',
                           **kwargs)
Example #2
0
 def test_list(self, array: dict) -> None:
     """
     Iterates through a schema array to pass appropriate nested items for further test_checks.
     Only object keys need case checking, so that's what we're looking for.
     """
     item = read_items(array)
     if read_type(item) == 'object':
         logger.debug('list -> dict')
         self.test_dict(obj=item)
     elif read_type(item) == 'array':
         logger.debug('list -> list')
         self.test_list(array=item)
Example #3
0
def test_read_type():
    """
    Ensure this helper function works as it's designed to.
    """
    e = 'Schema item has an invalid `type` attribute. The type should be a single string'
    with pytest.raises(OpenAPISchemaError, match=e):
        read_type('test')

    e = 'Schema item has an invalid `type` attribute. The type `bad type` is not supported.'
    with pytest.raises(OpenAPISchemaError, match=e):
        read_type({'type': 'bad type'})

    assert read_type({'type': 'string'}) == 'string'
Example #4
0
def _iterate_schema_list(l: dict) -> list:
    x = []
    i = read_items(l)
    if read_type(i) == 'object':
        x.append(_iterate_schema_dict(i))
    elif read_type(i) == 'array':
        x.append(_iterate_schema_list(i))  # type: ignore
    elif 'example' in i:
        x.append(i['example'])
    else:
        raise ImproperlyConfigured(
            f'This schema item does not seem to have example value. Item: {i}')
    return x
Example #5
0
 def test_dict(self, obj: dict) -> None:
     """
     Iterates through a schema object to check keys' case, and to pass nested values for further test_checks.
     """
     properties = read_properties(obj)
     for key, value in properties.items():
         conditional_check(key, self.case_check, self.ignored_keys)
         if read_type(value) == 'object':
             logger.debug('dict -> dict')
             self.test_dict(obj=value)
         elif read_type(value) == 'array':
             logger.debug('dict -> list')
             self.test_list(array=value)
Example #6
0
def _iterate_schema_dict(d: dict) -> dict:
    x = {}
    for key, value in d['properties'].items():
        if read_type(value) == 'object':
            x[key] = _iterate_schema_dict(value)
        elif read_type(value) == 'array':
            x[key] = _iterate_schema_list(value)  # type: ignore
        elif 'example' in value:
            x[key] = value['example']
        else:
            raise ImproperlyConfigured(
                f'This schema item does not seem to have example value. Item: {value}'
            )
    return x
Example #7
0
def serialize_schema(schema: dict) -> Union[list, dict, str, int, bool]:
    """
    Converts an OpenAPI schema representation of a dict to dict.
    """
    if read_type(schema) == 'array':
        logger.debug('--> list')
        return _iterate_schema_list(schema)
    elif read_type(schema) == 'object':
        logger.debug('--> dict')
        return _iterate_schema_dict(schema)
    elif 'example' in schema:
        return schema['example']
    else:
        raise ImproperlyConfigured(
            f'This schema item does not seem to have example value. Item: {schema}'
        )
Example #8
0
    def __init__(self, schema: dict, **kwargs) -> None:
        """
        Finds the appropriate case check function and calls the appropriate function base on the schema item type.

        :param schema: openapi schema item
        """
        self.case_check = case_check(settings.CASE)
        self.ignored_keys = set_ignored_keys(**kwargs)
        if read_type(schema) == 'object':
            logger.debug('root -> dict')
            self.test_dict(schema)
        elif read_type(schema) == 'array':
            logger.debug('root -> list')
            self.test_list(schema)
        else:
            logger.debug('Skipping case check')
Example #9
0
    def test_list(self, schema: dict, data: Union[list, dict], reference: str,
                  **kwargs) -> None:
        """
        Verifies that a schema array matches a response list.

        :param schema: OpenAPI schema
        :param data: Response data
        :param reference: string reference pointing to function caller
        :raises: django_swagger_tester.exceptions.SwaggerDocumentationError
        """
        if not isinstance(data, list):
            hint = ''
            if isinstance(data, dict):
                hint = 'You might need to wrap your response item in a list, or remove the excess list layer from your documented response.'
            raise format_error(
                error_message=
                f"Mismatched types. Expected response to be <class 'list'> but found {type(data)}.",
                data=data,
                schema=schema,
                reference=reference,
                hint=hint,
                **kwargs,
            )

        item = read_items(schema)
        for index in range(len(data)):

            if read_type(item) == 'object':
                logger.debug('test_list --> test_dict')
                self.test_dict(schema=item,
                               data=data[index],
                               reference=f'{reference}.list',
                               **kwargs)

            elif read_type(item) == 'array':
                logger.debug('test_list --> test_dict')
                self.test_list(schema=item,
                               data=data[index],
                               reference=f'{reference}.list',
                               **kwargs)

            elif read_type(item) in list_types():
                logger.debug('test_list --> test_item')
                self.test_item(schema=item,
                               data=data[index],
                               reference=f'{reference}.list',
                               **kwargs)
Example #10
0
    def test_dict(self, schema: dict, data: Union[list, dict], reference: str,
                  **kwargs) -> None:
        """
        Verifies that a schema dict matches a response dict.

        :param schema: OpenAPI schema
        :param data: Response data
        :param reference: string reference pointing to function caller
        :raises: django_swagger_tester.exceptions.SwaggerDocumentationError
        """
        if not isinstance(data, dict):
            hint = ''
            if isinstance(data, list):
                hint = 'The expected item should be a dict, or your schema should be a list.'
            raise format_error(
                error_message=
                f"Mismatched types. Expected response to be <class 'dict'> but found {type(data)}.",
                data=data,
                schema=schema,
                reference=reference,
                hint=hint,
                **kwargs,
            )

        properties = read_properties(schema)
        schema_keys = properties.keys()
        response_keys = data.keys()
        check_keys_match(schema_keys, response_keys, schema, data, reference)

        for schema_key, response_key in zip(schema_keys, response_keys):

            # Check that each element in the schema exists in the response, and vice versa
            if schema_key not in response_keys:
                raise format_error(
                    error_message=
                    f'Schema key `{schema_key}` was not found in the API response.',
                    data=data,
                    schema=schema,
                    reference=reference,
                    hint=
                    'You need to add the missing schema key to the response, or remove it from the documented response.',
                    **kwargs,
                )
            elif response_key not in schema_keys:
                raise format_error(
                    error_message=
                    f'Response key `{response_key}` not found in the OpenAPI schema.',
                    data=data,
                    schema=schema,
                    reference=reference,
                    hint=
                    'You need to add the missing schema key to your documented response, or stop returning it in your API.',
                    **kwargs,
                )

            # Pass nested elements to the appropriate function
            schema_value = properties[schema_key]
            response_value = data[schema_key]

            if read_type(schema_value) == 'object':
                logger.debug(
                    'test_dict --> test_dict. Response: %s, Schema: %s',
                    response_value, schema_value)
                self.test_dict(schema=schema_value,
                               data=response_value,
                               reference=f'{reference}.dict:key:{schema_key}',
                               **kwargs)
            elif read_type(schema_value) == 'array':
                logger.debug(
                    'test_dict --> test_list. Response: %s, Schema: %s',
                    response_value, schema_value)
                self.test_list(schema=schema_value,
                               data=response_value,
                               reference=f'{reference}.dict:key:{schema_key}',
                               **kwargs)
            elif read_type(schema_value) in list_types(
            ):  # This needs to come after array and object test_checks
                logger.debug(
                    'test_dict --> test_item. Response: %s, Schema: %s',
                    response_value, schema_value)
                self.test_item(schema=schema_value,
                               data=response_value,
                               reference=f'{reference}.dict:key:{schema_key}',
                               **kwargs)