Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

更新全局变量替换逻辑 #165

Merged
merged 6 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions httpfpt/common/send_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,17 @@ 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)
except Skipped as e:
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']
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
33 changes: 5 additions & 28 deletions httpfpt/db/mysql_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion httpfpt/utils/request/request_data_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
64 changes: 22 additions & 42 deletions httpfpt/utils/request/vars_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -26,61 +24,56 @@ def __init__(self) -> None:
self.vars_re = re.compile(r'\${([a-zA-Z_]\w*)}|(?<!\S)\$([a-zA-Z_]\w*)(?!\S)')
# 关联变量表达: ^{var} 或 ^var
self.relate_vars_re = re.compile(r'\^{([a-zA-Z_]\w*)}|(?<!\S)\^([a-zA-Z_]\w*)(?!\S)')
# SQL 变量语法: :{var} 或 :var
self.sql_vars_re = re.compile(r':{([a-zA-Z_]\w*)}|(?<!\S):([a-zA-Z_]\w*)(?!\S)')

def vars_replace(self, target: dict, env_filename: str | None = None) -> 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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
35 changes: 35 additions & 0 deletions httpfpt/utils/request/vars_recorder.py
Original file line number Diff line number Diff line change
@@ -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')
Loading