feat(项目管理): 环境管理断言不包括响应体

This commit is contained in:
xinxin.wu 2024-03-08 11:53:46 +08:00 committed by 刘瑞斌
parent 45929cd675
commit b491c783ad
7 changed files with 254 additions and 133 deletions

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<paramsTable <paramsTable
v-model:params="innerParams" v-model:params="condition.assertions"
:selectable="false" :selectable="false"
:columns="columns" :columns="columns"
:scroll="{ minWidth: '700px' }" :scroll="{ minWidth: '700px' }"
@ -12,25 +12,32 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineModel } from 'vue'; import { useVModel } from '@vueuse/core';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index'; import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue'; import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import type { ExecuteAssertionItem } from '@/models/apiTest/common';
interface Param { interface Param {
[key: string]: any; [key: string]: any;
assertions: ExecuteAssertionItem[];
} }
const innerParams = defineModel<Param[]>('modelValue', { default: [] }); const props = defineProps<{
data: Param;
const emit = defineEmits<{
(e: 'change', val: any[]): void; //
}>(); }>();
const emit = defineEmits<{
(e: 'change', data: Param): void;
}>();
const condition = useVModel(props, 'data', emit);
const defaultParamItem = { const defaultParamItem = {
responseHeader: '', header: '',
matchCondition: '', condition: '',
matchValue: '', expectedValue: '',
enable: true, enable: true,
}; };
@ -52,24 +59,24 @@
const columns: ParamTableColumn[] = [ const columns: ParamTableColumn[] = [
{ {
title: 'ms.assertion.responseHeader', // title: 'ms.assertion.responseHeader', //
dataIndex: 'responseHeader', dataIndex: 'header',
slotName: 'responseHeader', slotName: 'header',
showInTable: true, showInTable: true,
showDrag: true, showDrag: true,
options: responseHeaderOption, options: responseHeaderOption,
}, },
{ {
title: 'ms.assertion.matchCondition', // title: 'ms.assertion.matchCondition', //
dataIndex: 'matchCondition', dataIndex: 'condition',
slotName: 'matchCondition', slotName: 'condition',
showInTable: true, showInTable: true,
showDrag: true, showDrag: true,
options: statusCodeOptions, options: statusCodeOptions,
}, },
{ {
title: 'ms.assertion.matchValue', // title: 'ms.assertion.matchValue', //
dataIndex: 'matchValue', dataIndex: 'expectedValue',
slotName: 'matchValue', slotName: 'expectedValue',
showInTable: true, showInTable: true,
showDrag: true, showDrag: true,
}, },
@ -82,10 +89,11 @@
showDrag: true, showDrag: true,
}, },
]; ];
function handleParamTableChange(resultArr: any[], isInit?: boolean) { function handleParamTableChange(resultArr: any[], isInit?: boolean) {
innerParams.value = [...resultArr]; condition.value.assertions = [...resultArr];
if (!isInit) { if (!isInit) {
emit('change', resultArr); emit('change', { ...condition.value });
} }
} }
</script> </script>

View File

