feat(接口测试): 联调接口测试接口用例同步合并保存
This commit is contained in:
parent
9842103d3e
commit
192a82fff5
|
@ -55,6 +55,7 @@ import {
|
|||
GetModuleTreeUrl,
|
||||
GetPoolId,
|
||||
GetPoolOptionUrl,
|
||||
getSyncedCaseDetailUrl,
|
||||
GetTrashModuleCountUrl,
|
||||
GetTrashModuleTreeUrl,
|
||||
ignoreEveryTimeApiChangeUrl,
|
||||
|
@ -125,6 +126,7 @@ import {
|
|||
DefinitionHistoryItem,
|
||||
DefinitionHistoryPageParams,
|
||||
DefinitionReferencePageParams,
|
||||
diffSyncParams,
|
||||
EnvModule,
|
||||
ImportApiDefinitionParams,
|
||||
mockParams,
|
||||
|
@ -330,10 +332,14 @@ export function ignoreEveryTimeChange(id: string, ignore: boolean) {
|
|||
export function caseTableBatchSync(data: TableQueryParams) {
|
||||
return MSR.post({ url: caseTableBatchSyncUrl, data });
|
||||
}
|
||||
// // 接口测试-接口用例-定义对比用例
|
||||
// 接口测试-接口用例-定义对比用例
|
||||
export function diffDataRequest(id: string) {
|
||||
return MSR.get({ url: `${diffDataUrl}/${id}` });
|
||||
}
|
||||
// 接口测试-接口用例-定义对比用例-同步-获取同步后的用例详情
|
||||
export function getSyncedCaseDetail(data: diffSyncParams) {
|
||||
return MSR.post({ url: getSyncedCaseDetailUrl, data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock
|
||||
|
|
|
@ -42,6 +42,7 @@ export const clearThisChangeUrl = '/api/case/api-change/clear'; // 接口定义-
|
|||
export const caseTableBatchSyncUrl = '/api/case/batch/api-change/sync'; // 接口测试-接口管理-接口用例-批量同步
|
||||
export const ignoreEveryTimeApiChangeUrl = '/api/case/api-change/ignore'; // 接口测试-接口用例-忽略每次接口变更
|
||||
export const diffDataUrl = '/api/case/api/compare'; // 接口测试-接口用例-定义对比用例
|
||||
export const getSyncedCaseDetailUrl = '/api/case/api-change/sync'; // 接口测试-接口用例-定义对比用例-同步-获取同步后的用例详情
|
||||
|
||||
/**
|
||||
* Mock
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
// 编辑器实例,每次调用组件都会创建独立的实例
|
||||
let editor: monaco.editor.IStandaloneCodeEditor;
|
||||
// 编辑器diffEditor实例 开启diffMode则会初始化
|
||||
let diffEditor: monaco.editor.IStandaloneDiffEditor;
|
||||
let diffEditor: monaco.editor.IStandaloneDiffEditor | null;
|
||||
const codeContainerRef = ref();
|
||||
|
||||
// 用于全屏的容器 ref
|
||||
|
@ -403,12 +403,18 @@
|
|||
(newDiffMode) => {
|
||||
if (newDiffMode) {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
// 确保编辑器实例的 DOM 节点存在且是当前组件的一部分
|
||||
const editorDomNode = editor.getDomNode();
|
||||
if (editorDomNode && editorDomNode.parentNode) {
|
||||
editor.dispose();
|
||||
}
|
||||
}
|
||||
initDiffEditor(props.originalValue, props.modelValue);
|
||||
} else {
|
||||
if (diffEditor) {
|
||||
// 清空上一次的初始结果,避免初始编辑器已存在Error
|
||||
diffEditor.dispose();
|
||||
diffEditor = null;
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
|
||||
import { BatchApiParams, ModuleTreeNode, TableQueryParams } from '../common';
|
||||
import { ExecuteRequestParams, ResponseDefinition } from './common';
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
// 定义-自定义字段
|
||||
export interface ApiDefinitionCustomField {
|
||||
|
@ -419,3 +420,10 @@ export interface batchSyncForm {
|
|||
syncItems: syncItem;
|
||||
deleteRedundantParam: boolean;
|
||||
}
|
||||
// 对比同步参数
|
||||
export interface diffSyncParams {
|
||||
syncItems: syncItem; // 同步项
|
||||
id: string;
|
||||
deleteRedundantParam: boolean; // 是否删除多余参数
|
||||
apiCaseRequest: RequestParam; // 用例详情请求request
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
ref="createAndEditCaseDrawerRef"
|
||||
v-bind="$attrs"
|
||||
@load-case="(id)=>getCaseDetailInfo(id as string)"
|
||||
@show-diff="showDiffDrawer"
|
||||
/>
|
||||
<a-divider :margin="0"></a-divider>
|
||||
<MsTab
|
||||
|
@ -88,7 +89,7 @@
|
|||
:active-defined-id="activeDefinedId"
|
||||
@close="closeDifferent"
|
||||
@clear-this-change="clearThisChangeHandler"
|
||||
@sync="syncHandler"
|
||||
@sync="syncParamsHandler"
|
||||
/>
|
||||
</div>
|
||||
<tab-case-dependency v-else-if="activeKey === 'reference'" :source-id="caseDetail.id" />
|
||||
|
@ -404,9 +405,10 @@
|
|||
getCaseDetailInfo(caseId.value);
|
||||
}
|
||||
|
||||
function syncHandler(id: string) {
|
||||
// TODO 这里需要调用同步合并后的详情接口回显详情
|
||||
createAndEditCaseDrawerRef.value?.open(id, caseDetail.value, false);
|
||||
// 同步参数
|
||||
async function syncParamsHandler(mergedRequestParam: RequestParam) {
|
||||
caseDetail.value = { ...caseDetail.value, ...mergedRequestParam };
|
||||
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value, false);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
:active-defined-id="activeDefinedId"
|
||||
@close="closeDifferent"
|
||||
@clear-this-change="clearThisChangeHandler"
|
||||
@sync="syncHandler"
|
||||
@sync="syncParamsHandler"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="reference" :title="t('apiTestManagement.reference')" class="px-[18px] py-[16px]">
|
||||
|
@ -351,9 +351,10 @@
|
|||
emit('loadCase', props.detail.id as string);
|
||||
}
|
||||
|
||||
function syncHandler(id: string) {
|
||||
// TODO 这里需要调用同步合并后的详情接口回显详情
|
||||
createAndEditCaseDrawerRef.value?.open(id, caseDetail.value, false);
|
||||
// 同步参数
|
||||
function syncParamsHandler(mergedRequestParam: RequestParam) {
|
||||
caseDetail.value = { ...caseDetail.value, ...mergedRequestParam };
|
||||
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value, false);
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
|
@ -315,7 +315,7 @@
|
|||
:active-defined-id="activeDefinedId"
|
||||
@close="closeDifferent"
|
||||
@clear-this-change="handleClearThisChange"
|
||||
@sync="syncHandler"
|
||||
@sync="syncParamsHandler"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
@ -1034,10 +1034,12 @@
|
|||
}
|
||||
|
||||
// 对比抽屉同步成功打开编辑
|
||||
function syncHandler(definedId: string) {
|
||||
// TODO 这里调用同步后的最新的合并后的详情,打开编辑抽屉用户手动保存更新即可生效
|
||||
createAndEditCaseDrawerRef.value?.open(definedId, caseDetail.value as RequestParam, false);
|
||||
async function syncParamsHandler(mergedRequestParam: RequestParam) {
|
||||
await getCaseDetailInfo(activeApiCaseId.value);
|
||||
caseDetail.value = { ...caseDetail.value, ...mergedRequestParam };
|
||||
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value as RequestParam, false);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
loadCaseList,
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-for="item of requestBodyList" :key="item.value">
|
||||
<div class="grid grid-cols-2 gap-[24px]">
|
||||
<div v-if="getBodyDefinedCode(item.value) || getBodyCaseCode(item.value)" class="grid grid-cols-2 gap-[24px]">
|
||||
<div class="title">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
|
@ -47,7 +47,10 @@
|
|||
{{ t('case.notSetData') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="grid grid-cols-2 gap-[24px]">
|
||||
<div
|
||||
v-if="!getShowDiffer(item.value) && (getBodyDefinedCode(item.value) || getBodyCaseCode(item.value))"
|
||||
class="grid grid-cols-2 gap-[24px]"
|
||||
>
|
||||
<div
|
||||
v-if="!getBodyCaseCode(item.value) && !getBodyDefinedCode(item.value)"
|
||||
class="no-json-case-data no-case-data"
|
||||
|
|
|
@ -13,12 +13,11 @@
|
|||
<div class="flex w-full items-center justify-between">
|
||||
<div>{{ t('case.apiAndCaseDiff') }}</div>
|
||||
<div class="flex items-center text-[14px]">
|
||||
<div class="-mt-[2px] mr-[8px]"> {{ t('case.syncItem') }}</div>
|
||||
<a-checkbox-group v-model="checkType">
|
||||
<div v-if="caseDetail.inconsistentWithApi" class="-mt-[2px] mr-[8px]"> {{ t('case.syncItem') }}</div>
|
||||
<a-checkbox-group v-if="caseDetail.inconsistentWithApi" v-model="checkType">
|
||||
<a-checkbox v-for="item of checkList" :key="item.value" :value="item.value">
|
||||
<div class="flex items-center"
|
||||
>{{ item.label }}
|
||||
|
||||
<div class="flex items-center">
|
||||
{{ item.label }}
|
||||
<a-tooltip v-if="item.tooltip" :content="item.tooltip" position="top">
|
||||
<div class="flex items-center">
|
||||
<icon-question-circle
|
||||
|
@ -30,7 +29,7 @@
|
|||
</div>
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<a-divider direction="vertical" :margin="0" class="!mr-[8px]"></a-divider>
|
||||
<a-divider v-if="caseDetail.inconsistentWithApi" direction="vertical" :margin="0" class="!mr-[8px]" />
|
||||
<a-switch
|
||||
v-model:model-value="form.ignoreApiChange"
|
||||
:before-change="(val) => changeIgnore(val)"
|
||||
|
@ -38,28 +37,41 @@
|
|||
/>
|
||||
<div class="ml-[8px]">{{ t('case.ignoreAllChange') }}</div>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<a-switch v-model:model-value="form.deleteRedundantParam" size="small" />
|
||||
<div class="ml-[8px] font-normal text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div>
|
||||
<a-switch
|
||||
v-if="caseDetail.inconsistentWithApi"
|
||||
v-model:model-value="form.deleteRedundantParam"
|
||||
size="small"
|
||||
/>
|
||||
<div v-if="caseDetail.inconsistentWithApi" class="ml-[8px] font-normal text-[var(--color-text-1)]">{{
|
||||
t('case.deleteNotCorrespondValue')
|
||||
}}</div>
|
||||
<a-divider direction="vertical" :margin="0" class="!ml-[8px]"></a-divider>
|
||||
<a-button class="mx-[12px]" type="secondary" @click="cancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button class="mr-[12px]" type="outline" @click="clearThisChangeHandler">
|
||||
<a-button
|
||||
v-if="caseDetail.inconsistentWithApi"
|
||||
class="mr-[12px]"
|
||||
type="outline"
|
||||
@click="clearThisChangeHandler"
|
||||
>
|
||||
{{ t('case.ignoreThisChange') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="syncLoading" :disabled="!checkType.length" @click="confirmSync">
|
||||
{{ t('case.apiSyncChange') }}
|
||||
{{ caseDetail.inconsistentWithApi ? t('case.apiSyncChange') : t('common.confirm') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 图例 -->
|
||||
<div class="legend-container">
|
||||
<div class="item mr-[24px]">
|
||||
<div class="legend add"></div>
|
||||
{{ t('case.diffAdd') }}
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="legend delete"></div>
|
||||
{{ t('common.delete') }}
|
||||
<div class="flex items-center">
|
||||
<div class="item mr-[8px]">
|
||||
<div class="legend add"></div>
|
||||
{{ t('case.diffAdd') }}
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="legend delete"></div>
|
||||
{{ t('common.delete') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 对比 -->
|
||||
|
@ -81,7 +93,6 @@
|
|||
<DiffItem :diff-distance-map="diffDistanceMap" mode="delete" :detail="caseDetail as RequestParam" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DiffRequestBody
|
||||
:defined-detail="apiDefinedRequest as RequestParam"
|
||||
:case-detail="caseDetail as RequestParam"
|
||||
|
@ -108,12 +119,13 @@
|
|||
diffDataRequest,
|
||||
getCaseDetail,
|
||||
getDefinitionDetail,
|
||||
getSyncedCaseDetail,
|
||||
ignoreEveryTimeChange,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { EnableKeyValueParam, ExecuteRequestCommonParam } from '@/models/apiTest/common';
|
||||
import type { syncItem } from '@/models/apiTest/management';
|
||||
import type { diffSyncParams, syncItem } from '@/models/apiTest/management';
|
||||
import { ApiDefinitionDetail } from '@/models/apiTest/management';
|
||||
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
|
||||
|
||||
|
@ -130,7 +142,7 @@
|
|||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'clearThisChange'): void;
|
||||
(e: 'sync', activeDefinedId: string): void;
|
||||
(e: 'sync', mergeRequest: RequestParam): void;
|
||||
}>();
|
||||
|
||||
const showDiffVisible = defineModel<boolean>('visible', {
|
||||
|
@ -165,8 +177,6 @@
|
|||
query: false,
|
||||
rest: false,
|
||||
},
|
||||
noticeApiCaseCreator: true,
|
||||
noticeApiScenarioCreator: true,
|
||||
ignoreApiChange: false,
|
||||
};
|
||||
|
||||
|
@ -175,21 +185,13 @@
|
|||
const form = ref({ ...initForm });
|
||||
|
||||
function cancel() {
|
||||
form.value = { ...initForm };
|
||||
checkType.value = [];
|
||||
showDiffVisible.value = false;
|
||||
emit('close');
|
||||
}
|
||||
const syncLoading = ref<boolean>(false);
|
||||
// 同步
|
||||
function confirmSync() {
|
||||
// 处理同步类型参数
|
||||
checkType.value.forEach((e: any) => {
|
||||
const key = e.toLowerCase() as keyof syncItem;
|
||||
form.value.syncItems[key] = true;
|
||||
});
|
||||
|
||||
emit('sync', props.activeDefinedId);
|
||||
showDiffVisible.value = false;
|
||||
}
|
||||
const syncLoading = ref<boolean>(false);
|
||||
|
||||
const defaultCaseParams = inject<RequestParam>('defaultCaseParams');
|
||||
const caseDetail = ref<Record<string, any>>({});
|
||||
|
@ -318,10 +320,43 @@
|
|||
getBodyData(RequestBodyFormat.FORM_DATA);
|
||||
getBodyData(RequestBodyFormat.WWW_FORM);
|
||||
}
|
||||
const syncCaseDetail = ref<Record<string, any>>({});
|
||||
|
||||
// 同步
|
||||
async function confirmSync() {
|
||||
if (!caseDetail.value.inconsistentWithApi) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
syncLoading.value = true;
|
||||
try {
|
||||
// 处理同步类型参数
|
||||
checkType.value.forEach((e: any) => {
|
||||
const key = e.toLowerCase() as keyof syncItem;
|
||||
form.value.syncItems[key] = true;
|
||||
});
|
||||
const { id, request } = syncCaseDetail.value;
|
||||
|
||||
const params: diffSyncParams = {
|
||||
...form.value,
|
||||
apiCaseRequest: request,
|
||||
id,
|
||||
};
|
||||
const mergeRequest = await getSyncedCaseDetail(params);
|
||||
emit('sync', mergeRequest);
|
||||
cancel();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
syncLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用例详情
|
||||
async function getCaseDetailInfo(id: string) {
|
||||
try {
|
||||
const res = await getCaseDetail(id);
|
||||
syncCaseDetail.value = res;
|
||||
const result = await diffDataRequest(id);
|
||||
const { caseRequest, apiRequest } = result;
|
||||
caseDetail.value = {
|
||||
|
|
|
@ -279,9 +279,8 @@
|
|||
watch(
|
||||
[() => configList.value, () => cardItemList.value],
|
||||
() => {
|
||||
const configValue = resetConfigEditList(configList.value);
|
||||
const cardItemValue = resetConfigEditList(cardItemList.value);
|
||||
|
||||
const configValue = resetConfigEditList(cloneDeep(configList.value));
|
||||
const cardItemValue = resetConfigEditList(cloneDeep(cardItemList.value));
|
||||
const isisEqualList = props.isGroup ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig);
|
||||
if (!isEqual(configValue, isisEqualList) || (!isEqual(cardItemValue, isisEqualList) && !isInit.value)) {
|
||||
if (isInit.value) {
|
||||
|
|
|
@ -616,7 +616,6 @@
|
|||
...currentItem,
|
||||
...formValue,
|
||||
};
|
||||
|
||||
innerCardList.value = innerCardList.value.map((item: configItem) => {
|
||||
if (item.id === currentItem.id) {
|
||||
return {
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
apiBugCount: 0,
|
||||
scenarioBugCount: 0,
|
||||
testPlanName: '',
|
||||
defaultLayout: true,
|
||||
defaultLayout: false,
|
||||
});
|
||||
|
||||
const isGroup = computed(() => route.query.type === 'GROUP');
|
||||
|
|
Loading…
Reference in New Issue