import os from enum import Enum from typing import Any, Callable, Dict, List, Text, Union from pydantic import BaseModel, Field, HttpUrl Name = Text Url = Text BaseUrl = Union[HttpUrl, Text] VariablesMapping = Dict[Text, Any] FunctionsMapping = Dict[Text, Callable] Headers = Dict[Text, Text] Cookies = Dict[Text, Text] Verify = bool Hooks = List[Union[Text, Dict[Text, Text]]] Export = List[Text] Validators = List[Dict] Env = Dict[Text, Any] class MethodEnum(Text, Enum): GET = "GET" POST = "POST" PUT = "PUT" DELETE = "DELETE" HEAD = "HEAD" OPTIONS = "OPTIONS" PATCH = "PATCH" # configs for thrift rpc class TConfigThrift(BaseModel): psm: Text = None env: Text = None cluster: Text = None target: Text = None include_dirs: List[Text] = None thrift_client: Any = None class TConfig(BaseModel): name: Name verify: Verify = False base_url: BaseUrl = "" # Text: prepare variables in debugtalk.py, ${gen_variables()} variables: Union[VariablesMapping, Text] = {} parameters: Union[VariablesMapping, Text] = {} # setup_hooks: Hooks = [] # teardown_hooks: Hooks = [] export: Export = [] path: Text = None # configs for other protocols thrift: TConfigThrift = None class TRequest(BaseModel): """requests.Request model""" method: MethodEnum url: Url params: Dict[Text, Text] = {} headers: Headers = {} req_json: Union[Dict, List, Text] = Field(None, alias="json") data: Union[Text, Dict[Text, Any]] = None cookies: Cookies = {} timeout: float = 120 allow_redirects: bool = True verify: Verify = False upload: Dict = {} # used for upload files class TStep(BaseModel): name: Name request: Union[TRequest, None] = None testcase: Union[Text, Callable, None] = None variables: VariablesMapping = {} setup_hooks: Hooks = [] teardown_hooks: Hooks = [] # used to extract request's response field extract: VariablesMapping = {} # used to export session variables from referenced testcase export: Export = [] validators: Validators = Field([], alias="validate") validate_script: List[Text] = [] retry_times: int = 0 retry_interval: int = 0 # sec class TestCase(BaseModel): config: TConfig teststeps: List[TStep] class ProjectMeta(BaseModel): debugtalk_py: Text = "" # debugtalk.py file content debugtalk_path: Text = "" # debugtalk.py file path dot_env_path: Text = "" # .env file path functions: FunctionsMapping = {} # functions defined in debugtalk.py env: Env = {} RootDir: Text = os.getcwd() # project root directory (ensure absolute), the path debugtalk.py located class TestsMapping(BaseModel): project_meta: ProjectMeta testcases: List[TestCase] class TestCaseTime(BaseModel): start_at: float = 0 start_at_iso_format: Text = "" duration: float = 0 class TestCaseInOut(BaseModel): config_vars: VariablesMapping = {} export_vars: Dict = {} class RequestStat(BaseModel): content_size: float = 0 response_time_ms: float = 0 elapsed_ms: float = 0 class AddressData(BaseModel): client_ip: Text = "N/A" client_port: int = 0 server_ip: Text = "N/A" server_port: int = 0 class RequestData(BaseModel): method: MethodEnum = MethodEnum.GET url: Url headers: Headers = {} cookies: Cookies = {} body: Union[Text, bytes, List, Dict, None] = {} class ResponseData(BaseModel): status_code: int headers: Dict cookies: Cookies encoding: Union[Text, None] = None content_type: Text body: Union[Text, bytes, List, Dict] class ReqRespData(BaseModel): request: RequestData response: ResponseData class SessionData(BaseModel): """request session data, including request, response, validators and stat data""" success: bool = False # in most cases, req_resps only contains one request & response # while when 30X redirect occurs, req_resps will contain multiple request & response req_resps: List[ReqRespData] = [] stat: RequestStat = RequestStat() address: AddressData = AddressData() validators: Dict = {} class StepResult(BaseModel): """teststep data, each step maybe corresponding to one request or one testcase""" name: Text = "" # teststep name step_type: Text = "" # teststep type, request or testcase success: bool = False data: Union[SessionData, List['StepResult']] = None elapsed: float = 0.0 # teststep elapsed time content_size: float = 0 # response content size export_vars: VariablesMapping = {} attachment: Text = "" # teststep attachment StepResult.update_forward_refs() class IStep(object): def name(self) -> str: raise NotImplementedError def type(self) -> str: raise NotImplementedError def struct(self) -> TStep: raise NotImplementedError def run(self, runner) -> StepResult: # runner: HttpRunner raise NotImplementedError class TestCaseSummary(BaseModel): name: Text success: bool case_id: Text time: TestCaseTime in_out: TestCaseInOut = {} log: Text = "" step_results: List[StepResult] = [] class PlatformInfo(BaseModel): httprunner_version: Text python_version: Text platform: Text class TestCaseRef(BaseModel): name: Text base_url: Text = "" testcase: Text variables: VariablesMapping = {} class TestSuite(BaseModel): config: TConfig testcases: List[TestCaseRef] class Stat(BaseModel): total: int = 0 success: int = 0 fail: int = 0 class TestSuiteSummary(BaseModel): success: bool = False stat: Stat = Stat() time: TestCaseTime = TestCaseTime() platform: PlatformInfo testcases: List[TestCaseSummary]