@ -4,29 +4,37 @@
<span class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseTime') }}</span> <span class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseTime') }}</span>
<span class="text-[var(--color-text-4)]">(ms)</span> <span class="text-[var(--color-text-4)]">(ms)</span>
</div> </div>
<a-input-number v-model:model-value="innerParams" :step="100" mode="button" /> <a-input-number
v-model="condition.expectedValue"
:step="100"
mode="button"
@blur="
emit('change', {
...condition,
})
"
/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useVModel } from '@vueuse/core';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { ExecuteAssertion } from '../type';
const { t } = useI18n(); const { t } = useI18n();
interface ResponseTimeTabProps {
responseTime: number;
}
const props = defineProps<{ const props = defineProps<{
value: ResponseTimeTabProps; data: ExecuteAssertion;
}>(); }>();
const innerParams = ref(props.value.responseTime);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'change', val: ResponseTimeTabProps): void; // (e: 'change', data: ExecuteAssertion): void;
}>(); }>();
watchEffect(() => {
emit('change', { responseTime: innerParams.value }); const condition = useVModel(props, 'data', emit);
});
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -3,12 +3,11 @@
<div> <div>
<div class="mb-[8px]">{{ t('ms.assertion.statusCode') }}</div> <div class="mb-[8px]">{{ t('ms.assertion.statusCode') }}</div>
<a-select <a-select
v-model="selectValue" v-model="condition.condition"
class="w-[157px]" class="w-[157px]"
@change=" @change="
emit('change', { emit('change', {
statusCode: statusCode, ...condition,
selectValue: selectValue,
}) })
" "
> >
@ -17,15 +16,14 @@
</a-option> </a-option>
</a-select> </a-select>
</div> </div>
<a-input-number <a-input
v-if="showInput" v-if="showInput"
v-model:modelValue="statusCode" v-model="condition.expectedValue"
hide-button hide-button
class="w-[157px]" class="w-[157px]"
@change=" @change="
emit('change', { emit('change', {
statusCode: statusCode, ...condition,
selectValue: selectValue,
}) })
" "
/> />
@ -34,28 +32,30 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { useVModel } from '@vueuse/core';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index'; import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
interface Param {
id: string;
name: string;
assertionType: string;
condition: string;
expectedValue: string;
}
const props = defineProps<{ const props = defineProps<{
value: { data: Param;
statusCode: number;
selectValue: string;
};
}>(); }>();
const emit = defineEmits(['change']); const emit = defineEmits<{
const selectValue = ref<string>(''); (e: 'change', data: Param): void;
const statusCode = ref<number>(200); }>();
const showInput = computed(() => selectValue.value !== 'none' && selectValue.value !== ''); const condition = useVModel(props, 'data', emit);
const showInput = computed(() => condition.value.condition !== 'none' && condition.value.condition !== '');
const { t } = useI18n();
watchEffect(() => {
selectValue.value = props.value.selectValue;
statusCode.value = props.value.statusCode;
});
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -1,6 +1,6 @@
<template> <template>
<paramsTable <paramsTable
v-model:params="innerParams" v-model:params="condition.variableAssertionItems"
:selectable="false" :selectable="false"
:columns="columns" :columns="columns"
:scroll="{ minWidth: '700px' }" :scroll="{ minWidth: '700px' }"
@ -10,27 +10,30 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index'; import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue'; import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
interface Param { interface Param {
[key: string]: any; [key: string]: any;
variableAssertionItems: any[];
} }
const props = defineProps<{ const props = defineProps<{
value?: Param[]; data: Param;
}>(); }>();
const innerParams = ref(props.value || []);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'change'): void; // (e: 'change', data: Param): void;
}>(); }>();
const condition = useVModel(props, 'data', emit);
const defaultParamItem = { const defaultParamItem = {
responseHeader: '', variableName: '',
matchCondition: '', condition: '',
matchValue: '', expectedValue: '',
enable: true, enable: true,
}; };
@ -52,7 +55,7 @@
const columns: ParamTableColumn[] = [ const columns: ParamTableColumn[] = [
{ {
title: 'ms.assertion.variableName', // title: 'ms.assertion.variableName', //
dataIndex: 'key', dataIndex: 'variableName',
slotName: 'key', slotName: 'key',
showInTable: true, showInTable: true,
showDrag: true, showDrag: true,
@ -60,16 +63,16 @@
}, },
{ {
title: 'ms.assertion.matchCondition', // title: 'ms.assertion.matchCondition', //
dataIndex: 'matchCondition', dataIndex: 'condition',
slotName: 'matchCondition', slotName: 'condition',
showInTable: true, showInTable: true,
showDrag: true, showDrag: true,
options: statusCodeOptions, options: statusCodeOptions,
}, },
{ {
title: 'ms.assertion.matchValue', // title: 'ms.assertion.matchValue', //
dataIndex: 'value', dataIndex: 'expectedValue',
slotName: 'value', slotName: 'expectedValue',
showInTable: true, showInTable: true,
showDrag: true, showDrag: true,
}, },
@ -83,9 +86,9 @@
}, },
]; ];
function handleParamTableChange(resultArr: any[], isInit?: boolean) { function handleParamTableChange(resultArr: any[], isInit?: boolean) {
innerParams.value = [...resultArr]; condition.value.variableAssertionItems = [...resultArr];
if (!isInit) { if (!isInit) {
emit('change'); emit('change', { ...condition.value });
} }
} }
</script> </script>

View File

