init v3: make http request without validation

This commit is contained in:
debugtalk
2020-04-19 12:47:28 +08:00
parent f2e09e5bba
commit 8b7291fa08
9 changed files with 436 additions and 0 deletions

0
examples/__init__.py Normal file
View File

View File

View File

@@ -0,0 +1,79 @@
from httprunner.v3.runner import TestCaseRunner
from httprunner.v3.schema import TestsConfig, TestStep
class TestCaseRequestMethodsHardcode(TestCaseRunner):
config = TestsConfig(**{
"name": "request methods testcase in hardcode",
"base_url": "https://postman-echo.com",
"verify": False
})
teststeps = [
TestStep(**{
"name": "get with params",
"request": {
"method": "GET",
"url": "/get",
"params": {
"foo1": "bar1",
"foo2": "bar2"
},
"headers": {
"User-Agent": "HttpRunner/3.0"
}
},
"validate": [
{"eq": ["status_code", 200]}
]
}),
TestStep(**{
"name": "post raw text",
"request": {
"method": "POST",
"url": "/post",
"data": "This is expected to be sent back as part of response body.",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "text/plain"
}
},
"validate": [
{"eq": ["status_code", 200]}
]
}),
TestStep(**{
"name": "post form data",
"request": {
"method": "POST",
"url": "/post",
"data": "foo1=bar1&foo2=bar2",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "application/x-www-form-urlencoded"
}
},
"validate": [
{"eq": ["status_code", 200]}
]
}),
TestStep(**{
"name": "put request",
"request": {
"method": "PUT",
"url": "/put",
"data": "This is expected to be sent back as part of response body.",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "text/plain"
}
},
"validate": [
{"eq": ["status_code", 200]}
]
})
]
if __name__ == '__main__':
TestCaseRequestMethodsHardcode().run()

View File

View File

@@ -0,0 +1,81 @@
""" failure type exceptions
these exceptions will mark test as failure
"""
class MyBaseFailure(Exception):
pass
class ParseTestsFailure(MyBaseFailure):
pass
class ValidationFailure(MyBaseFailure):
pass
class ExtractFailure(MyBaseFailure):
pass
class SetupHooksFailure(MyBaseFailure):
pass
class TeardownHooksFailure(MyBaseFailure):
pass
""" error type exceptions
these exceptions will mark test as error
"""
class MyBaseError(Exception):
pass
class FileFormatError(MyBaseError):
pass
class ParamsError(MyBaseError):
pass
class NotFoundError(MyBaseError):
pass
class FileNotFound(FileNotFoundError, NotFoundError):
pass
class FunctionNotFound(NotFoundError):
pass
class VariableNotFound(NotFoundError):
pass
class EnvNotFound(NotFoundError):
pass
class CSVNotFound(NotFoundError):
pass
class ApiNotFound(NotFoundError):
pass
class TestcaseNotFound(NotFoundError):
pass
class SummaryEmpty(MyBaseError):
""" test result summary data is empty
"""

View File

