Example #1
0
def swagger_test_yield(swagger_yaml_path=None, app_url=None, authorize_error=None,
                       wait_between_test=False, use_example=True):
    """Test the given swagger api. Yield the action and operation done for each test.

    Test with either a swagger.yaml path for a connexion app or with an API
    URL if you have a running API.

    Args:
        swagger_yaml_path: path of your YAML swagger file.
        app_url: URL of the swagger api.
        authorize_error: dict containing the error you don't want to raise.
                         ex: {
                            'get': {
                                '/pet/': ['404']
                            }
                         }
                         Will ignore 404 when getting a pet.
        wait_between_test: wait between tests (useful if you use Elasticsearch).
        use_example: use example of your swagger file instead of generated data.

    Returns:
        Yield between each test: (action, operation)

    Raises:
        ValueError: In case you specify neither a swagger.yaml path or an app URL.
    """
    if authorize_error is None:
        authorize_error = {}

    # Init test
    if swagger_yaml_path is not None:
        app = connexion.App(__name__, port=8080, debug=True, specification_dir=os.path.dirname(os.path.realpath(swagger_yaml_path)))
        app.add_api(os.path.basename(swagger_yaml_path))
        app_client = app.app.test_client()
        swagger_parser = SwaggerParser(swagger_yaml_path, use_example=use_example)
    elif app_url is not None:
        app_client = requests
        swagger_parser = SwaggerParser(swagger_dict=requests.get(u'{0}/swagger.json'.format(app_url)).json(),
                                       use_example=False)
    else:
        raise ValueError('You must either specify a swagger.yaml path or an app url')

    operation_sorted = {'post': [], 'get': [], 'put': [], 'patch': [], 'delete': []}

    # Sort operation by action
    for operation, request in swagger_parser.operation.items():
        operation_sorted[request[1]].append((operation, request))

    postponed = []

    # For every operationId
    for action in ['post', 'get', 'put', 'patch', 'delete']:
        for operation in operation_sorted[action]:
            # Make request
            path = operation[1][0]
            action = operation[1][1]

            request_args = get_request_args(path, action, swagger_parser)
            url, body, headers, files = get_url_body_from_request(action, path, request_args, swagger_parser)

            logger.info(u'TESTING {0} {1}'.format(action.upper(), url))

            if swagger_yaml_path is not None:
                response = get_method_from_action(app_client, action)(url, headers=headers,
                                                                      data=body)
            else:
                response = get_method_from_action(app_client, action)(u'{0}{1}'.format(app_url.replace(swagger_parser.base_path, ''), url),
                                                                      headers=dict(headers),
                                                                      data=body,
                                                                      files=files)

            logger.info(u'Got status code: {0}'.format(response.status_code))

            # Check if authorize error
            if (action in authorize_error and path in authorize_error[action] and
                    response.status_code in authorize_error[action][path]):
                logger.info(u'Got authorized error on {0} with status {1}'.format(url, response.status_code))
                yield (action, operation)
                continue

            if not response.status_code == 404:
                # Get valid request and response body
                body_req = swagger_parser.get_send_request_correct_body(path, action)

                try:
                    response_spec = swagger_parser.get_request_data(path, action, body_req)
                except (TypeError, ValueError) as exc:
                    logger.warning(u'Error in the swagger file: {0}'.format(repr(exc)))
                    continue

                # Get response data
                if hasattr(response, 'content'):
                    response_text = response.content
                else:
                    response_text = response.data

                # Convert to str
                if hasattr(response_text, 'decode'):
                    response_text = response_text.decode('utf-8')

                # Get json
                try:
                    response_json = json.loads(response_text)
                except ValueError:
                    response_json = response_text

                assert response.status_code < 400

                if response.status_code in response_spec.keys():
                    validate_definition(swagger_parser, response_spec[response.status_code], response_json)
                elif 'default' in response_spec.keys():
                    validate_definition(swagger_parser, response_spec['default'], response_json)
                else:
                    raise AssertionError('Invalid status code {0}. Expected: {1}'.format(response.status_code,
                                                                                         response_spec.keys()))

                if wait_between_test:  # Wait
                    time.sleep(2)

                yield (action, operation)
            else:
                # 404 => Postpone retry
                if {'action': action, 'operation': operation} in postponed:  # Already postponed => raise error
                    raise Exception(u'Invalid status code {0}'.format(response.status_code))

                operation_sorted[action].append(operation)
                postponed.append({'action': action, 'operation': operation})
                yield (action, operation)
                continue
