fix(全局): bug修复

This commit is contained in:
baiqi 2024-04-22 21:01:15 +08:00 committed by Craftsman
parent 5b60c332db
commit 1a13f39a96
21 changed files with 319 additions and 233 deletions

View File

@ -124,7 +124,7 @@
:selectable="true"
:columns="xPathColumns"
:scroll="{ minWidth: '700px' }"
:default-param-item="xPathDefaultParamItem"
:default-param-item="defaultAssertParamsItem"
@change="(data:any[],isInit?: boolean) => handleChange(data, ResponseBodyAssertionType.XPATH,isInit)"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
>
@ -363,7 +363,7 @@
import { TableColumnData, TableData } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import { EQUAL, statusCodeOptions } from '@/components/pure/ms-advance-filter';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { TableOperationColumn } from '@/components/business/ms-user-group-comp/authTable.vue';
import fastExtraction from '@/views/api-test/components/fastExtraction/index.vue';
@ -387,15 +387,14 @@
RegexExtract,
XPathExtract,
} from '@/models/apiTest/common';
import { RequestExtractExpressionEnum, ResponseBodyAssertionType } from '@/enums/apiEnum';
import {
RequestExtractEnvType,
RequestExtractExpressionEnum,
RequestExtractExpressionRuleType,
RequestExtractResultMatchingRule,
RequestExtractScope,
ResponseBodyAssertionType,
ResponseBodyXPathAssertionFormat,
} from '@/enums/apiEnum';
defaultAssertParamsItem,
defaultExtractParamItem,
jsonPathDefaultParamItem,
regexDefaultParamItem,
} from '@/views/api-test/components/config';
const { t } = useI18n();
@ -445,20 +444,6 @@
// const disabledExpressionSuffix = ref(false);
export type ExpressionConfig = (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>;
const defaultExtractParamItem: ExpressionConfig = {
enable: true,
variableName: '',
variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY,
expression: '',
extractType: RequestExtractExpressionEnum.JSON_PATH,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1,
responseFormat: ResponseBodyXPathAssertionFormat.XML,
moreSettingPopoverVisible: false,
};
const activeRecord = ref({ ...defaultExtractParamItem }); //
const responseRadios = [
@ -506,50 +491,6 @@
},
];
// json
const jsonPathDefaultParamItem = {
enable: true,
variableName: '',
variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY,
expression: '',
condition: EQUAL.value,
extractType: RequestExtractExpressionEnum.JSON_PATH,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1,
responseFormat: ResponseBodyXPathAssertionFormat.XML,
moreSettingPopoverVisible: false,
};
// xpath
const xPathDefaultParamItem = {
expression: '',
enable: true,
valid: true,
variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY,
extractType: RequestExtractExpressionEnum.X_PATH,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1,
responseFormat: ResponseBodyXPathAssertionFormat.XML,
moreSettingPopoverVisible: false,
};
// xpath
const regexDefaultParamItem = {
expression: '',
enable: true,
valid: true,
variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY,
extractType: RequestExtractExpressionEnum.REGEX,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1,
responseFormat: ResponseBodyXPathAssertionFormat.XML,
moreSettingPopoverVisible: false,
};
const handleChange = (data: any[], type: string, isInit?: boolean) => {
switch (type) {
case ResponseBodyAssertionType.JSON_PATH:

View File

@ -9,9 +9,9 @@
</div>
</a-button>
<template #content>
<a-doption v-for="item in assertOptionSource" :key="item.value" :value="item.value">{{
item.label
}}</a-doption>
<a-doption v-for="item in assertOptionSource" :key="item.value" :value="item.value">
{{ item.label }}
</a-doption>
</template>
</a-dropdown>
<div v-if="props.isDefinition && innerConfig" class="flex items-center">

View File

@ -115,15 +115,26 @@
/**
* 替换文档
* @param beautifyDoc 格式化后的文档
* @param isHtml 是否是html
*/
function replaceDoc(beautifyDoc: string) {
// HTML icon
flattenedXml.value = beautifyDoc
function replaceDoc(beautifyDoc: string, isHtml = false) {
// XML/HTML icon
let resultArr: XpathNode[] = [];
const tempStr = beautifyDoc
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/(&lt;([^/][^&]*?)&gt;)/g, '<span style="color: rgb(var(--primary-5));cursor: pointer">$1📋</span>')
.split(/\r?\n/)
.map((e) => ({ content: e, xpath: '' }));
.replace(/(&lt;([^/][^&]*?)&gt;)/g, '<span style="color: rgb(var(--primary-5));cursor: pointer">$1📋</span>');
if (isHtml) {
resultArr = HtmlBeautify(
tempStr.replace(/(\S)(?=<)/gs, '$1\n'), // html
{ ocd: true }
)
.split(/\r?\n/)
.map((e) => ({ content: e, xpath: '' }));
} else {
resultArr = tempStr.split(/\r?\n/).map((e) => ({ content: e, xpath: '' }));
}
flattenedXml.value = resultArr;
}
/**
@ -142,8 +153,9 @@
isValidXml.value = true;
parsedXml.value = xmlDoc;
const beautifyDoc = HtmlBeautify(props.xmlString, { ocd: true });
replaceDoc(beautifyDoc);
replaceDoc(beautifyDoc, true);
// HTML xpath
tempXmls.value = [];
flattenHtml(xmlDoc.documentElement, '');
// XML/HTML xpath xpath
flattenedXml.value = flattenedXml.value
@ -157,7 +169,7 @@
xpath,
};
}
return false;
return e.content.includes('&lt;/') ? e : false;
})
.filter(Boolean) as any[];
emit('init', 'html');
@ -185,20 +197,23 @@
const beautifyDoc = new XmlBeautify().beautify(props.xmlString);
replaceDoc(beautifyDoc);
// XML xpath
tempXmls.value = [];
flattenXml(xmlDoc.documentElement, '');
// XML xpath xpath
flattenedXml.value = flattenedXml.value.map((e) => {
const targetNodeIndex = tempXmls.value.findIndex((txt) => e.content.includes(`&lt;${txt.content}`));
if (targetNodeIndex >= 0) {
const { xpath } = tempXmls.value[targetNodeIndex];
tempXmls.value.splice(targetNodeIndex, 1); // tempXmls
return {
...e,
xpath,
};
}
return e;
});
flattenedXml.value = flattenedXml.value
.map((e) => {
const targetNodeIndex = tempXmls.value.findIndex((txt) => e.content.includes(`&lt;${txt.content}`));
if (targetNodeIndex >= 0) {
const { xpath } = tempXmls.value[targetNodeIndex];
tempXmls.value.splice(targetNodeIndex, 1); // tempXmls
return {
...e,
xpath,
};
}
return false;
})
.filter(Boolean) as any[];
emit('init', 'xml');
} catch (error) {
// eslint-disable-next-line no-console

View File

@ -78,6 +78,8 @@ export interface ApiScenarioTableItem {
deleteUser: string;
deleteTime: number;
deleted: boolean;
environmentId: string;
environmentName: string;
createUserName: string;
updateUserName: string;
deleteUserName: string;

View File

@ -276,7 +276,7 @@
</div>
<template v-if="condition.dataSourceId">
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.sqlScript') }}</div>
<div class="mb-[8px] h-[300px]">
<div class="mb-[8px]">
<MsCodeEditor
v-model:model-value="condition.script"
:read-only="props.disabled"

View File

@ -1,18 +1,23 @@
<template>
<div class="mb-[8px] flex items-center justify-between">
<a-dropdown @select="(val) => addCondition(val as ConditionType)">
<a-button type="outline" :disabled="props.disabled">
<template #icon>
<icon-plus :size="14" />
<div class="flex items-center gap-[8px]">
<a-dropdown @select="(val) => addCondition(val as ConditionType)">
<a-button type="outline" :disabled="props.disabled">
<template #icon>
<icon-plus :size="14" />
</template>
{{ t(props.addText) }}
</a-button>
<template #content>
<a-doption v-for="key of props.conditionTypes" :key="key" :value="key">
{{ t(conditionTypeNameMap[key]) }}
</a-doption>
</template>
{{ t(props.addText) }}
</a-button>
<template #content>
<a-doption v-for="key of props.conditionTypes" :key="key" :value="key">
{{ t(conditionTypeNameMap[key]) }}
</a-doption>
</template>
</a-dropdown>
</a-dropdown>
<div v-if="$slots.dropdownAppend" class="flex items-center">
<slot name="dropdownAppend"></slot>
</div>
</div>
<div v-if="$slots.titleRight" class="flex items-center">
<slot name="titleRight"></slot>
</div>

View File

@ -1,3 +1,5 @@
import { EQUAL } from '@/components/pure/ms-advance-filter';
import {
EnableKeyValueParam,
ExecuteBody,
@ -13,11 +15,19 @@ import {
RequestBodyFormat,
RequestCaseStatus,
RequestContentTypeEnum,
RequestExtractEnvType,
RequestExtractExpressionEnum,
RequestExtractExpressionRuleType,
RequestExtractResultMatchingRule,
RequestExtractScope,
RequestParamsType,
ResponseBodyFormat,
ResponseBodyXPathAssertionFormat,
ResponseComposition,
} from '@/enums/apiEnum';
import type { ExpressionConfig } from './fastExtraction/moreSetting.vue';
// 请求 body 参数表格默认行的值
export const defaultBodyParamsItem: ExecuteRequestFormBodyFormValue = {
key: '',
@ -173,3 +183,47 @@ export const defaultAssertXpathParamsItem: ResponseAssertionItem = {
expression: '',
enable: true,
};
// 断言 xpath
export const defaultExtractParamItem: ExpressionConfig = {
enable: true,
variableName: '',
variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY,
expression: '',
extractType: RequestExtractExpressionEnum.JSON_PATH,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1,
responseFormat: ResponseBodyXPathAssertionFormat.XML,
moreSettingPopoverVisible: false,
};
// 断言 json默认值
export const jsonPathDefaultParamItem = {
enable: true,
variableName: '',
variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY,
expression: '',
condition: EQUAL.value,
extractType: RequestExtractExpressionEnum.JSON_PATH,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1,
responseFormat: ResponseBodyXPathAssertionFormat.XML,
moreSettingPopoverVisible: false,
};
// 断言 正则默认值
export const regexDefaultParamItem = {
expression: '',
enable: true,
valid: true,
variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY,
extractType: RequestExtractExpressionEnum.REGEX,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,
resultMatchingRuleNum: 1,
responseFormat: ResponseBodyXPathAssertionFormat.XML,
moreSettingPopoverVisible: false,
};

View File

@ -237,10 +237,7 @@
matchResult.value = [nodes];
} else if (Array.isArray(nodes)) {
//
matchResult.value = nodes
.map((node) => node.textContent?.split('\n') || false)
.flat(Infinity)
.filter(Boolean);
matchResult.value = nodes.map((node) => node.firstChild?.nodeValue || false).filter(Boolean);
} else {
//
matchResult.value = nodes.textContent ? [nodes.textContent] : [];

View File

@ -87,11 +87,12 @@
v-if="columnConfig.inputType === 'autoComplete'"
v-model:model-value="record[columnConfig.dataIndex as string]"
:disabled="props.disabledExceptParam || columnConfig.disabledColumn"
:data="record[columnConfig.dataIndex as string] !== '' ? columnConfig.autoCompleteParams?.filter((e) => e.isShow === true) : columnConfig.autoCompleteParams"
:data="getAutoCompleteData(columnConfig, record)"
class="ms-form-table-input"
:trigger-props="{ contentClass: 'ms-form-table-input-trigger' }"
:filter-option="false"
size="mini"
@focus="handleAutoCompleteFocus(record)"
@search="(val) => handleSearchParams(val, columnConfig)"
@change="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
@select="(val) => selectAutoComplete(val, record, columnConfig)"
@ -1041,6 +1042,21 @@
});
}
const activeRecord = ref<Record<string, any>>({});
function getAutoCompleteData(columnConfig: ParamTableColumn, record: Record<string, any>) {
if (activeRecord.value.id !== record.id) {
//
return [];
}
return activeRecord.value[columnConfig.dataIndex as string] !== ''
? columnConfig.autoCompleteParams?.filter((e) => e.isShow === true)
: columnConfig.autoCompleteParams;
}
function handleAutoCompleteFocus(record: Record<string, any>) {
activeRecord.value = record;
}
function selectAutoComplete(val: string, record: Record<string, any>, item: FormTableColumn) {
record[item.dataIndex as string] = val;
}

View File

@ -126,7 +126,7 @@
<script setup lang="ts">
import { Message, SelectOptionData } from '@arco-design/web-vue';
import { cloneDeep, debounce } from 'lodash-es';
import { debounce } from 'lodash-es';
import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
import MsTab from '@/components/pure/ms-tab/index.vue';
@ -142,17 +142,21 @@
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { ExecuteConditionConfig, PluginConfig } from '@/models/apiTest/common';
import { PluginConfig } from '@/models/apiTest/common';
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
import { RequestAuthType, RequestBodyFormat, RequestComposition, RequestConditionProcessor } from '@/enums/apiEnum';
import { RequestAuthType, RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
import {
defaultBodyParamsItem,
defaultHeaderParamsItem,
defaultKeyValueParamItem,
defaultRequestParamsItem,
} from '@/views/api-test/components/config';
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
import {
filterAssertions,
filterConditionsSqlValidParams,
filterKeyValParams,
parseRequestBodyFiles,
} from '@/views/api-test/components/utils';
import type { Api } from '@form-create/arco-design';
// Http
@ -430,20 +434,6 @@
}
}
function filterConditionsSqlValidParams(condition: ExecuteConditionConfig) {
const conditionCopy = cloneDeep(condition);
conditionCopy.processors = conditionCopy.processors.map((processor) => {
if (processor.processorType === RequestConditionProcessor.SQL) {
processor.extractParams = filterKeyValParams(
processor.extractParams || [],
defaultKeyValueParamItem
).validParams;
}
return processor;
});
return conditionCopy;
}
//
function makeRequestParams(executeType?: 'localExec' | 'serverExec') {
const isExecute = executeType === 'localExec' || executeType === 'serverExec';
@ -498,6 +488,7 @@
const requestName = requestVModel.value.name ?? '';
const requestModuleId = requestVModel.value.moduleId ?? '';
const { assertionConfig } = requestVModel.value.children[0];
return {
id: requestVModel.value.id.toString(),
name: requestName,
@ -511,7 +502,10 @@
children: [
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: requestVModel.value.children[0].assertionConfig,
assertionConfig: {
...requestVModel.value.children[0].assertionConfig,
assertions: filterAssertions(assertionConfig, isExecute),
},
postProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].postProcessorConfig),
preProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].preProcessorConfig),
},

View File

@ -509,15 +509,13 @@
import {
casePriorityOptions,
caseStatusOptions,
defaultAssertParamsItem,
defaultAssertXpathParamsItem,
defaultBodyParamsItem,
defaultHeaderParamsItem,
defaultKeyValueParamItem,
defaultRequestParamsItem,
defaultResponse,
} from '@/views/api-test/components/config';
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
import { filterAssertions, filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
import type { Api } from '@form-create/arco-design';
// Http
@ -1138,45 +1136,6 @@
//
const { assertionConfig } = requestVModel.value.children[0];
const assertionList = assertionConfig.assertions.map((assertItem: any) => {
const bodyAssertionDataByTypeList = filterKeyValParams(
assertItem?.bodyAssertionDataByType?.assertions || [],
defaultAssertParamsItem,
isExecute
).validParams;
return {
...assertItem,
bodyAssertionDataByType: {
...assertItem.bodyAssertionDataByType,
assertions: bodyAssertionDataByTypeList,
},
regexAssertion: {
...assertItem?.regexAssertion,
assertions: filterKeyValParams(
assertItem?.regexAssertion?.assertions || [],
defaultAssertXpathParamsItem,
isExecute
).validParams,
},
xpathAssertion: {
...assertItem.xpathAssertion,
assertions: filterKeyValParams(
assertItem?.xpathAssertion?.assertions || [],
defaultAssertXpathParamsItem,
isExecute
).validParams,
},
jsonPathAssertion: {
...assertItem.jsonPathAssertion,
assertions: filterKeyValParams(
assertItem?.jsonPathAssertion?.assertions || [],
defaultAssertParamsItem,
isExecute
).validParams,
},
};
});
return {
id: requestVModel.value.id.toString(),
reportId: reportId.value,
@ -1195,7 +1154,7 @@
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: {
...assertionConfig,
assertions: assertionList,
assertions: filterAssertions(assertionConfig, isExecute),
},
postProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].postProcessorConfig),
preProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].preProcessorConfig),

View File

@ -10,6 +10,9 @@
:sql-code-editor-height="props.sqlCodeEditorHeight"
@change="emit('change')"
>
<template v-if="$slots.dropdownAppend" #dropdownAppend>
<slot name="dropdownAppend" />
</template>
<template v-if="props.isDefinition" #titleRight>
<a-switch
v-model:model-value="innerConfig.enableGlobal"

View File

@ -8,6 +8,9 @@
add-text="apiTestDebug.precondition"
@change="emit('change')"
>
<template v-if="$slots.dropdownAppend" #dropdownAppend>
<slot name="dropdownAppend" />
</template>
<template v-if="props.isDefinition" #titleRight>
<a-switch
v-model:model-value="innerConfig.enableGlobal"

View File

@ -1,13 +1,17 @@
import { cloneDeep, isEqual } from 'lodash-es';
import { ExecuteBody } from '@/models/apiTest/common';
import { RequestParamsType } from '@/enums/apiEnum';
import { type ExecuteAssertionConfig, ExecuteBody, type ExecuteConditionConfig } from '@/models/apiTest/common';
import { RequestConditionProcessor, RequestParamsType, ResponseBodyAssertionType } from '@/enums/apiEnum';
import {
defaultAssertParamsItem,
defaultBodyParamsItem,
defaultExtractParamItem,
defaultHeaderParamsItem,
defaultKeyValueParamItem,
defaultRequestParamsItem,
jsonPathDefaultParamItem,
regexDefaultParamItem,
} from './config';
import type { RequestParam } from './requestComposition/index.vue';
@ -129,13 +133,18 @@ export function filterKeyValParams<T>(
validParams: params,
};
}
// id和enable属性不参与比较
// id、enable、valid属性不参与比较
delete lastData.id;
delete lastData.enable;
delete lastData.valid;
delete defaultParam.valid;
delete defaultParam.id;
delete defaultParam.enable;
const lastDataIsDefault = isEqual(lastData, defaultParam) || lastData.key === '';
let validParams: (T & Record<string, any>)[];
if (defaultParam.condition) {
console.log(`assertionConfig`, lastDataIsDefault, lastData, defaultParam);
}
if (lastDataIsDefault) {
// 如果最后一条数据是默认数据,非用户添加更改的,说明是无效参数,删除最后一个
validParams = params.slice(0, params.length - 1);
@ -170,3 +179,57 @@ export function getValidRequestTableParams(requestVModel: RequestParam) {
})) || [],
};
}
/**
*
* @param condition
*/
export function filterConditionsSqlValidParams(condition: ExecuteConditionConfig) {
const conditionCopy = cloneDeep(condition);
conditionCopy.processors = conditionCopy.processors.map((processor) => {
if (processor.processorType === RequestConditionProcessor.SQL) {
processor.extractParams = filterKeyValParams(processor.extractParams || [], defaultKeyValueParamItem).validParams;
}
return processor;
});
return conditionCopy;
}
/**
*
* @param assertionConfig
* @param isExecute
*/
export function filterAssertions(assertionConfig: ExecuteAssertionConfig, isExecute = false) {
return assertionConfig.assertions.map((assertItem: any) => {
return {
...assertItem,
bodyAssertionDataByType: {
...assertItem.bodyAssertionDataByType,
assertions: filterKeyValParams(
assertItem?.bodyAssertionDataByType?.assertions || [],
defaultAssertParamsItem,
isExecute
).validParams,
},
regexAssertion: {
...assertItem?.regexAssertion,
assertions: filterKeyValParams(assertItem?.regexAssertion?.assertions || [], regexDefaultParamItem, isExecute)
.validParams,
},
xpathAssertion: {
...assertItem.xpathAssertion,
assertions: filterKeyValParams(assertItem?.xpathAssertion?.assertions || [], defaultExtractParamItem, isExecute)
.validParams,
},
jsonPathAssertion: {
...assertItem.jsonPathAssertion,
assertions: filterKeyValParams(
assertItem?.jsonPathAssertion?.assertions || [],
jsonPathDefaultParamItem,
isExecute
).validParams,
},
};
});
}

View File

@ -395,7 +395,6 @@
EnableKeyValueParam,
ExecuteApiRequestFullParams,
ExecuteBody,
ExecuteConditionConfig,
ExecutePluginRequestParams,
ExecuteRequestCommonParam,
ExecuteRequestFormBody,
@ -409,7 +408,6 @@
RequestAuthType,
RequestBodyFormat,
RequestComposition,
RequestConditionProcessor,
RequestMethods,
ResponseComposition,
ScenarioStepType,
@ -420,11 +418,15 @@
defaultBodyParams,
defaultBodyParamsItem,
defaultHeaderParamsItem,
defaultKeyValueParamItem,
defaultRequestParamsItem,
defaultResponse,
} from '@/views/api-test/components/config';
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
import {
filterAssertions,
filterConditionsSqlValidParams,
filterKeyValParams,
parseRequestBodyFiles,
} from '@/views/api-test/components/utils';
import type { Api } from '@form-create/arco-design';
// Http
@ -995,20 +997,6 @@
verticalSplitBoxRef.value?.expand(0.6);
}
function filterConditionsSqlValidParams(condition: ExecuteConditionConfig) {
const conditionCopy = cloneDeep(condition);
conditionCopy.processors = conditionCopy.processors.map((processor) => {
if (processor.processorType === RequestConditionProcessor.SQL) {
processor.extractParams = filterKeyValParams(
processor.extractParams || [],
defaultKeyValueParamItem
).validParams;
}
return processor;
});
return conditionCopy;
}
/**
* 生成请求参数
* @param executeType 执行类型执行时传入
@ -1062,6 +1050,7 @@
polymorphicName,
};
}
const { assertionConfig } = requestVModel.value.children[0];
return {
...requestParams,
resourceId: requestVModel.value.resourceId,
@ -1077,7 +1066,10 @@
children: [
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: requestVModel.value.children[0].assertionConfig,
assertionConfig: {
...requestVModel.value.children[0].assertionConfig,
assertions: filterAssertions(assertionConfig, isExecute),
},
postProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].postProcessorConfig),
preProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].preProcessorConfig),
},

View File

@ -304,13 +304,12 @@
import { characterLimit } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import { ExecuteConditionConfig, PluginConfig, RequestResult } from '@/models/apiTest/common';
import { PluginConfig, RequestResult } from '@/models/apiTest/common';
import { ScenarioStepFileParams, ScenarioStepItem } from '@/models/apiTest/scenario';
import {
RequestAuthType,
RequestBodyFormat,
RequestComposition,
RequestConditionProcessor,
RequestMethods,
ResponseComposition,
ScenarioStepRefType,
@ -322,11 +321,15 @@
defaultBodyParams,
defaultBodyParamsItem,
defaultHeaderParamsItem,
defaultKeyValueParamItem,
defaultRequestParamsItem,
defaultResponse,
} from '@/views/api-test/components/config';
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
import {
filterAssertions,
filterConditionsSqlValidParams,
filterKeyValParams,
parseRequestBodyFiles,
} from '@/views/api-test/components/utils';
import { Api } from '@form-create/arco-design';
// Http
const httpHeader = defineAsyncComponent(() => import('@/views/api-test/components/requestComposition/header.vue'));
@ -824,20 +827,6 @@
verticalSplitBoxRef.value?.expand(0.6);
}
function filterConditionsSqlValidParams(condition: ExecuteConditionConfig) {
const conditionCopy = cloneDeep(condition);
conditionCopy.processors = conditionCopy.processors.map((processor) => {
if (processor.processorType === RequestConditionProcessor.SQL) {
processor.extractParams = filterKeyValParams(
processor.extractParams || [],
defaultKeyValueParamItem
).validParams;
}
return processor;
});
return conditionCopy;
}
/**
* 生成请求参数
* @param executeType 执行类型执行时传入
@ -894,6 +883,7 @@
polymorphicName,
};
}
const { assertionConfig } = requestVModel.value.children[0];
return {
...requestParams,
resourceId: requestVModel.value.resourceId,
@ -908,7 +898,10 @@
children: [
{
polymorphicName: 'MsCommonElement', // MsCommonElement
assertionConfig: requestVModel.value.children[0].assertionConfig,
assertionConfig: {
...requestVModel.value.children[0].assertionConfig,
assertions: filterAssertions(assertionConfig, isExecute),
},
postProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].postProcessorConfig),
preProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].preProcessorConfig),
},

View File

@ -8,7 +8,16 @@
:tip-content="t('apiScenario.openGlobalPreConditionTip')"
is-scenario
@change="emit('change', true)"
/>
>
<template #dropdownAppend>
<a-tooltip :content="t('apiScenario.preConditionTip')" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</template>
</precondition>
</div>
<a-divider class="my-[8px]" type="dashed" />
<div>
@ -20,7 +29,16 @@
:tip-content="t('apiScenario.openGlobalPostConditionTip')"
is-scenario
@change="emit('change', false)"
/>
>
<template #dropdownAppend>
<a-tooltip :content="t('apiScenario.postConditionTip')" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</template>
</postcondition>
</div>
</div>
</template>

View File

@ -375,15 +375,15 @@
</a-form-item>
</a-form>
<template #footer>
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="flex items-center justify-end">
<!-- <div class="flex items-center">
<div class="text-[var(--color-text-4)]">
{{ t('apiScenario.valuePriority') }}
</div>
<div v-if="scenarioConfigParamTip" class="text-[var(--color-text-1)]">
{{ scenarioConfigParamTip }}
</div>
</div>
</div> -->
<div class="flex items-center gap-[12px]">
<a-button type="secondary" @click="cancelScenarioConfig">{{ t('common.cancel') }}</a-button>
<a-button type="primary" @click="saveScenarioConfig">{{ t('common.confirm') }}</a-button>

View File

@ -148,6 +148,7 @@
import { defaultScenario } from './components/config';
import updateStepStatus from './components/utils';
import { filterAssertions, filterConditionsSqlValidParams } from '@/views/api-test/components/utils';
//
const detail = defineAsyncComponent(() => import('./detail/index.vue'));
@ -169,6 +170,7 @@
]);
const activeScenarioTab = ref<ScenarioParams>(scenarioTabs.value[0] as ScenarioParams);
const executeButtonRef = ref<InstanceType<typeof executeButton>>();
const localExecuteUrl = computed(() => executeButtonRef.value?.localExecuteUrl);
const websocketMap: Record<string | number, WebSocket> = {};
@ -179,11 +181,11 @@
/**
* 开启websocket监听接收执行结果
*/
function debugSocket(scenario: Scenario, executeType?: 'localExec' | 'serverExec', localExecuteUrl?: string) {
function debugSocket(scenario: Scenario, executeType?: 'localExec' | 'serverExec') {
websocketMap[scenario.reportId] = getSocket(
scenario.reportId || '',
executeType === 'localExec' ? '/ws/debug' : '',
executeType === 'localExec' ? localExecuteUrl : ''
executeType === 'localExec' ? localExecuteUrl.value : ''
);
websocketMap[scenario.reportId].addEventListener('message', (event) => {
const data = JSON.parse(event.data);
@ -227,13 +229,12 @@
* @param executeParams 执行参数
* @param isExecute 是否执行否则是调试
* @param executeType 执行类型
* @param localExecuteUrl 本地执行地址
*/
async function realExecute(
executeParams: Pick<ApiScenarioDebugRequest, 'steps' | 'stepDetails' | 'reportId'>,
isExecute?: boolean,
executeType?: 'localExec' | 'serverExec',
localExecuteUrl?: string
envId?: string
) {
try {
activeScenarioTab.value.executeLoading = true;
@ -244,7 +245,7 @@
activeScenarioTab.value.executeFakeErrorCount = 0;
activeScenarioTab.value.stepResponses = {};
activeScenarioTab.value.reportId = executeParams.reportId; // ID
debugSocket(activeScenarioTab.value, executeType, localExecuteUrl); // websocket
debugSocket(activeScenarioTab.value, executeType); // websocket
activeScenarioTab.value.isDebug = !isExecute;
let res;
if (isExecute && executeType !== 'localExec' && !activeScenarioTab.value.isNew) {
@ -252,7 +253,7 @@
res = await executeScenario({
id: activeScenarioTab.value.id,
grouped: false,
environmentId: appStore.getCurrentEnvId || '',
environmentId: envId || appStore.getCurrentEnvId || '',
projectId: appStore.currentProjectId,
scenarioConfig: activeScenarioTab.value.scenarioConfig,
...executeParams,
@ -268,7 +269,7 @@
res = await debugScenario({
id: activeScenarioTab.value.id,
grouped: false,
environmentId: appStore.getCurrentEnvId || '',
environmentId: envId || appStore.getCurrentEnvId || '',
projectId: appStore.currentProjectId,
scenarioConfig: activeScenarioTab.value.scenarioConfig,
stepFileParam: activeScenarioTab.value.stepFileParam,
@ -282,9 +283,9 @@
}),
});
}
if (executeType === 'localExec' && localExecuteUrl) {
if (executeType === 'localExec' && localExecuteUrl.value) {
// debug
await localExecuteApiDebug(localExecuteUrl, res);
await localExecuteApiDebug(localExecuteUrl.value, res);
}
} catch (error) {
// eslint-disable-next-line no-console
@ -298,9 +299,9 @@
/**
* 执行场景
* @param executeType 执行类型
* @param localExecuteUrl 本地执行地址
* @param envId 环境ID
*/
function handleExecute(executeType?: 'localExec' | 'serverExec', localExecuteUrl?: string) {
function handleExecute(executeType?: 'localExec' | 'serverExec', envId?: string) {
const waitingDebugStepDetails: Record<string, ScenarioStepDetails> = {};
const waitTingDebugSteps = filterTree(activeScenarioTab.value.steps, (node) => {
if (node.enable) {
@ -323,7 +324,7 @@
},
true,
executeType,
localExecuteUrl
envId
);
}
@ -431,7 +432,10 @@
if (action === 'execute') {
nextTick(() => {
// tab
handleExecute(executeButtonRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec');
handleExecute(
executeButtonRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec',
defaultScenarioInfo.environmentId
);
});
}
} else {
@ -485,6 +489,7 @@
async function realSaveScenario() {
try {
saveLoading.value = true;
const { assertionConfig } = activeScenarioTab.value.scenarioConfig;
if (activeScenarioTab.value.isNew) {
const res = await addScenario({
...activeScenarioTab.value,
@ -494,6 +499,16 @@
parent: null, // axios
};
}),
scenarioConfig: {
...activeScenarioTab.value.scenarioConfig,
assertionConfig: { ...assertionConfig, assertions: filterAssertions(assertionConfig) },
postProcessorConfig: filterConditionsSqlValidParams(
activeScenarioTab.value.scenarioConfig.postProcessorConfig
),
preProcessorConfig: filterConditionsSqlValidParams(
activeScenarioTab.value.scenarioConfig.preProcessorConfig
),
},
projectId: appStore.currentProjectId,
environmentId: appStore.getCurrentEnvId || '',
});
@ -535,6 +550,16 @@
} else {
await updateScenario({
...activeScenarioTab.value,
scenarioConfig: {
...activeScenarioTab.value.scenarioConfig,
assertionConfig: { ...assertionConfig, assertions: filterAssertions(assertionConfig) },
postProcessorConfig: filterConditionsSqlValidParams(
activeScenarioTab.value.scenarioConfig.postProcessorConfig
),
preProcessorConfig: filterConditionsSqlValidParams(
activeScenarioTab.value.scenarioConfig.preProcessorConfig
),
},
environmentId: appStore.getCurrentEnvId || '',
steps: mapTree(activeScenarioTab.value.steps, (node) => {
return {
@ -572,7 +597,10 @@
activeScenarioTab.value = scenarioTabs.value[isLoadedTabIndex];
// tabid,id
if (action === 'execute') {
handleExecute(executeButtonRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec');
handleExecute(
executeButtonRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec',
typeof record === 'string' ? undefined : record.environmentId
);
}
return;
}
@ -605,7 +633,6 @@
useLeaveTabUnSaveCheck(scenarioTabs.value, ['PROJECT_API_SCENARIO:READ+ADD', 'PROJECT_API_SCENARIO:READ+UPDATE']);
const hasLocalExec = computed(() => executeButtonRef.value?.hasLocalExec);
const localExecuteUrl = computed(() => executeButtonRef.value?.localExecuteUrl);
const isPriorityLocalExec = computed(() => executeButtonRef.value?.isPriorityLocalExec);
const scenarioId = computed(() => activeScenarioTab.value.id);
const scenarioExecuteLoading = computed(() => activeScenarioTab.value.executeLoading);

View File

@ -267,4 +267,6 @@ export default {
'apiScenario.schedule.table.tooltip.disable': 'Scheduled task is disabled',
'apiScenario.save.env': 'Scenario Environment',
'apiScenario.execute.no.step.tips': 'No open step',
'apiScenario.preConditionTip': 'Execute once before the scene step',
'apiScenario.postConditionTip': 'Execute once after the scene step',
};

View File

@ -254,4 +254,6 @@ export default {
'apiScenario.schedule.table.tooltip.disable': '定时任务未开启',
'apiScenario.save.env': '场景保存的环境',
'apiScenario.execute.no.step.tips': '没有开启的步骤',
'apiScenario.preConditionTip': '在场景步骤前分别执行一次',
'apiScenario.postConditionTip': '在场景步骤后分别执行一次',
};