@@ -0,0 +1,178 @@
import re
from typing import Any, Set
from typing import Dict
from httprunner.v3.exceptions import ParamsError
absolute_http_url_regexp = re.compile(r"^https?://", re.I)
# use $$ to escape $ notation
dolloar_regex_compile = re.compile(r"\$\$")
# variable notation, e.g. ${var} or $var
variable_regex_compile = re.compile(r"\$\{(\w+)\}|\$(\w+)")
# function notation, e.g. ${func1($var_1, $var_3)}
function_regex_compile = re.compile(r"\$\{(\w+)\(([\$\w\.\-/\s=,]*)\)\}")
def build_url(base_url, path):
""" prepend url with base_url unless it's already an absolute URL """
if absolute_http_url_regexp.match(path):
return path
elif base_url:
return "{}/{}".format(base_url.rstrip("/"), path.lstrip("/"))
else:
raise ParamsError("base url missed!")
def regex_findall_variables(content):
""" extract all variable names from content, which is in format $variable
Args:
content (str): string content
Returns:
list: variables list extracted from string content
Examples:
>>> regex_findall_variables("$variable")
["variable"]
>>> regex_findall_variables("/blog/$postid")
["postid"]
>>> regex_findall_variables("/$var1/$var2")
["var1", "var2"]
>>> regex_findall_variables("abc")
[]
"""
try:
vars_list = []
for var_tuple in variable_regex_compile.findall(content):
vars_list.append(
var_tuple[0] or var_tuple[1]
)
return vars_list
except TypeError:
return []
def extract_variables(content: Any) -> Set:
""" extract all variables in content recursively.
"""
if isinstance(content, (list, set, tuple)):
variables = set()
for item in content:
variables = variables | extract_variables(item)
return variables
elif isinstance(content, dict):
variables = set()
for key, value in content.items():
variables = variables | extract_variables(value)
return variables
elif isinstance(content, str):
return set(regex_findall_variables(content))
return set()
def parse_string_variables(content, variables_mapping):
""" parse string content with variables mapping.
Args:
content (str): string content to be parsed.
variables_mapping (dict): variables mapping.
Returns:
str: parsed string content.
Examples:
>>> content = "/api/users/$uid"
>>> variables_mapping = {"$uid": 1000}
>>> parse_string_variables(content, variables_mapping)
"/api/users/1000"
"""
variables_list = extract_variables(content)
for variable_name in variables_list:
variable_value = variables_mapping[variable_name]
# TODO: replace variable label from $var to {{var}}
if "${}".format(variable_name) == content:
# content is a variable
content = variable_value
else:
# content contains one or several variables
if not isinstance(variable_value, str):
variable_value = str(variable_value)
content = content.replace(
"${}".format(variable_name),
variable_value, 1
)
return content
def parse_content(content: Any, variables_mapping: Dict[str, Any] = None, functions_mapping=None):
""" parse content with evaluated variables mapping.
Notice: variables_mapping should not contain any variable or function.
"""
# TODO: refactor type check
if content is None or isinstance(content, (int, float, bool)):
return content
elif isinstance(content, str):
# content is in string format here
variables_mapping = variables_mapping or {}
functions_mapping = functions_mapping or {}
content = content.strip()
# replace functions with evaluated value
# Notice: _eval_content_functions must be called before _eval_content_variables
# content = parse_string_functions(content, variables_mapping, functions_mapping)
# replace variables with binding value
content = parse_string_variables(content, variables_mapping)
return content
elif isinstance(content, (list, set, tuple)):
return [
parse_content(item, variables_mapping)
for item in content
]
elif isinstance(content, dict):
parsed_content = {}
for key, value in content.items():
parsed_key = parse_content(key, variables_mapping)
parsed_value = parse_content(value, variables_mapping)
parsed_content[parsed_key] = parsed_value
return parsed_content
return content
def parse_variables_mapping(variables_mapping: Dict[str, Any]):
parsed_variables: Dict[str, Any] = {}
while len(parsed_variables) != len(variables_mapping):
for var_name in variables_mapping:
var_value = variables_mapping[var_name]
# variables = extract_variables(var_value)
if var_name in parsed_variables:
continue
parsed_value = parse_content(var_value, parsed_variables)
parsed_variables[var_name] = parsed_value
return parsed_variables

View File

@@ -0,0 +1,38 @@
from typing import List
import requests
from httprunner.v3.parser import build_url
from httprunner.v3.schema import TestsConfig, TestStep
class TestCaseRunner(object):
config: TestsConfig = {}
teststeps: List[TestStep] = []
session: requests.Session = None
def with_session(self, s: requests.Session) -> "TestCaseRunner":
self.session = s
return self
def with_variables(self, **variables) -> "TestCaseRunner":
self.config.variables.update(variables)
return self
def run_step(self, step):
request_dict = step.request.dict()
method = request_dict.pop("method")
url_path = request_dict.pop("url")
url = build_url(self.config.base_url, url_path)
request_dict["json"] = request_dict.pop("req_json", {})
session = self.session or requests.Session()
resp = session.request(method, url, **request_dict)
def run(self):
for step in self.teststeps:
step.variables.update(self.config.variables)
self.run_step(step)

View File

@@ -0,0 +1,60 @@
from enum import Enum
from typing import Any
from typing import Dict, List, Text, Union
from pydantic import BaseModel, Field
from pydantic import HttpUrl
Name = Text
Url = Text
BaseUrl = Union[HttpUrl, Text]
Variables = Dict[Text, Any]
Headers = Dict[Text, Text]
Verify = bool
Hook = List[Text]
Export = List[Text]
Validate = List[Dict]
Env = Dict[Text, Any]
class MethodEnum(Text, Enum):
GET = 'GET'
POST = 'POST'
PUT = "PUT"
DELETE = "DELETE"
HEAD = "HEAD"
OPTIONS = "OPTIONS"
PATCH = "PATCH"
CONNECT = "CONNECT"
TRACE = "TRACE"
class TestsConfig(BaseModel):
name: Name
verify: Verify = False
base_url: BaseUrl = ""
variables: Variables = {}
setup_hooks: Hook = []
teardown_hooks: Hook = []
export: Export = []
class Request(BaseModel):
method: MethodEnum = MethodEnum.GET
url: Url
params: Dict[Text, Text] = {}
headers: Headers = {}
req_json: Dict = Field({}, alias="json")
data: Union[Text, Dict[Text, Any]] = ""
cookies: Dict[Text, Text] = {}
timeout: int = 120
allow_redirects: bool = True
verify: Verify = False
class TestStep(BaseModel):
name: Name
request: Request
variables: Variables = {}
extract: Union[Dict[Text, Text], List[Text]] = {}
validation: Validate = Field([], alias="validate")