Exemplo n.º 1
0
 def test_test_content_templating(self):
     context = Context()
     test = TestCase('', None, None, context)
     handler = ContentHandler()
     handler.is_template_content = True
     handler.content = '{"first_name": "Gaius","id": "$id","last_name": "Baltar","login": "******"}'
     context.bind_variables({'id': 9, 'login': '******'})
     test.body = handler
     test.pre_update(context=context)
     self.assertEqual(
         string.Template(handler.content).safe_substitute(
             context.get_values()), test.body)
Exemplo n.º 2
0
    def test_variables(self):
        """ Test bind/return of variables """

        context = Context()
        context.variables = {}
        self.assertTrue(context.get_value('foo') is None)
        self.assertEqual(0, context.mod_count)

        context.bind_variable('foo', 'bar')
        self.assertEqual('bar', context.get_value('foo'))
        self.assertEqual('bar', context.get_values()['foo'])
        self.assertEqual(1, context.mod_count)

        context.bind_variable('foo', 'bar2')
        self.assertEqual('bar2', context.get_value('foo'))
        self.assertEqual(2, context.mod_count)
Exemplo n.º 3
0
class TestCase:
    DEFAULT_NAME = "NO NAME"

    KEYWORD_DICT = {k: v for k, v in TestCaseKeywords.__dict__.items() if not k.startswith('__')}

    def __init__(self, base_url, extract_binds, variable_binds, context=None, config=None):
        self.__base_url = base_url
        self.__url = None
        self.__body = None
        self.__config = config if config else TestCaseConfig()
        self.__auth_username = None
        self.__auth_password = None
        self.__delay = 0
        self.__verbose = False
        self.__ssl_insecure = False
        self.__response_headers = None
        self.__response_code = None
        self.__passed = False
        self.__failure_list = []
        self.__abs_url = False

        self.__header_dict = {}
        self.__http_method = EnumHttpMethod.GET.name
        self.__group = TestCaseGroup.DEFAULT_GROUP
        self.__name = TestCase.DEFAULT_NAME
        self._should_stop_on_failure = False
        self._test_run_delay = 0
        self._auth_type = AuthType.BASIC
        self._curl_options = None
        self.__variable_binds_dict = variable_binds if variable_binds else {}
        self.__generator_binds_dict = {}
        self.__extract_binds_dict = extract_binds if extract_binds else {}
        self.__validator_list = []

        self.__expected_http_status_code_list = [200]
        self.__context = Context() if context is None else context

        self.templates = {}
        self.result = None
        self.config = config

    def __str__(self):
        return json.dumps(self, default=Parser.safe_to_json)

    @property
    def config(self) -> Optional[TestCaseConfig]:
        return self.__config

    @config.setter
    def config(self, config_object: TestCaseConfig):
        if config_object:
            self.variable_binds.update(config_object.variable_binds)
            self.generator_binds.update(config_object.generators)

    @property
    def auth_username(self):
        return self.__auth_username

    @auth_username.setter
    def auth_username(self, username):
        self.__auth_username = Parser.coerce_string_to_ascii(username)

    @property
    def ssl_insecure(self):
        return self.__ssl_insecure

    @ssl_insecure.setter
    def ssl_insecure(self, val):
        self.__ssl_insecure = bool(val)

    @property
    def auth_password(self):
        return self.__auth_password

    @auth_password.setter
    def auth_password(self, password):
        self.__auth_password = Parser.coerce_string_to_ascii(password)

    @property
    def http_method(self):
        return self.__http_method

    @http_method.setter
    def http_method(self, method: str):
        __method = ["GET", "PUT", "POST", "DELETE", "PATCH"]
        if method.upper() not in __method:
            raise HttpMethodError("Method %s is not supported." % method)
        self.__http_method = method.upper()

    @property
    def name(self):
        return self.__name

    @property
    def group(self):
        return self.__group

    @property
    def is_passed(self):
        return bool(self.__passed)

    @property
    def url(self):
        val = self.realize_template("url", self.__context)
        if val is None:
            val = self.__url
        if not self.__abs_url:
            val = urljoin(self.__base_url, val)
        if isinstance(val, dict):
            logger.warning("URL is not applied template values.")
        return val

    @url.setter
    def url(self, value):

        if isinstance(value, dict):
            # this is the templated url , we need to convert it into actual URL
            template_str = Parser.lowercase_keys(value)['template']
            self.set_template("url", Parser.coerce_to_string(template_str))
            self.__url = value
        else:
            self.__url = value

    @property
    def generator_binds(self):
        return self.__generator_binds_dict

    @property
    def delay(self):
        return self.__delay

    @generator_binds.setter
    def generator_binds(self, value: Dict):
        binds_dict = Parser.flatten_dictionaries(value)
        __binds_dict = {str(k): str(v) for k, v in binds_dict.items()}
        self.__generator_binds_dict.update(__binds_dict)

    @property
    def variable_binds(self):
        return self.__variable_binds_dict

    @property
    def extract_binds(self):
        return self.__extract_binds_dict

    @extract_binds.setter
    def extract_binds(self, bind_dict):

        bind_dict = Parser.flatten_dictionaries(bind_dict)

        for variable_name, extractor in bind_dict.items():

            if not isinstance(extractor, dict) or len(extractor) == 0:
                raise BindError("Extractors must be defined as maps of extractorType:{configs} with 1 entry")
            if len(extractor) > 1:
                raise BindError("Cannot define multiple extractors for given variable name")
            for extractor_type, extractor_config in extractor.items():
                self.__extract_binds_dict[variable_name] = parse_extractor(extractor_type, extractor_config)

    @property
    def expected_http_status_code_list(self):
        return [int(x) for x in self.__expected_http_status_code_list]

    @expected_http_status_code_list.setter
    def expected_http_status_code_list(self, value):
        self.__expected_http_status_code_list = value

    @property
    def validators(self):
        return self.__validator_list

    @validators.setter
    def validators(self, validator_list):
        if not isinstance(validator_list, list):
            raise ValidatorError('Misconfigured validator section, must be a list of validators')
        for validator in validator_list:
            if not isinstance(validator, dict):
                raise ValidatorError("Validators must be defined as validatorType:{configs} ")
            for validator_type, validator_config in validator.items():
                validator = parse_validator(validator_type, validator_config)
                self.__validator_list.append(validator)

    @property
    def headers(self) -> Dict:
        # if not self.templates.get('headers'):
        #     return self.__header_dict
        context_values = self.__context.get_values()
        header_dict = {}
        for key, header in self.__header_dict.items():
            if isinstance(header, dict):
                if key == 'template':
                    for k, v in header.items():
                        templated_string = string.Template(v).safe_substitute(context_values)
                        header_dict[k] = templated_string
                    continue
                templated_value = header.get('template')
                if templated_value:
                    templated_string = string.Template(templated_value).safe_substitute(context_values)
                    header_dict[key] = templated_string
                else:
                    logger.warning("Skipping the header: %s. We don't support mapping as header" % header)
            else:
                header_dict[key] = header

        return header_dict

    @headers.setter
    def headers(self, headers):
        config_value = Parser.flatten_dictionaries(headers)
        if isinstance(config_value, dict):
            for key, value in config_value.items():
                if isinstance(value, dict):
                    if value.get('template'):
                        self.set_template("headers", value.get('template'))
            self.__header_dict.update(config_value)
        else:
            raise ValidatorError("Illegal header type: headers must be a dictionary or list of dictionary keys")

    def set_template(self, variable_name, template_string):
        self.templates[variable_name] = string.Template(str(template_string))

    @property
    def body(self):
        if isinstance(self.__body, str) or self.__body is None:
            return self.__body
        return self.__body.get_content(context=self.__context)

    @body.setter
    def body(self, value):
        if value:
            if isinstance(value, bytes):
                self.__body = ContentHandler.parse_content(value.decode())
            elif isinstance(value, str):
                self.__body = ContentHandler.parse_content(value)
            else:
                self.__body = value
        else:
            self.__body = value

    @property
    def failures(self):
        return self.__failure_list

    def realize_template(self, variable_name, context):
        if (context or self.templates) is None or (variable_name not in self.templates):
            return None
        if not context.get_values():
            return None
        val = self.templates[variable_name].safe_substitute(context.get_values())
        return val

    def parse(self, testcase_dict):
        testcase_dict = Parser.flatten_lowercase_keys_dict(testcase_dict)

        for keyword in TestCase.KEYWORD_DICT.keys():
            value = testcase_dict.get(keyword)
            if value is None:
                continue

            if keyword == TestCaseKeywords.auth_username:
                self.auth_username = value
            elif keyword == TestCaseKeywords.auth_password:
                self.auth_password = value
            elif keyword == TestCaseKeywords.method:
                self.http_method = value
            elif keyword == TestCaseKeywords.delay:
                self.__delay = int(value)
            elif keyword == TestCaseKeywords.group:
                self.__group = value
            elif keyword == TestCaseKeywords.name:
                self.__name = value
            elif keyword == TestCaseKeywords.url:
                self.url = value
            elif keyword == TestCaseKeywords.extract_binds:
                self.extract_binds = value
            elif keyword == TestCaseKeywords.validators:
                self.validators = value
            elif keyword == TestCaseKeywords.headers:
                self.headers = value
            elif keyword == TestCaseKeywords.variable_binds:
                self.__variable_binds_dict = Parser.flatten_dictionaries(value)
            elif keyword == TestCaseKeywords.generator_binds:
                self.__generator_binds_dict = {str(k): str(v) for k, v in Parser.flatten_dictionaries(value)}
            elif keyword == TestCaseKeywords.options:
                raise NotImplementedError("Yet to Support")
            elif keyword == TestCaseKeywords.body:
                self.body = value
            elif keyword == TestCaseKeywords.absolute_urls:
                self.__abs_url = Parser.safe_to_bool(value)

        expected_status = testcase_dict.get(TestCaseKeywords.expected_status, [])
        if expected_status:
            self.expected_http_status_code_list = expected_status
        else:
            if self.http_method in ["POST", "PUT", "DELETE"]:
                self.expected_http_status_code_list = [200, 201, 204]

    def pre_update(self, context):
        if self.variable_binds:
            context.bind_variables(self.variable_binds)
        if self.generator_binds:
            for key, value in self.generator_binds.items():
                context.bind_generator_next(key, value)

    def post_update(self, context):
        if self.extract_binds:
            for key, value in self.extract_binds.items():
                result = value.extract(
                    body=self.body, headers=self.headers, context=context)
                if result:
                    context.bind_variable(key, result)

    def is_dynamic(self):
        if self.templates or (isinstance(self.__body, ContentHandler) and self.__body.is_dynamic()):
            return True
        return False

    def render(self):
        if self.is_dynamic() or self.__context is not None:
            if isinstance(self.__body, ContentHandler):
                self.__body = self.__body.get_content(self.__context)

    def __perform_validation(self) -> List:

        failure_list = []
        for validator in self.validators:
            logger.debug("Running validator: %s" % validator.name)
            validate_result = validator.validate(body=self.body, headers=self.headers, context=self.__context)
            if not validate_result:
                self.__passed = False
            if hasattr(validate_result, 'details'):
                failure_list.append(validate_result)

        return failure_list

    def run(self, context=None, timeout=None, curl_handler=None):

        if context is None:
            context = self.__context

        self.pre_update(context)
        self.render()
        if timeout is None:
            timeout = DEFAULT_TIMEOUT

        if curl_handler:

            try:  # Check the curl handle isn't closed, and reuse it if possible
                curl_handler.getinfo(curl_handler.HTTP_CODE)
                # Below clears the cookies & curl options for clean run
                # But retains the DNS cache and connection pool
                curl_handler.reset()
                curl_handler.setopt(curl_handler.COOKIELIST, "ALL")
            except pycurl.error:
                curl_handler = pycurl.Curl()
                curl_handler.setopt(pycurl.CAINFO, certifi.where())  # Fix for #29
                curl_handler.setopt(pycurl.FOLLOWLOCATION, 1)  # Support for HTTP 301
        else:
            curl_handler = pycurl.Curl()

        body_byte, header_byte = self.__default_curl_config(curl_handler, timeout)
        if self.config.timeout:
            curl_handler.setopt(pycurl.CONNECTTIMEOUT, self.config.timeout)

        if self.__ssl_insecure:
            curl_handler.setopt(pycurl.SSL_VERIFYPEER, 0)
            curl_handler.setopt(pycurl.SSL_VERIFYHOST, 0)

        if self.body:
            logger.debug("Request body %s" % self.body)
            curl_handler.setopt(curl_handler.READFUNCTION, BytesIO(bytes(self.body, 'utf-8')).read)

        if self.auth_username and self.auth_password:
            curl_handler.setopt(pycurl.USERPWD, self.auth_username + ':' + self.auth_password)

        self.__configure_curl_method(curl_handler)

        head = self.headers
        self.__configure_curl_headers(curl_handler, head)

        if self.__delay:
            time.sleep(self.__delay)
        try:
            logger.info("Hitting %s" % self.url)
            curl_handler.perform()
        except pycurl.error as e:
            logger.error("Unknown Exception", exc_info=True)
            self.__passed = False
            curl_handler.close()
            trace = traceback.format_exc()
            self.__failure_list.append(
                Failure(message="Curl Exception: {0}".format(e), details=trace, failure_type=FAILURE_CURL_EXCEPTION))
            return
        self.body = body_byte.getvalue()
        body_byte.close()
        response_code = curl_handler.getinfo(pycurl.RESPONSE_CODE)
        self.__response_code = int(response_code)
        if self.config.print_bodies:
            print(self.body)
        try:
            response_headers = Parser.parse_headers(header_byte.getvalue())
            self.__response_headers = response_headers
            logger.debug("RESPONSE HEADERS: %s" % self.__response_headers)
            header_byte.close()

        except Exception as e:  # Need to catch the expected exception
            trace = traceback.format_exc()
            self.__failure_list.append(Failure(
                message="Header parsing exception: {0}".format(e), details=trace, failure_type=FAILURE_TEST_EXCEPTION)
            )
            self.__passed = False
            curl_handler.close()
            return

        if self.__response_code in self.expected_http_status_code_list:
            self.__passed = True
            self.__failure_list.extend(self.__perform_validation())
            self.post_update(context)
        else:
            self.__passed = False
            failure_message = "Invalid HTTP response code: response code {0} not in expected codes {1}".format(
                self.__response_code, self.expected_http_status_code_list
            )
            self.__failure_list.append(
                Failure(message=failure_message, details=None, failure_type=FAILURE_INVALID_RESPONSE)
            )
        curl_handler.close()

    @staticmethod
    def __configure_curl_headers(curl_handler, head):
        if head.get('content-type'):
            content_type = head['content-type']
            head[u'content-type'] = '%s ; charset=UTF-8' % content_type
        headers = ["%s:%s" % (header_name, header_value) for header_name, header_value in head.items()]
        headers.append("Expect:")
        headers.append("Connection: close")
        logger.debug("Request headers %s " % head)
        curl_handler.setopt(curl_handler.HTTPHEADER, headers)

    def __configure_curl_method(self, curl_handler):
        body_length = len(self.body) if self.body else 0
        if self.http_method == EnumHttpMethod.POST.name:
            curl_handler.setopt(EnumHttpMethod.POST.value, 1)
            curl_handler.setopt(pycurl.POSTFIELDSIZE, body_length)

        elif self.http_method == EnumHttpMethod.PUT.name:
            curl_handler.setopt(EnumHttpMethod.PUT.value, 1)
            curl_handler.setopt(pycurl.INFILESIZE, body_length)

        elif self.http_method == EnumHttpMethod.PATCH.name:
            curl_handler.setopt(EnumHttpMethod.PATCH.value, EnumHttpMethod.PATCH.name)
            curl_handler.setopt(pycurl.POSTFIELDS, self.body)

        elif self.http_method == EnumHttpMethod.DELETE.name:
            curl_handler.setopt(EnumHttpMethod.DELETE.value, EnumHttpMethod.DELETE.name)
            if self.body:
                curl_handler.setopt(pycurl.POSTFIELDS, self.body)
                curl_handler.setopt(pycurl.POSTFIELDSIZE, body_length)

        elif self.http_method == EnumHttpMethod.HEAD.name:
            curl_handler.setopt(pycurl.NOBODY, 1)
            curl_handler.setopt(EnumHttpMethod.HEAD.value, EnumHttpMethod.HEAD.name)
        else:
            curl_handler.setopt(pycurl.CUSTOMREQUEST, self.http_method.upper())
            if self.body:
                curl_handler.setopt(pycurl.POSTFIELDS, self.body)
                curl_handler.setopt(pycurl.POSTFIELDSIZE, body_length)

    def __default_curl_config(self, curl_handler, timeout):
        body_byte = BytesIO()
        header_byte = BytesIO()
        curl_handler.setopt(curl_handler.URL, str(self.url))
        curl_handler.setopt(curl_handler.TIMEOUT, timeout)
        curl_handler.setopt(pycurl.WRITEFUNCTION, body_byte.write)
        curl_handler.setopt(pycurl.HEADERFUNCTION, header_byte.write)
        curl_handler.setopt(pycurl.VERBOSE, self.__verbose)
        return body_byte, header_byte