Example #2
0
def swagger_test_yield(swagger_yaml_path=None, app_url=None, authorize_error=None,
                       wait_time_between_tests=0, use_example=True, dry_run=False,
                       extra_headers={}):
    """Test the given swagger api. Yield the action and operation done for each test.

    Test with either a swagger.yaml path for a connexion app or with an API
    URL if you have a running API.

    Args:
        swagger_yaml_path: path of your YAML swagger file.
        app_url: URL of the swagger api.
        authorize_error: dict containing the error you don't want to raise.
                         ex: {
                            'get': {
                                '/pet/': ['404']
                            }
                         }
                         Will ignore 404 when getting a pet.
        wait_time_between_tests: an number that will be used as waiting time between tests [in seconds].
        use_example: use example of your swagger file instead of generated data.
        dry_run: don't actually execute the test, only show what would be sent
        extra_headers: additional headers you may want to send for all operations

    Returns:
        Yield between each test: (action, operation)

    Raises:
        ValueError: In case you specify neither a swagger.yaml path or an app URL.
    """
    if authorize_error is None:
        authorize_error = {}

    # Init test
    if swagger_yaml_path is not None and app_url is not None:
        app_client = requests
        swagger_parser = SwaggerParser(swagger_yaml_path, use_example=use_example)
    elif swagger_yaml_path is not None:
        specification_dir = os.path.dirname(os.path.realpath(swagger_yaml_path))
        app = connexion.App(__name__, port=8080, debug=True, specification_dir=specification_dir)
        app.add_api(os.path.basename(swagger_yaml_path))
        app_client = app.app.test_client()
        swagger_parser = SwaggerParser(swagger_yaml_path, use_example=use_example)
    elif app_url is not None:
        app_client = requests
        remote_swagger_def = requests.get(u'{0}/swagger.json'.format(app_url)).json()
        swagger_parser = SwaggerParser(swagger_dict=remote_swagger_def, use_example=use_example)
    else:
        raise ValueError('You must either specify a swagger.yaml path or an app url')

    print("Starting testrun against {0} or {1} using examples: "
          "{2}".format(swagger_yaml_path, app_url, use_example))

    operation_sorted = {method: [] for method in _HTTP_METHODS}

    # Sort operation by action
    operations = swagger_parser.operation.copy()
    operations.update(swagger_parser.generated_operation)
    for operation, request in operations.items():
        operation_sorted[request[1]].append((operation, request))

    postponed = []

    # For every operationId
    for action in _HTTP_METHODS:
        for operation in operation_sorted[action]:
            # Make request
            path = operation[1][0]
            action = operation[1][1]
            client_name = getattr(app_client, '__name__', 'FlaskClient')

            request_args = get_request_args(path, action, swagger_parser)
            url, body, headers, files = get_url_body_from_request(action, path, request_args, swagger_parser)

            logger.info(u'TESTING {0} {1}'.format(action.upper(), url))

            # Add any extra headers specified by the user
            headers.extend([(key, value)for key, value in extra_headers.items()])

            if swagger_yaml_path is not None and app_url is None:
                if dry_run:
                    logger.info("\nWould send %s to %s with body %s and headers %s" %
                                (action.upper(), url, body, headers))
                    continue
                response = get_method_from_action(app_client, action)(url, headers=headers, data=body)
            else:
                if app_url.endswith(swagger_parser.base_path):
                    base_url = app_url[:-len(swagger_parser.base_path)]
                else:
                    base_url = app_url
                full_path = u'{0}{1}'.format(base_url, url)
                if dry_run:
                    logger.info("\nWould send %s to %s with body %s and headers %s" %
                                (action.upper(), full_path, body, headers))
                    continue
                response = get_method_from_action(app_client, action)(full_path,
                                                                      headers=dict(headers),
                                                                      data=body,
                                                                      files=files)

            logger.info(u'Using {0}, got status code {1} for ********** {2} {3}'.format(
                client_name, response.status_code, action.upper(), url))

            # Check if authorize error
            if (action in authorize_error and path in authorize_error[action] and
                    response.status_code in authorize_error[action][path]):
                logger.info(u'Got expected authorized error on {0} with status {1}'.format(url, response.status_code))
                yield (action, operation)
                continue

            if response.status_code is not 404:
                # Get valid request and response body
                body_req = swagger_parser.get_send_request_correct_body(path, action)

                try:
                    response_spec = swagger_parser.get_request_data(path, action, body_req)
                except (TypeError, ValueError) as exc:
                    logger.warning(u'Error in the swagger file: {0}'.format(repr(exc)))
                    continue

                # Get response data
                if hasattr(response, 'content'):
                    response_text = response.content
                else:
                    response_text = response.data

                # Convert to str
                if hasattr(response_text, 'decode'):
                    response_text = response_text.decode('utf-8')

                # Get json
                try:
                    response_json = json.loads(response_text)
                except ValueError:
                    response_json = response_text

                if response.status_code in response_spec.keys():
                    validate_definition(swagger_parser, response_spec[response.status_code], response_json)
                elif 'default' in response_spec.keys():
                    validate_definition(swagger_parser, response_spec['default'], response_json)
                else:
                    raise AssertionError('Invalid status code {0}. Expected: {1}'.format(response.status_code,
                                                                                         response_spec.keys()))

                if wait_time_between_tests > 0:
                    time.sleep(wait_time_between_tests)

                yield (action, operation)
            else:
                # 404 => Postpone retry
                if {'action': action, 'operation': operation} in postponed:  # Already postponed => raise error
                    raise Exception(u'Invalid status code {0}'.format(response.status_code))

                operation_sorted[action].append(operation)
                postponed.append({'action': action, 'operation': operation})
                yield (action, operation)
                continue