@ -12,9 +12,9 @@
</template> </template>
</a-dropdown> </a-dropdown>
<div v-if="showBody" class="ms-assertion-body"> <div v-if="showBody" class="ms-assertion-body">
<VueDraggable v-model="selectItems" class="ms-assertion-body-left" ghost-class="ghost" handle=".sort-handle"> <VueDraggable v-model="assertions" class="ms-assertion-body-left" ghost-class="ghost" handle=".sort-handle">
<div <div
v-for="(item, index) in selectItems" v-for="(item, index) in assertions"
:key="item.id" :key="item.id"
class="ms-assertion-body-left-item" class="ms-assertion-body-left-item"
:class="{ :class="{
@ -25,7 +25,7 @@
> >
<div class="ms-assertion-body-left-item-row"> <div class="ms-assertion-body-left-item-row">
<span class="ms-assertion-body-left-item-row-num">{{ index + 1 }}</span> <span class="ms-assertion-body-left-item-row-num">{{ index + 1 }}</span>
<span class="ms-assertion-body-left-item-row-title">{{ item.label }}</span> <span class="ms-assertion-body-left-item-row-title">{{ item.name }}</span>
</div> </div>
<div class="ms-assertion-body-left-item-switch"> <div class="ms-assertion-body-left-item-switch">
<div class="ms-assertion-body-left-item-switch-action"> <div class="ms-assertion-body-left-item-switch-action">
@ -50,40 +50,46 @@
</MsTableMoreAction> </MsTableMoreAction>
</div> </div>
<a-switch type="line" size="small" /> <a-switch v-model:model-value="item.enable" type="line" size="small" />
</div> </div>
</div> </div>
</VueDraggable> </VueDraggable>
<section class="ms-assertion-body-right"> <section class="ms-assertion-body-right">
<StatusCodeTab <!-- 响应头 -->
v-if="valueKey === 'statusCode'"
:value="codeTabState.statusCode"
@change="(val) => handleChange(val, 'statusCode')"
/>
<ResponseHeaderTab <ResponseHeaderTab
v-if="valueKey === 'responseHeader'" v-if="valueKey === ResponseAssertionType.RESPONSE_HEADER"
:value="codeTabState.responseHeader" v-model:data="getCurrentItemState"
@change="(val) => handleChange(val, 'responseHeader')" @change="handleChange"
/> />
<!-- 状态码 -->
<StatusCodeTab
v-if="valueKey === ResponseAssertionType.RESPONSE_CODE"
v-model:data="getCurrentItemState"
@change="handleChange"
/>
<!-- 响应体 -->
<ResponseBodyTab <ResponseBodyTab
v-if="valueKey === 'responseBody'" v-if="valueKey === ResponseAssertionType.RESPONSE_BODY"
:value="codeTabState.responseBody" :value="getCurrentItemState"
@change="(val) => handleChange(val, 'responseBody')" @change="handleChange"
/> />
<!-- 响应时间 -->
<ResponseTimeTab <ResponseTimeTab
v-if="valueKey === 'responseTime'" v-if="valueKey === ResponseAssertionType.RESPONSE_TIME"
:value="codeTabState.responseTime" v-model:data="getCurrentItemState"
@="(val) => handleChange(val, 'responseTime')" @change="handleChange"
/> />
<!-- 变量 -->
<VariableTab <VariableTab
v-if="valueKey === 'variable'" v-if="valueKey === ResponseAssertionType.VARIABLE"
:value="codeTabState.variable" v-model:data="getCurrentItemState"
@change="(val) => handleChange(val, 'variable')" @change="handleChange"
/> />
<!-- 脚本 -->
<ScriptTab <ScriptTab
v-if="valueKey === 'script'" v-if="valueKey === ResponseAssertionType.SCRIPT"
:value="codeTabState.script" :value="getCurrentItemState"
@change="(val) => handleChange(val, 'script')" @change="handleChange"
/> />
</section> </section>
</div> </div>
@ -92,6 +98,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineModel } from 'vue'; import { defineModel } from 'vue';
import { cloneDeep } from 'lodash-es';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
@ -107,7 +114,9 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { MsAssertionItem, ValueObject } from './type'; import { ResponseAssertionType } from '@/enums/apiEnum';
import { ExecuteAssertion, MsAssertionItem } from './type';
defineOptions({ defineOptions({
name: 'MsAssertion', name: 'MsAssertion',
@ -116,26 +125,28 @@
const { t } = useI18n(); const { t } = useI18n();
// key // key
const focusKey = ref<string>(''); const focusKey = ref<string>('');
// //
const selectItems = defineModel<any[]>('params', { default: [] }); const assertions = defineModel<any[]>('params', { default: [] });
// Itemkey // Itemkey
const activeKey = ref<string>(''); const activeKey = ref<string>(assertions.value[0].id);
// valueKey // value
const valueKey = computed(() => { const valueKey = computed(() => {
return activeKey.value && selectItems.value.find((item) => item.id === activeKey.value)?.value; return activeKey.value && assertions.value.find((item) => item.id === activeKey.value)?.assertionType;
}); });
//
const codeTabState = computed({ //
const getCurrentItemState = computed({
get: () => { get: () => {
return (selectItems.value.find((item) => item.id === activeKey.value)?.valueObj || {}) as ValueObject; return assertions.value.find((item) => item.id === activeKey.value);
}, },
set: (val: ValueObject) => { set: (val: ExecuteAssertion) => {
const currentIndex = selectItems.value.findIndex((item) => item.id === val.assertionType); const currentIndex = assertions.value.findIndex((item) => item.id === activeKey.value);
const tmpArr = selectItems.value; const tmpArr = assertions.value;
tmpArr[currentIndex].valueObj = { ...val }; tmpArr[currentIndex] = cloneDeep(val);
selectItems.value = tmpArr; assertions.value = tmpArr;
}, },
}); });
const itemMoreActions: ActionsItem[] = [ const itemMoreActions: ActionsItem[] = [
{ {
label: 'common.copy', label: 'common.copy',
@ -150,62 +161,111 @@
const assertOptionSource = [ const assertOptionSource = [
{ {
label: t('ms.assertion.statusCode'), label: t('ms.assertion.statusCode'),
value: 'statusCode', value: ResponseAssertionType.RESPONSE_CODE,
}, },
{ {
label: t('ms.assertion.responseHeader'), label: t('ms.assertion.responseHeader'),
value: 'responseHeader', value: ResponseAssertionType.RESPONSE_HEADER,
}, },
{ {
label: t('ms.assertion.responseBody'), label: t('ms.assertion.responseBody'),
value: 'responseBody', value: ResponseAssertionType.RESPONSE_BODY,
}, },
{ {
label: t('ms.assertion.responseTime'), label: t('ms.assertion.responseTime'),
value: 'responseTime', value: ResponseAssertionType.RESPONSE_TIME,
}, },
{ {
label: t('ms.assertion.param'), label: t('ms.assertion.param'),
value: 'variable', value: ResponseAssertionType.VARIABLE,
}, },
{ {
label: t('ms.assertion.script'), label: t('ms.assertion.script'),
value: 'script', value: ResponseAssertionType.SCRIPT,
}, },
]; ];
// //
const showBody = computed(() => { const showBody = computed(() => {
return selectItems.value.length > 0; return assertions.value.length > 0;
}); });
// dropdown // dropdown
const handleSelect = (value: string | number | Record<string, any> | undefined) => { const handleSelect = (value: string | number | Record<string, any> | undefined) => {
const id = new Date().getTime().toString();
const tmpObj = { const tmpObj = {
label: assertOptionSource.find((item) => item.value === value)?.label || '', name: assertOptionSource.find((item) => item.value === value)?.label || '',
value: value as string, assertionType: value,
id: new Date().getTime().toString(), id,
enable: true,
}; };
if (activeKey.value) {
const currentIndex = selectItems.value.findIndex((item) => item.id === activeKey.value); switch (value) {
const tmpArr = selectItems.value; //
tmpArr.splice(currentIndex, 0, tmpObj); case ResponseAssertionType.RESPONSE_HEADER:
selectItems.value = tmpArr; assertions.value.push({
} else { ...tmpObj,
selectItems.value.push(tmpObj); assertions: [],
});
break;
//
case ResponseAssertionType.RESPONSE_CODE:
assertions.value.push({
...tmpObj,
condition: '',
expectedValue: '',
});
break;
case ResponseAssertionType.RESPONSE_BODY:
assertions.value.push({
...tmpObj,
assertionBodyType: '',
jsonPathAssertion: {
assertions: [],
},
xpathAssertion: {
assertions: [],
},
regexAssertion: {
assertions: [],
},
bodyAssertionDataByType: {},
});
break;
//
case ResponseAssertionType.RESPONSE_TIME:
assertions.value.push({
...tmpObj,
expectedValue: 0,
});
break;
case ResponseAssertionType.VARIABLE:
assertions.value.push({
...tmpObj,
condition: '',
expectedValue: '',
variableAssertionItems: [],
});
break;
case ResponseAssertionType.SCRIPT:
break;
default:
break;
} }
activeKey.value = tmpObj.id; activeKey.value = id;
}; };
const handleMoreActionSelect = (event: ActionsItem, item: MsAssertionItem) => { const handleMoreActionSelect = (event: ActionsItem, item: MsAssertionItem) => {
const currentIndex = selectItems.value.findIndex((tmpItem) => tmpItem.id === item.id); const currentIndex = assertions.value.findIndex((tmpItem) => tmpItem.id === item.id);
if (event.eventTag === 'delete') { if (event.eventTag === 'delete') {
selectItems.value.splice(currentIndex, 1); assertions.value.splice(currentIndex, 1);
activeKey.value = currentIndex > 0 ? selectItems.value[currentIndex - 1].id : ''; activeKey.value = currentIndex > 0 ? assertions.value[currentIndex - 1].id : '';
} else { } else {
// copy item // copy item
const tmpObj = { ...selectItems.value[currentIndex], id: new Date().getTime().valueOf().toString() }; const tmpObj = { ...assertions.value[currentIndex], id: new Date().getTime().valueOf().toString() };
const tmpArr = selectItems.value; const tmpArr = assertions.value;
tmpArr.splice(currentIndex, 0, tmpObj); tmpArr.splice(currentIndex, 0, tmpObj);
selectItems.value = tmpArr; assertions.value = tmpArr;
activeKey.value = tmpObj.id; activeKey.value = tmpObj.id;
} }
}; };
@ -215,9 +275,33 @@
activeKey.value = item.id; activeKey.value = item.id;
}; };
const handleChange = (val: any, key: string) => { const handleChange = (val: any) => {
codeTabState[key] = { ...val, assertionType: key }; switch (val.assertionType) {
case ResponseAssertionType.RESPONSE_HEADER:
getCurrentItemState.value = { ...val };
break;
case ResponseAssertionType.RESPONSE_CODE:
getCurrentItemState.value = { ...val };
break;
case ResponseAssertionType.RESPONSE_BODY:
break;
case ResponseAssertionType.RESPONSE_TIME:
getCurrentItemState.value = { ...val };
break;
case ResponseAssertionType.VARIABLE:
getCurrentItemState.value = { ...val };
break;
case ResponseAssertionType.SCRIPT:
break;
default:
break;
}
}; };
watchEffect(() => {
console.log(getCurrentItemState.value);
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -1,3 +1,5 @@
import type { ExecuteAssertionItem } from '@/models/apiTest/common';
export interface ValueObject { export interface ValueObject {
assertionType: string; assertionType: string;
[key: string]: any; [key: string]: any;
@ -8,3 +10,14 @@ export interface MsAssertionItem {
value: string; value: string;
valueObj: ValueObject; valueObj: ValueObject;
} }
export interface ExecuteAssertion {
assertionType: string;
enable: boolean;
name: string;
id: string;
assertions: ExecuteAssertionItem[];
expectedValue: any;
condition: string;
variableAssertionItems: ExecuteAssertionItem[];
}

View File

@ -264,17 +264,22 @@
@change="handleTypeCheckingColChange(false)" @change="handleTypeCheckingColChange(false)"
/> />
</template> </template>
<template #responseHeader="{ record, columnConfig, rowIndex }"> <!-- 响应头 -->
<a-select v-model="record.responseHeader" class="param-input" size="mini" @change="() => addTableLine(rowIndex)"> <template #header="{ record, columnConfig, rowIndex }">
<a-select v-model="record.header" class="param-input" size="mini" @change="() => addTableLine(rowIndex)">
<a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option> <a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option>
</a-select> </a-select>
</template> </template>
<template #matchCondition="{ record, columnConfig }"> <!-- 匹配条件 -->
<template #condition="{ record, columnConfig }">
<a-select v-model="record.condition" size="mini" class="param-input"> <a-select v-model="record.condition" size="mini" class="param-input">
<a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option> <a-option v-for="item in columnConfig.options" :key="item.value" :value="item.value">{{
t(item.label)
}}</a-option>
</a-select> </a-select>
</template> </template>
<template #matchValue="{ record, rowIndex, columnConfig }"> <!-- 匹配值 -->
<template #expectedValue="{ record, rowIndex, columnConfig }">
<a-tooltip <a-tooltip
v-if="columnConfig.hasRequired" v-if="columnConfig.hasRequired"
:content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')" :content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')"
@ -291,7 +296,7 @@
<div>*</div> <div>*</div>
</MsButton> </MsButton>
</a-tooltip> </a-tooltip>
<a-input v-model="record.matchValue" size="mini" class="param-input" /> <a-input v-model="record.expectedValue" size="mini" class="param-input" />
</template> </template>
<template #project="{ record, rowIndex }"> <template #project="{ record, rowIndex }">
<a-select <a-select