diff --git a/httpfpt/common/send_request.py b/httpfpt/common/send_request.py index 9315384..7228d0d 100644 --- a/httpfpt/common/send_request.py +++ b/httpfpt/common/send_request.py @@ -139,7 +139,7 @@ def send_request( raise SendRequestError('请求发起失败,请使用合法的请求引擎') # 获取解析后的请求数据 - log.info('开始解析请求数据...' if not relate_log else '开始解析关联请求数据...') + log.info('开始解析用例数据...' if not relate_log else '开始解析关联用例数据...') try: request_data_parse = RequestDataParse(request_data, request_engin) parsed_data = request_data_parse.get_request_data_parsed(relate_log) @@ -147,9 +147,9 @@ def send_request( raise e except Exception as e: if not relate_log: - log.error(f'请求数据解析失败: {e}') + log.error(f'用例数据解析失败: {e}') raise e - log.info('请求数据解析完成' if not relate_log else '关联请求数据解析完成') + log.info('用例数据解析完成' if not relate_log else '关联用例数据解析完成') # 记录请求前置数据; 此处数据中如果包含关联用例变量, 不会被替换为结果记录, 因为替换动作还未发生 setup = parsed_data['setup'] @@ -169,7 +169,8 @@ def send_request( if relate_parsed_data: parsed_data = relate_parsed_data elif key == SetupType.SQL: - mysql_client.exec_case_sql(value, parsed_data['env']) + sql = var_extractor.vars_replace({'sql': value}, parsed_data['env'])['sql'] + mysql_client.exec_case_sql(sql, parsed_data['env']) elif key == SetupType.HOOK: hook_executor.exec_hook_func(value) elif key == SetupType.WAIT_TIME: @@ -212,6 +213,11 @@ def send_request( request_data_parsed.update({'content': request_data_parsed.pop('body')}) else: request_data_parsed.update({'data': request_data_parsed.pop('body')}) + try: + request_data_parsed = var_extractor.vars_replace(request_data_parsed, parsed_data['env']) + except Exception as e: + log.error(e) + raise e # 发送请求 response_data = self.init_response_metadata @@ -255,13 +261,17 @@ def send_request( for key, value in item.items(): if value is not None: if key == TeardownType.SQL: - mysql_client.exec_case_sql(value, parsed_data['env']) + sql = var_extractor.vars_replace({'sql': value}, parsed_data['env'])['sql'] + mysql_client.exec_case_sql(sql, parsed_data['env']) if key == TeardownType.HOOK: hook_executor.exec_hook_func(value) if key == TeardownType.EXTRACT: var_extractor.teardown_var_extract(response_data, value, parsed_data['env']) if key == TeardownType.ASSERT: - asserter.exec_asserter(response_data, assert_text=value) + assert_text = var_extractor.vars_replace( + target={'assert_text': value}, env=parsed_data['env'] + )['assert_text'] + asserter.exec_asserter(response_data, assert_text) elif key == TeardownType.WAIT_TIME: log.info(f'执行请求后等待:{value} s') time.sleep(value) diff --git a/httpfpt/db/mysql_db.py b/httpfpt/db/mysql_db.py index a65e5d7..42b6de9 100644 --- a/httpfpt/db/mysql_db.py +++ b/httpfpt/db/mysql_db.py @@ -10,20 +10,14 @@ import pymysql from dbutils.pooled_db import PooledDB -from jsonpath import findall -from httpfpt.common.env_handler import write_env_vars -from httpfpt.common.errors import JsonPathFindError, SQLSyntaxError, VariableError +from httpfpt.common.errors import SQLSyntaxError from httpfpt.common.log import log -from httpfpt.common.variable_cache import variable_cache -from httpfpt.common.yaml_handler import write_yaml_vars from httpfpt.core.get_conf import config -from httpfpt.core.path_conf import RUN_ENV_PATH from httpfpt.enums.query_fetch_type import QueryFetchType from httpfpt.enums.sql_type import SqlType -from httpfpt.enums.var_type import VarType from httpfpt.utils.enum_control import get_enum_values -from httpfpt.utils.request.vars_extractor import var_extractor +from httpfpt.utils.request.vars_recorder import record_variables class MysqlDB: @@ -134,18 +128,16 @@ def execute(self, sql: str) -> int: finally: self.close(conn, cursor) - def exec_case_sql(self, sql: str, env_filename: str | None = None) -> dict | list | int | None: + def exec_case_sql(self, sql: str, env: str | None = None) -> dict | list | int | None: """ 执行用例 sql :param sql: - :param env_filename: + :param env: :return: """ # 获取返回数据 if isinstance(sql, str): - if env_filename is not None: - sql = var_extractor.vars_replace({'sql': sql}, env_filename)['sql'] log.info(f'执行 SQL: {sql}') if sql.startswith(SqlType.select): return self.query(sql) @@ -159,25 +151,10 @@ def exec_case_sql(self, sql: str, env_filename: str | None = None) -> dict | lis set_type = sql['type'] sql_text = sql['sql'] json_path = sql['jsonpath'] - if env_filename is not None: - sql_text = var_extractor.vars_replace({'sql': sql_text}, env_filename)['sql'] query_data = self.query(sql_text) if not query_data: raise SQLSyntaxError('变量提取失败,SQL 查询结果为空') - value = findall(json_path, query_data) - if not value: - raise JsonPathFindError(f'jsonpath 取值失败, 表达式: {json_path}') - value_str = str(value[0]) - if set_type == VarType.CACHE: - variable_cache.set(key, value_str) - elif set_type == VarType.ENV: - write_env_vars(RUN_ENV_PATH, env_filename, key, value_str) # type: ignore - elif set_type == VarType.GLOBAL: - write_yaml_vars({key: value_str}) - else: - raise VariableError( - f'前置 SQL 设置变量失败, 用例参数 "type: {set_type}" 值错误, 请使用 cache / env / global' - ) + record_variables(json_path, query_data, key, set_type, env) # type: ignore @staticmethod def sql_verify(sql: str) -> None: diff --git a/httpfpt/utils/request/request_data_parse.py b/httpfpt/utils/request/request_data_parse.py index 8d8c33b..c1ac7a6 100644 --- a/httpfpt/utils/request/request_data_parse.py +++ b/httpfpt/utils/request/request_data_parse.py @@ -39,7 +39,10 @@ def _error_msg(info: str) -> str: class RequestDataParse: def __init__(self, request_data: dict, request_engin: str): - self.request_data = var_extractor.vars_replace(hook_executor.hook_func_value_replace(request_data)) + self.request_data = var_extractor.vars_replace( + hook_executor.hook_func_value_replace(request_data), + exception=False, + ) self.request_engin = request_engin self._is_run() # put bottom diff --git a/httpfpt/utils/request/vars_extractor.py b/httpfpt/utils/request/vars_extractor.py index 2908e90..0de35ea 100644 --- a/httpfpt/utils/request/vars_extractor.py +++ b/httpfpt/utils/request/vars_extractor.py @@ -6,15 +6,13 @@ import os.path import re -from jsonpath import findall - -from httpfpt.common.env_handler import get_env_dict, write_env_vars -from httpfpt.common.errors import JsonPathFindError, RequestDataParseError, VariableError +from httpfpt.common.env_handler import get_env_dict +from httpfpt.common.errors import RequestDataParseError, VariableError from httpfpt.common.log import log from httpfpt.common.variable_cache import variable_cache -from httpfpt.common.yaml_handler import read_yaml, write_yaml_vars +from httpfpt.common.yaml_handler import read_yaml from httpfpt.core.path_conf import RUN_ENV_PATH, TEST_DATA_PATH -from httpfpt.enums.var_type import VarType +from httpfpt.utils.request.vars_recorder import record_variables class VarsExtractor: @@ -26,61 +24,56 @@ def __init__(self) -> None: self.vars_re = re.compile(r'\${([a-zA-Z_]\w*)}|(? dict: + def vars_replace(self, target: dict, env: str | None = None, exception: bool = True) -> dict: """ 变量替换 :param target: - :param env_filename: + :param env: + :param exception: :return: """ str_target = json.dumps(target, ensure_ascii=False) - match = self.vars_re.search(str_target) or self.sql_vars_re.search(str_target) + match = self.vars_re.search(str_target) if not match: return target # 获取环境名称 - env = env_filename or target.get('config', {}).get('request', {}).get('env') - if not env or not isinstance(env, str): + _env = env or target.get('config', {}).get('request', {}).get('env') + if not _env or not isinstance(_env, str): raise RequestDataParseError('运行环境获取失败, 测试用例数据缺少 config:request:env 参数') try: - env_file = os.path.join(RUN_ENV_PATH, env) + env_file = os.path.join(RUN_ENV_PATH, _env) env_vars = get_env_dict(env_file) except OSError: raise RequestDataParseError('运行环境获取失败, 请检查测试用例环境配置') - # 获取全局变量 - global_vars = read_yaml(TEST_DATA_PATH, filename='global_vars.yaml') - - # 获取 re 规则字符串 - var_re = self.sql_vars_re if env_filename else self.vars_re + # 执行变量替换 + var_re = self.vars_re for match in var_re.finditer(str_target): var_key = match.group(1) or match.group(2) if var_key is not None: - log_type = '请求数据' try: # 设置默认值为特殊字符, 避免变量值为 None 时错误判断 default = '`AE86`' # 替换: 临时变量 > 环境变量 > 全局变量 cache_value = variable_cache.get(var_key, default=default) if cache_value == default: + global_vars = read_yaml(TEST_DATA_PATH, filename='global_vars.yaml') var_value = env_vars.get(var_key.upper(), global_vars.get(var_key, default)) if var_value != default: - if env_filename is not None: - log_type = 'SQL ' str_target = var_re.sub(str(var_value), str_target, 1) - log.info(f'{log_type}变量 {var_key} 替换完成') + log.info(f'用例数据变量 {var_key} 替换完成') else: raise VariableError(var_key) else: str_target = var_re.sub(str(cache_value), str_target, 1) - log.info(f'{log_type}变量 {var_key} 替换完成') + log.info(f'用例数据变量 {var_key} 替换完成') except Exception as e: - raise VariableError(f'{log_type}变量 {var_key} 替换失败: {e}') + if exception: + raise VariableError(f'用例数据变量 {var_key} 替换失败: {e}') dict_target = json.loads(str_target) @@ -110,11 +103,11 @@ def relate_vars_replace(self, target: dict) -> dict: if cache_value != default: try: str_target = self.relate_vars_re.sub(str(cache_value), str_target, 1) - log.info(f'请求数据关联变量 {var_key} 替换完成') + log.info(f'用例数据关联变量 {var_key} 替换完成') except Exception as e: - raise VariableError(f'请求数据关联变量 {var_key} 替换失败: {e}') + raise VariableError(f'用例数据关联变量 {var_key} 替换失败: {e}') else: - raise VariableError(f'请求数据关联变量替换失败,临时变量池不存在变量: "{var_key}"') + raise VariableError(f'用例数据关联变量替换失败,临时变量池不存在变量: "{var_key}"') log.info('关联测试用例变量替换完毕') # TODO: https://github.com/StKali/cache3/issues/18 @@ -142,20 +135,7 @@ def teardown_var_extract(response: dict, extract: dict, env: str) -> None: key = extract['key'] set_type = extract['type'] json_path = extract['jsonpath'] - value = findall(json_path, response) - if not value: - raise JsonPathFindError(f'jsonpath 取值失败, 表达式: {json_path}') - value_str = str(value[0]) - if set_type == VarType.CACHE: - variable_cache.set(key, value_str) - elif set_type == VarType.ENV: - write_env_vars(RUN_ENV_PATH, env, key, value_str) - elif set_type == VarType.GLOBAL: - write_yaml_vars({key: value_str}) - else: - raise VariableError( - f'前置 SQL 设置变量失败, 用例参数 "type: {set_type}" 值错误, 请使用 cache / env / global' - ) + record_variables(json_path, response, key, set_type, env) var_extractor = VarsExtractor() diff --git a/httpfpt/utils/request/vars_recorder.py b/httpfpt/utils/request/vars_recorder.py new file mode 100644 index 0000000..2ea5ae1 --- /dev/null +++ b/httpfpt/utils/request/vars_recorder.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from jsonpath import findall + +from httpfpt.common.env_handler import write_env_vars +from httpfpt.common.errors import JsonPathFindError, VariableError +from httpfpt.common.variable_cache import variable_cache +from httpfpt.common.yaml_handler import write_yaml_vars +from httpfpt.core.path_conf import RUN_ENV_PATH +from httpfpt.enums.var_type import VarType + + +def record_variables(jsonpath: str, target: dict, key: str, set_type: str, env: str) -> None: + """ + 记录变量 + + :param jsonpath: + :param set_type: + :param target: + :param key: + :param env: + :return: + """ + value = findall(jsonpath, target) + if not value: + raise JsonPathFindError(f'jsonpath 取值失败, 表达式: {jsonpath}') + value_str = str(value[0]) + if set_type == VarType.CACHE: + variable_cache.set(key, value_str) + elif set_type == VarType.ENV: + write_env_vars(RUN_ENV_PATH, env, key, value_str) + elif set_type == VarType.GLOBAL: + write_yaml_vars({key: value_str}) + else: + raise VariableError(f'变量设置失败, 用例参数 "type: {set_type}" 值错误, 请使用 cache / env / global')