def swagger_test_yield(swagger_yaml_path=None, app_url=None, authorize_error=None,
                       wait_time_between_tests=0, use_example=True, dry_run=False):
    """Test the given swagger api. Yield the action and operation done for each test.

    Test with either a swagger.yaml path for a connexion app or with an API
    URL if you have a running API.

    Args:
        swagger_yaml_path: path of your YAML swagger file.
        app_url: URL of the swagger api.
        authorize_error: dict containing the error you don't want to raise.
                         ex: {
                            'get': {
                                '/pet/': ['404']
                            }
                         }
                         Will ignore 404 when getting a pet.
        wait_time_between_tests: an number that will be used as waiting time between tests [in seconds].
        use_example: use example of your swagger file instead of generated data.
        dry_run: don't actually execute the test, only show what would be sent

    Returns:
        Yield between each test: (action, operation)

    Raises:
        ValueError: In case you specify neither a swagger.yaml path or an app URL.
    """
    if authorize_error is None:
        authorize_error = {}

    # Init test
    if swagger_yaml_path is not None and app_url is not None:
        app_client = requests
        swagger_parser = SwaggerParser(swagger_yaml_path, use_example=use_example)
    elif swagger_yaml_path is not None:
        specification_dir = os.path.dirname(os.path.realpath(swagger_yaml_path))
        app = connexion.App(__name__, port=8080, debug=True, specification_dir=specification_dir)
        app.add_api(os.path.basename(swagger_yaml_path))
        app_client = app.app.test_client()
        swagger_parser = SwaggerParser(swagger_yaml_path, use_example=use_example)
    elif app_url is not None:
        app_client = requests
        remote_swagger_def = requests.get(u'{0}/swagger.json'.format(app_url)).json()
        swagger_parser = SwaggerParser(swagger_dict=remote_swagger_def, use_example=use_example)
    else:
        raise ValueError('You must either specify a swagger.yaml path or an app url')

    print("Starting testrun against {0} or {1} using examples: "
          "{2}".format(swagger_yaml_path, app_url, use_example))

    operation_sorted = {method: [] for method in _HTTP_METHODS}

    # Sort operation by action
    operations = swagger_parser.operation.copy()
    operations.update(swagger_parser.generated_operation)
    for operation, request in operations.items():
        operation_sorted[request[1]].append((operation, request))

    postponed = []

    # For every operationId
    for action in _HTTP_METHODS:
        for operation in operation_sorted[action]:
            # Make request
            path = operation[1][0]
            action = operation[1][1]
            client_name = getattr(app_client, '__name__', 'FlaskClient')

            request_args = get_request_args(path, action, swagger_parser)
            url, body, headers, files = get_url_body_from_request(action, path, request_args, swagger_parser)

            logger.info(u'TESTING {0} {1}'.format(action.upper(), url))

            if swagger_yaml_path is not None and app_url is None:
                if dry_run:
                    logger.info("\nWould send %s to %s with body %s and headers %s" %
                                (action.upper(), url, body, headers))
                    continue
                response = get_method_from_action(app_client, action)(url, headers=headers, data=body)
            else:
                full_path = u'{0}{1}'.format(app_url.replace(swagger_parser.base_path, ''), url)
                if dry_run:
                    logger.info("\nWould send %s to %s with body %s and headers %s" %
                                (action.upper(), full_path, body, headers))
                    continue
                response = get_method_from_action(app_client, action)(full_path,
                                                                      headers=dict(headers),
                                                                      data=body,
                                                                      files=files)

            logger.info(u'Using {0}, got status code {1} for ********** {2} {3}'.format(
                client_name, response.status_code, action.upper(), url))

            # Check if authorize error
            if (action in authorize_error and path in authorize_error[action] and
                    response.status_code in authorize_error[action][path]):
                logger.info(u'Got expected authorized error on {0} with status {1}'.format(url, response.status_code))
                yield (action, operation)
                continue

            if response.status_code is not 404:
                # Get valid request and response body
                body_req = swagger_parser.get_send_request_correct_body(path, action)

                try:
                    response_spec = swagger_parser.get_request_data(path, action, body_req)
                except (TypeError, ValueError) as exc:
                    logger.warning(u'Error in the swagger file: {0}'.format(repr(exc)))
                    continue

                # Get response data
                if hasattr(response, 'content'):
                    response_text = response.content
                else:
                    response_text = response.data

                # Convert to str
                if hasattr(response_text, 'decode'):
                    response_text = response_text.decode('utf-8')

                # Get json
                try:
                    response_json = json.loads(response_text)
                except ValueError:
                    response_json = response_text

                if response.status_code in response_spec.keys():
                    validate_definition(swagger_parser, response_spec[response.status_code], response_json)
                elif 'default' in response_spec.keys():
                    validate_definition(swagger_parser, response_spec['default'], response_json)
                else:
                    raise AssertionError('Invalid status code {0}. Expected: {1}'.format(response.status_code,
                                                                                         response_spec.keys()))

                if wait_time_between_tests > 0:
                    time.sleep(wait_time_between_tests)

                yield (action, operation)
            else:
                # 404 => Postpone retry
                if {'action': action, 'operation': operation} in postponed:  # Already postponed => raise error
                    raise Exception(u'Invalid status code {0}'.format(response.status_code))

                operation_sorted[action].append(operation)
                postponed.append({'action': action, 'operation': operation})
                yield (action, operation)
                continue
