HTTP请求组件支持传入请求头数据(参考PostMan),方便调试执行单个HTTP请求

https://gitee.com/azhengzz/api-automation-test/issues/I3E5Z5
This commit is contained in:
azhengzz 2021-03-31 09:18:12 +08:00
parent 60088e41e9
commit c56d07c5c0
7 changed files with 173 additions and 8 deletions

View File

@ -1,10 +1,12 @@
# coding=utf-8
from copy import deepcopy
from app.models import Case, Dispatcher
from app.cores.logger import DispatcherLogger
from app.cores.dictionaries import DISPATCHER_TYPE, REPORT_RESULT
from app.cores.dispatcher import AbstractCaseDispatcher
from app.cores.case.http.request_util import handle_url
from app.cores.case.http.request_util import handle_url, handle_headers
from app.cores.case.http.http_cookie_pool_manager import HTTPCookiePoolManager
from app.cores.case.http.http_request_header_pool_manager import HTTPRequestHeaderPoolManager
from app.cores.case.base.script import exec_postprocessor_script, exec_preprocessor_script
@ -40,8 +42,6 @@ class HTTPCaseDispatcher(AbstractCaseDispatcher):
# cookie数据
self.rcj = None
# http请求头
self.headers = None
# http请求字段
self.protocol = None
@ -57,6 +57,7 @@ class HTTPCaseDispatcher(AbstractCaseDispatcher):
self.parameters = None
self.expectations = None
self.file_upload = None
self.headers = None
def set_up(self):
super().set_up()
@ -64,6 +65,8 @@ class HTTPCaseDispatcher(AbstractCaseDispatcher):
self.rcj = HTTPCookiePoolManager.get_request_cookie_jar(type=self.dispatcher_type)
# 获取HTTP请求头
self.headers = HTTPRequestHeaderPoolManager.get_http_request_header(project_id=self.case.scene.module.project.id)
self.headers = deepcopy(self.headers) # 后面会对请求头数据更新但不要影响HTTPRequestHeaderPool中的数据
# 预处理脚本执行
preprocessor_script = self.case.specific_case.preprocessor_script_
exec_preprocessor_script(
@ -87,6 +90,8 @@ class HTTPCaseDispatcher(AbstractCaseDispatcher):
self.parameters = self.case.specific_case.parameters
self.expectations = self.case.specific_case.expectations
self.file_upload = self.case.specific_case.file_upload
_headers = self.case.specific_case.headers
self.headers.update(handle_headers(_headers)) # 将HTTPCase中定义的headers覆盖到HTTPRequestHeaderManagerTool中
def execute(self):
super().execute()

View File

@ -31,6 +31,18 @@ def handle_params(params):
return ret
def handle_headers(headers):
"""
处理请求头数据
:param headers: 请求头数据
:type headers: List[HTTPCaseHeader]
"""
ret = {}
for header in headers:
ret[header.name_] = header.value_
return ret
def handle_data(params, content_type=None):
"""
处理请求参数使其满足post请求的数据格式

View File

@ -1731,6 +1731,7 @@ class HTTPCase(db.Model):
关系说明
case: 案例组件 Case:HTTPCase is 1:1
parameters: 案例参数 HTTPCase:HTTPCaseParameter is 1:N
headers: 请求头参数 HTTPCase:HTTPCaseHeader is 1:N
expectations: 案例期望 HTTPCase:HTTPCaseExpectation is 1:N
file_upload: 文件上传参数 HTTPCase:HTTPCaseFileUpload is 1:N
"""
@ -1753,6 +1754,7 @@ class HTTPCase(db.Model):
case_id = db.Column(db.Integer, db.ForeignKey('case.id'))
case = db.relationship('Case')
parameters = db.relationship('HTTPCaseParameter', back_populates='http_case', cascade='all, delete-orphan') # 解除与case的关系后删除
headers = db.relationship('HTTPCaseHeader', back_populates='http_case', cascade='all, delete-orphan')
expectations = db.relationship('HTTPCaseExpectation', back_populates='http_case', cascade='all, delete-orphan')
file_upload = db.relationship('HTTPCaseFileUpload', back_populates='http_case', cascade='all, delete-orphan')
@ -1788,7 +1790,7 @@ class HTTPCase(db.Model):
@classmethod
def add(cls, name, description, scene_id, protocol, domain, port, method, path, encoding, expectation_logic,
message_body, content_type, preprocessor_script, postprocessor_script, status=STATUS.NORMAL,
parameters=None, file_upload=None, expectations=None):
parameters=None, headers=None, file_upload=None, expectations=None):
"""
新增一条案例
:param name: 案例名
@ -1807,6 +1809,7 @@ class HTTPCase(db.Model):
:param preprocessor_script: 预处理脚本
:param postprocessor_script: 后处理脚本
:param parameters: 参数数据
:param headers: 请求头数据
:param file_upload: 文件上传参数数据
:param expectations: 期望数据
"""
@ -1832,6 +1835,7 @@ class HTTPCase(db.Model):
)
http_case.case = case
http_case._update_parameters(parameters)
http_case._update_headers(headers)
http_case._update_expectations(expectations)
http_case._update_file_upload(file_upload)
db.session.add(case)
@ -1841,8 +1845,8 @@ class HTTPCase(db.Model):
@classmethod
def update(cls, id, name, description, protocol, domain, port, method, path, encoding, expectation_logic,
message_body, content_type, preprocessor_script, postprocessor_script, params=None, file_upload=None,
expectations=None):
message_body, content_type, preprocessor_script, postprocessor_script, params=None, headers=None,
file_upload=None, expectations=None):
"""
更新一条案例
:param id: 案例编号
@ -1860,6 +1864,7 @@ class HTTPCase(db.Model):
:param preprocessor_script: 预处理脚本
:param postprocessor_script: 后处理脚本
:param params: 参数数据
:param headers: 请求头数据
:param file_upload: 文件上传参数
:param expectations: 期望数据
"""
@ -1881,6 +1886,7 @@ class HTTPCase(db.Model):
http_case.preprocessor_script = preprocessor_script
http_case.postprocessor_script = postprocessor_script
http_case._update_parameters(params)
http_case._update_headers(headers)
http_case._update_file_upload(file_upload)
http_case._update_expectations(expectations)
db.session.commit()
@ -1911,6 +1917,7 @@ class HTTPCase(db.Model):
preprocessor_script=self.preprocessor_script,
postprocessor_script=self.postprocessor_script,
parameters=self.parameters,
headers=self.headers,
expectations=self.expectations,
file_upload=self.file_upload,
)
@ -1944,6 +1951,22 @@ class HTTPCase(db.Model):
)
self.parameters.append(parameter)
def _update_headers(self, headers):
self.headers = []
if headers is not None:
for _header in headers:
if hasattr(_header, 'get'):
header = HTTPCaseHeader(
name=_header.get('name'),
value=_header.get('value'),
)
else: # copy_from_self func exec this branch
header = HTTPCaseHeader(
name=_header.name,
value=_header.value,
)
self.headers.append(header)
def _update_expectations(self, expectations):
self.expectations = []
if expectations is not None:
@ -2020,6 +2043,35 @@ class HTTPCaseParameter(db.Model):
return _p(self.value)
class HTTPCaseHeader(db.Model):
"""
字段说明
name: 参数名
value: 参数值
关系说明
http_case: 请求头所属HTTP案例 HTTPCase:HTTPCaseHeader is 1:N
"""
# 需要转换为json数据格式的字段
render_field_list = ['name', 'value']
# field
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(512), nullable=False, default='', server_default='')
value = db.Column(db.String(512), nullable=False, default='', server_default='')
# relationship
http_case_id = db.Column(db.Integer, db.ForeignKey('http_case.id'))
http_case = db.relationship('HTTPCase', back_populates='headers')
# 解析处理
@property
def name_(self):
return _p(self.name)
@property
def value_(self):
return _p(self.value)
class HTTPCaseExpectation(db.Model):
"""
字段说明

View File

@ -54,6 +54,8 @@ def save_http_case():
if not exist: return form_content_type
exist, form_param_json = get_form_from_request(request, 'param_json')
if not exist: return form_param_json
exist, form_header_json = get_form_from_request(request, 'header_json')
if not exist: return form_header_json
exist, form_file_upload_json = get_form_from_request(request, 'file_upload_json')
if not exist: return form_file_upload_json
exist, form_expectation_json = get_form_from_request(request, 'expectation_json')
@ -66,6 +68,7 @@ def save_http_case():
if not exist: return form_postprocessor_script
try:
params = json.loads(form_param_json)
headers = json.loads(form_header_json)
file_upload = json.loads(form_file_upload_json)
expectations = json.loads(form_expectation_json)
if not Case.exist_and_status_not_delete(id=form_id):
@ -85,6 +88,7 @@ def save_http_case():
encoding=form_content_encoding,
expectation_logic=form_expectation_logic,
params=params,
headers=headers,
file_upload=file_upload,
expectations=expectations,
message_body=form_message_body_json,
@ -2503,10 +2507,12 @@ def case():
data_expectations = json.loads(render_to_json(case.specific_case.expectations))
if case.case_type == CASE_TYPE.HTTP:
data_parameters = json.loads(render_to_json(case.specific_case.parameters))
data_headers = json.loads(render_to_json(case.specific_case.headers))
data_file_upload = json.loads(render_to_json(case.specific_case.file_upload))
table_cookie_manager_data = json.loads(render_to_json(case.specific_case.file_upload))
else:
data_parameters = []
data_headers = []
data_file_upload = []
table_cookie_manager_data = []
except Exception as e:
@ -2524,6 +2530,7 @@ def case():
'html_text': html_text,
'data_expectations': data_expectations,
'data_parameters': data_parameters,
'data_headers': data_headers,
'data_file_upload': data_file_upload,
'table_cookie_manager_data': table_cookie_manager_data,
})

View File

@ -276,16 +276,19 @@ function getHTTPCaseElement(case_id, case_type) {
element.dom.$input_content_encoding = $(`#input-content-encoding-${case_id}`);
element.dom.$input_content_type = $(`#input-content-type-${case_id}`);
element.dom.$div_table_parameter_spinner = $(`#div-table-parameter-spinner-${case_id}`);
element.dom.$div_table_header_spinner = $(`#div-table-header-spinner-${case_id}`);
element.dom.$div_table_file_upload_spinner = $(`#div-table-file-upload-spinner-${case_id}`);
element.dom.$nav_link_param_tab = $(`#param-tab-${case_id}`);
element.dom.$nav_link_header_tab = $(`#header-tab-${case_id}`);
element.dom.$nav_link_file_upload_tab = $(`#file-upload-tab-${case_id}`);
element.obj.table_parameter = null; // 请求参数表格
element.obj.table_header = null; // 请求头表格
element.obj.table_file_upload = null; // 文件上传表格
element.obj.ace_message_body_editor = null; // 消息体数据格式化编辑器
element.obj.splitterMinSize = [50, 70]; // splitter分割条最小长度
element.init = function (data_expectations, data_parameters, data_file_upload) {
element.init = function (data_expectations, data_parameters, data_headers, data_file_upload) {
// 参数
// data_expectations: 案例期望数据
// data_parameters: 案例请求参数数据
@ -301,6 +304,8 @@ function getHTTPCaseElement(case_id, case_type) {
renderAceEditor();
// 渲染参数表格
renderParameterTable(data_parameters);
// 渲染请求头表格
renderHeaderTable(data_headers);
// 渲染文件上传表格
renderFileUploadTable(data_file_upload);
// 获取CookieManager对象并初始化
@ -336,6 +341,7 @@ function getHTTPCaseElement(case_id, case_type) {
// 事件绑定
function eventBinding() {
element.dom.$nav_link_param_tab.on('shown.bs.tab', clickParamTab);
element.dom.$nav_link_header_tab.on('shown.bs.tab', clickHeaderTab);
element.dom.$nav_link_file_upload_tab.on('shown.bs.tab', clickFileTab);
function clickParamTab() {
@ -343,6 +349,11 @@ function getHTTPCaseElement(case_id, case_type) {
$(`#table-parameter-${case_id}`).handsontable('getInstance').render();
}
function clickHeaderTab() {
// 渲染ht
$(`#table-header-${case_id}`).handsontable('getInstance').render();
}
function clickFileTab() {
// 渲染ht
$(`#table-file-upload-${case_id}`).handsontable('getInstance').render();
@ -453,6 +464,72 @@ function getHTTPCaseElement(case_id, case_type) {
}
}
// 渲染请求头表格
function renderHeaderTable(data_headers) {
const option_table_header = {
data: data_headers,
rowHeaders: true,
// colHeaders: true,
licenseKey: 'non-commercial-and-evaluation',
// 表格宽度
// width: '95vw', // 不指定宽度使其自适应容器
// 拉伸方式
stretchH: 'all',
// tab键自动换行切换
autoWrapRow: true,
height: '40vh',
// 最大行数
maxRows: 99,
// 允许手工移动行或列
manualRowResize: true,
manualColumnResize: true,
// 列名
colHeaders: [
'名称',
'值',
],
// 为列设置默认值
dataSchema: {
name: '',
value: '',
url_encode: false,
content_type: 'text/plain',
include_equals: true,
},
// 设置列数据类型
columns: [
{
data: 'name'
},
{
data: 'value',
},
],
// 列宽比例
colWidths: [1, 1],
manualRowMove: true,
manualColumnMove: false,
// 右键菜单
contextMenu: ['row_above', 'row_below', '---------', 'remove_row', '---------', 'undo', 'redo', '---------', 'alignment', '---------', 'copy', 'cut'],
// 列是否支持过滤
filters: false,
// 下拉菜单
dropdownMenu: ['make_read_only', '---------', 'alignment'],
// 语言
language: 'zh-CN',
// 是否允许无效数据 默认 true
allowInvalid: false,
afterLoadData: afterLoadDataHookForTableParam,
};
let $container = $(`#table-header-${case_id}`);
element.obj.table_header = $container.handsontable(option_table_header).handsontable('getInstance');
// 数据加载完毕后隐藏spinner
function afterLoadDataHookForTableParam() {
element.dom.$div_table_header_spinner.css('display', 'none');
}
}
// 渲染文件上传表格
function renderFileUploadTable(data_file_upload) {
// const container_table_file_upload = document.getElementById(`table-file-upload-${case_id}`);
@ -561,6 +638,7 @@ function getHTTPCaseElement(case_id, case_type) {
expectation_logic: expectation_logic,
content_type: content_type,
param_json: JSON.stringify(element.obj.table_parameter.getSourceData()),
header_json: JSON.stringify(element.obj.table_header.getSourceData()),
file_upload_json: JSON.stringify(element.obj.table_file_upload.getSourceData()),
expectation_json: JSON.stringify(element.obj.table_expectation.getSourceData()),
message_body_json: message_body_json_str,

View File

@ -1330,6 +1330,7 @@ function showCase(case_id, case_type) {
if(case_type === 'HTTP'){
// 渲染ht
$(`#table-parameter-${case_id}`).handsontable('getInstance').render();
$(`#table-header-${case_id}`).handsontable('getInstance').render();
$(`#table-file-upload-${case_id}`).handsontable('getInstance').render();
}
if(!element_case_first_show_flag[case_id]){ // 只在第一次打开案例组件时渲染
@ -1850,7 +1851,7 @@ function addCaseElement(case_id, case_type) {
element_case_first_show_flag[case_id] = false;
}else if(data.case_type === 'HTTP'){
let element = getHTTPCaseElement(case_id, data.case_type);
element.init(data.data_expectations, data.data_parameters, data.data_file_upload);
element.init(data.data_expectations, data.data_parameters, data.data_headers, data.data_file_upload);
element_case_instance_dict[case_id] = element;
element_case_first_show_flag[case_id] = false;
}

View File

@ -102,6 +102,9 @@
<li class="nav-item">
<a class="nav-link" id="message-body-tab-{{ case.id }}" data-toggle="tab" href="#tab-message-body-{{ case.id }}" role="tab">消息体数据</a>
</li>
<li class="nav-item">
<a class="nav-link" id="header-tab-{{ case.id }}" data-toggle="tab" href="#tab-header-{{ case.id }}" role="tab">请求头</a>
</li>
{# <li class="nav-item">#}
{# <a class="nav-link" id="file-upload-tab-{{ case.id }}" data-toggle="tab" href="#tab-file-upload-{{ case.id }}" role="tab">文件上传</a>#}
{# </li>#}
@ -123,6 +126,13 @@
<div class="tab-pane fade" id="tab-message-body-{{ case.id }}" role="tabpanel">
<div class="container-fluid ace-message-body" id="div-ace-message-body-{{ case.id }}">{{ case.specific_case.message_body }}</div>
</div>
<div class="tab-pane fade" id="tab-header-{{ case.id }}" role="tabpanel">
<div id="table-header-{{ case.id }}" class="d-flex justify-content-center">
<div id="div-table-header-spinner-{{ case.id }}" class="spinner-border text-primary m-5" role="status">
<span class="sr-only">加载中Loading...</span>
</div>
</div>
</div>
<div class="tab-pane fade" id="tab-file-upload-{{ case.id }}" role="tabpanel">
<div id="table-file-upload-{{ case.id }}" class="d-flex justify-content-center">
<div id="div-table-file-upload-spinner-{{ case.id }}" class="spinner-border text-primary m-5" role="status">