Example #4
0
def swagger_test_yield(swagger_yaml_path=None, app_url=None, authorize_error=None,
                       wait_between_test=False, use_example=True):

    if authorize_error is None:
        authorize_error = {}


    #本地文件的情况
    if swagger_yaml_path is not None:
        app_client = requests
        swagger_parser = SwaggerParser(swagger_path=swagger_yaml_path,use_example=True)

    #url的情况
    elif app_url is not None:
        #使用requests提供的默认client
        app_client = requests
        #只需要/.json前面的url
        swagger_parser = SwaggerParser(swagger_dict=requests.get(u'{0}/swagger.json'.format(app_url)).json(),
                                       use_example=True)

        swagger_parser.definitions_example.get('Pet')
    else:
        raise ValueError('You must either specify a swagger.yaml path or an app url')

    operation_sorted = {'post': [], 'get': [], 'put': [], 'patch': [], 'delete': []}

    #将操作排序,operation.items存在的前提是swagger文档中每个操作均有operationID,所以要先做判断是否存在operaionid,但是无法应对有些有id有些无的情况。
    if len(swagger_parser.operation.items()) > 0:
        flag = 0
        for operation, request in swagger_parser.operation.items():
            operation_sorted[request[1]].append((operation, request))
    else:
        flag = 1
        operation_sorted = get_action_from_path(swagger_parser)

    postponed = []

    #记录测试的API数目
    test_no = 0
    #将测试信息输出到excel表格中
    excel_headers = ('No.', 'path', 'action', 'status_code', 'inconsisdency', 'error_info')
    excel_dataset = tablib.Dataset()
    excel_dataset.headers = excel_headers

#按上面的顺序对各个方法进行测试
    for action in ['post', 'get', 'put', 'patch', 'delete']:
        for operation in operation_sorted[action]:
            if flag == 0:
                #operation_sorted以键值对的形式分别存储操作的路径和操作类型:key=operationid,value=(path, action, tag)
                path = operation[1][0]
                action = operation[1][1]
            if flag == 1:
                #以键值对的形式存储:key=path, value=action
                path = operation[0]
                action = operation[1]


            #调用函数得到自动生成的参数#####################################################
            #request_args = get_args_from_example(path, action, swagger_parser)
            request_args = get_args_from_example(path, action, swagger_parser)
            #处理url
            url, body, headers, files = get_url_body_from_request(action, path, request_args, swagger_parser)

            #将测试的信息输出到控制台
            logger.info(u'TESTING {0} {1}'.format(action.upper(), url))
            print(u'TESTING {0} {1}'.format(action.upper(), url))

            #两种client参数有区别
            if swagger_yaml_path is not None:
                my_url = u'{0}{1}'.format('https://' + swagger_parser.host, url)    #应该替换为swagger_parser.schemes
                response = get_method_from_action(app_client, action)(my_url,
                                                                      headers=dict(headers),
                                                                      data=body,
                                                                      files=files)
            ###################################### url ###########
            else:
                my_url = u'{0}{1}'.format('https://' + swagger_parser.host, url)
                response = get_method_from_action(app_client, action)(my_url,
                                                                      headers=dict(headers),
                                                                      data=body,
                                                                      files=files)

            #直接访问response的status_code方法得到状态码
            logger.info(u'Got status code: {0}'.format(response.status_code))
            print(u'Got status code: {0}'.format(response.status_code))

            #检查得到的状态码是不是authorize error中定义的
            if (action in authorize_error and path in authorize_error[action] and
                        response.status_code in authorize_error[action][path]):
                logger.info(u'Got authorized error on {0} with status {1}'.format(url, response.status_code))
                print(u'Got authorized error on {0} with status {1}'.format(url, response.status_code) )
                yield (action, operation)
                continue

            if not response.status_code == 404:
                #得到request的内容
                body_req = swagger_parser.get_send_request_correct_body(path, action)
                #错误处理
                try:
                    response_spec = swagger_parser.get_request_data(path, action, body_req)
                except (TypeError, ValueError) as exc:
                    logger.warning(u'Error in the swagger file: {0}'.format(repr(exc)))
                    print(u'Error in the swagger file: {0}'.format(repr(exc)) + '\n')
                    continue

                #分析response得到其中的数据
                if hasattr(response, 'content'):
                    response_text = response.content
                else:
                    response_text = response.data

                #将得到的response放入json中
                if hasattr(response_text, 'decode'):
                    response_text = response_text.decode('utf-8')


                try:
                    response_json = json.loads(response_text)
                except ValueError:
                    response_json = response_text

                #大于400的状态码统一视作错误
                #assert response.status_code < 400

                if response.status_code in response_spec.keys():
                    inconsisdency = validate_ins_definition(swagger_parser, response_spec[response.status_code], response_json)
                elif 'default' in response_spec.keys():
                    inconsisdency = validate_ins_definition(swagger_parser, response_spec['default'], response_json)
                #所有状态码【200】 都视为正确的返回,如果没有写200的参数,那么默认返回没有参数,而是一个标示操作成功的bool
                elif response.status_code is 200:
                    logger.info('Got status code 200, but undefined in spec.')
                    print('Got status code 200, but undefined in spec.\n')
                elif response.status_code is 405:
                    logger.info('Got status code 405. Method Not Allowed')
                else:
                    logger.info(u'Got status code:{0},parameters error or authorization error.'.format(response.status_code))


                if response.status_code == 200:
                    test_no += 1
                    if inconsisdency:
                        excel_dataset.append([test_no, path, action, response.status_code, 'Yes', '-'])
                    else:
                        excel_dataset.append([test_no, path, action, response.status_code, 'No', '-'])
                else:
                    test_no += 1
                    #excel_dataset.append([test_no, path, action, response.status_code, '-', response.reason])
                    excel_dataset.append([test_no, path, action, response.status_code, '-', response.content])


                if wait_between_test:  # Wait
                    time.sleep(2)
                yield (action, operation)
            else:
                #得到404错误,等待后重试
                if {'action': action, 'operation': operation} in postponed:
                    #如果已经重试过了,报错
                    logger.info(u'Path {0} has been modified or removed!'.format(path))
                    test_no += 1
                    excel_dataset.append([test_no, path, action, response.status_code, '-', response.reason])
                    postponed.remove({'action': action, 'operation': operation})
                else:
                    #将没有重试过的方法放到测试队列最后
                    operation_sorted[action].append(operation)
                    #同时标记为已经推迟过
                    postponed.append({'action': action, 'operation': operation})
                yield (action, operation)
                continue

    excel_dataset.title = 'test_result'
    #导出到Excel表格中
    excel_file = open('./output/test_excel.xlsx', 'wb')
    excel_file.write(excel_dataset.xlsx)
    excel_file.close()
def swagger_test_yield(
    swagger_yaml_path=None, app_url=None, authorize_error=None, wait_between_test=False, use_example=True
):
    """Test the given swagger api. Yield the action and operation done for each test.

    Test with either a swagger.yaml path for a connexion app or with an API
    URL if you have a running API.

    Args:
        swagger_yaml_path: path of your YAML swagger file.
        app_url: URL of the swagger api.
        authorize_error: dict containing the error you don't want to raise.
                         ex: {
                            'get': {
                                '/pet/': ['404']
                            }
                         }
                         Will ignore 404 when getting a pet.
        wait_between_test: wait between tests (useful if you use Elasticsearch).
        use_example: use example of your swagger file instead of generated data.

    Returns:
        Yield between each test: (action, operation)

    Raises:
        ValueError: In case you specify neither a swagger.yaml path or an app URL.
    """
    if authorize_error is None:
        authorize_error = {}

    # Init test
    if swagger_yaml_path is not None:
        app = connexion.App(
            __name__, port=8080, debug=True, specification_dir=os.path.dirname(os.path.realpath(swagger_yaml_path))
        )
        app.add_api(os.path.basename(swagger_yaml_path))
        app_client = app.app.test_client()
        swagger_parser = SwaggerParser(swagger_yaml_path, use_example=use_example)
    elif app_url is not None:
        app_client = requests
        swagger_parser = SwaggerParser(
            swagger_dict=requests.get(u"{0}/swagger.json".format(app_url)).json(), use_example=False
        )
    else:
        raise ValueError("You must either specify a swagger.yaml path or an app url")

    operation_sorted = {"post": [], "get": [], "put": [], "patch": [], "delete": []}

    # Sort operation by action
    for operation, request in swagger_parser.operation.items():
        operation_sorted[request[1]].append((operation, request))

    postponed = []

    # For every operationId
    for action in ["post", "get", "put", "patch", "delete"]:
        for operation in operation_sorted[action]:
            # Make request
            path = operation[1][0]
            action = operation[1][1]

            request_args = get_request_args(path, action, swagger_parser)
            url, body, headers = get_url_body_from_request(action, path, request_args, swagger_parser)

            if swagger_yaml_path is not None:
                response = get_method_from_action(app_client, action)(url, headers=headers, data=body)
            else:
                response = get_method_from_action(app_client, action)(
                    u"{0}{1}".format(app_url.replace(swagger_parser.base_path, ""), url),
                    headers=dict(headers),
                    data=body,
                )

            logger.info(u"TESTING {0} {1}: {2}".format(action.upper(), url, response.status_code))

            # Check if authorize error
            if (
                action in authorize_error
                and url in authorize_error[action]
                and response.status_code in authorize_error[action][url]
            ):
                yield (action, operation)
                continue

            if not response.status_code == 404:
                # Get valid request and response body
                body_req = swagger_parser.get_send_request_correct_body(path, action)
                response_spec = swagger_parser.get_request_data(path, action, body_req)

                # Get response data
                if hasattr(response, "content"):
                    response_text = response.content
                else:
                    response_text = response.data

                # Get json
                try:
                    response_json = json.loads(response_text.decode("utf-8"))
                except (ValueError, AttributeError):
                    response_json = None

                assert response.status_code in response_spec.keys()
                assert response.status_code < 400

                validate_definition(swagger_parser, response_spec[response.status_code], response_json)

                if wait_between_test:  # Wait
                    time.sleep(2)

                yield (action, operation)
            else:
                # 404 => Postpone retry
                if {"action": action, "operation": operation} in postponed:  # Already postponed => raise error
                    raise Exception(u"Invalid status code {0}".format(response.status_code))

                operation_sorted[action].append(operation)
                postponed.append({"action": action, "operation": operation})
                yield (action, operation)
                continue