feat(接口测试): 联调接口测试接口用例同步合并保存

This commit is contained in:
xinxin.wu 2024-08-08 16:43:59 +08:00 committed by Craftsman
parent 9842103d3e
commit 192a82fff5
12 changed files with 117 additions and 55 deletions

View File

@ -55,6 +55,7 @@ import {
GetModuleTreeUrl, GetModuleTreeUrl,
GetPoolId, GetPoolId,
GetPoolOptionUrl, GetPoolOptionUrl,
getSyncedCaseDetailUrl,
GetTrashModuleCountUrl, GetTrashModuleCountUrl,
GetTrashModuleTreeUrl, GetTrashModuleTreeUrl,
ignoreEveryTimeApiChangeUrl, ignoreEveryTimeApiChangeUrl,
@ -125,6 +126,7 @@ import {
DefinitionHistoryItem, DefinitionHistoryItem,
DefinitionHistoryPageParams, DefinitionHistoryPageParams,
DefinitionReferencePageParams, DefinitionReferencePageParams,
diffSyncParams,
EnvModule, EnvModule,
ImportApiDefinitionParams, ImportApiDefinitionParams,
mockParams, mockParams,
@ -330,10 +332,14 @@ export function ignoreEveryTimeChange(id: string, ignore: boolean) {
export function caseTableBatchSync(data: TableQueryParams) { export function caseTableBatchSync(data: TableQueryParams) {
return MSR.post({ url: caseTableBatchSyncUrl, data }); return MSR.post({ url: caseTableBatchSyncUrl, data });
} }
// // 接口测试-接口用例-定义对比用例 // 接口测试-接口用例-定义对比用例
export function diffDataRequest(id: string) { export function diffDataRequest(id: string) {
return MSR.get({ url: `${diffDataUrl}/${id}` }); return MSR.get({ url: `${diffDataUrl}/${id}` });
} }
// 接口测试-接口用例-定义对比用例-同步-获取同步后的用例详情
export function getSyncedCaseDetail(data: diffSyncParams) {
return MSR.post({ url: getSyncedCaseDetailUrl, data });
}
/** /**
* Mock * Mock

View File

@ -42,6 +42,7 @@ export const clearThisChangeUrl = '/api/case/api-change/clear'; // 接口定义-
export const caseTableBatchSyncUrl = '/api/case/batch/api-change/sync'; // 接口测试-接口管理-接口用例-批量同步 export const caseTableBatchSyncUrl = '/api/case/batch/api-change/sync'; // 接口测试-接口管理-接口用例-批量同步
export const ignoreEveryTimeApiChangeUrl = '/api/case/api-change/ignore'; // 接口测试-接口用例-忽略每次接口变更 export const ignoreEveryTimeApiChangeUrl = '/api/case/api-change/ignore'; // 接口测试-接口用例-忽略每次接口变更
export const diffDataUrl = '/api/case/api/compare'; // 接口测试-接口用例-定义对比用例 export const diffDataUrl = '/api/case/api/compare'; // 接口测试-接口用例-定义对比用例
export const getSyncedCaseDetailUrl = '/api/case/api-change/sync'; // 接口测试-接口用例-定义对比用例-同步-获取同步后的用例详情
/** /**
* Mock * Mock

View File

@ -89,7 +89,7 @@
// //
let editor: monaco.editor.IStandaloneCodeEditor; let editor: monaco.editor.IStandaloneCodeEditor;
// diffEditor diffMode // diffEditor diffMode
let diffEditor: monaco.editor.IStandaloneDiffEditor; let diffEditor: monaco.editor.IStandaloneDiffEditor | null;
const codeContainerRef = ref(); const codeContainerRef = ref();
// ref // ref
@ -403,12 +403,18 @@
(newDiffMode) => { (newDiffMode) => {
if (newDiffMode) { if (newDiffMode) {
if (editor) { if (editor) {
editor.dispose(); // DOM
const editorDomNode = editor.getDomNode();
if (editorDomNode && editorDomNode.parentNode) {
editor.dispose();
}
} }
initDiffEditor(props.originalValue, props.modelValue); initDiffEditor(props.originalValue, props.modelValue);
} else { } else {
if (diffEditor) { if (diffEditor) {
// Error
diffEditor.dispose(); diffEditor.dispose();
diffEditor = null;
} }
init(); init();
} }

View File

@ -8,6 +8,7 @@ import {
import { BatchApiParams, ModuleTreeNode, TableQueryParams } from '../common'; import { BatchApiParams, ModuleTreeNode, TableQueryParams } from '../common';
import { ExecuteRequestParams, ResponseDefinition } from './common'; import { ExecuteRequestParams, ResponseDefinition } from './common';
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
// 定义-自定义字段 // 定义-自定义字段
export interface ApiDefinitionCustomField { export interface ApiDefinitionCustomField {
@ -419,3 +420,10 @@ export interface batchSyncForm {
syncItems: syncItem; syncItems: syncItem;
deleteRedundantParam: boolean; deleteRedundantParam: boolean;
} }
// 对比同步参数
export interface diffSyncParams {
syncItems: syncItem; // 同步项
id: string;
deleteRedundantParam: boolean; // 是否删除多余参数
apiCaseRequest: RequestParam; // 用例详情请求request
}

View File

@ -57,6 +57,7 @@
ref="createAndEditCaseDrawerRef" ref="createAndEditCaseDrawerRef"
v-bind="$attrs" v-bind="$attrs"
@load-case="(id)=>getCaseDetailInfo(id as string)" @load-case="(id)=>getCaseDetailInfo(id as string)"
@show-diff="showDiffDrawer"
/> />
<a-divider :margin="0"></a-divider> <a-divider :margin="0"></a-divider>
<MsTab <MsTab
@ -88,7 +89,7 @@
:active-defined-id="activeDefinedId" :active-defined-id="activeDefinedId"
@close="closeDifferent" @close="closeDifferent"
@clear-this-change="clearThisChangeHandler" @clear-this-change="clearThisChangeHandler"
@sync="syncHandler" @sync="syncParamsHandler"
/> />
</div> </div>
<tab-case-dependency v-else-if="activeKey === 'reference'" :source-id="caseDetail.id" /> <tab-case-dependency v-else-if="activeKey === 'reference'" :source-id="caseDetail.id" />
@ -404,9 +405,10 @@
getCaseDetailInfo(caseId.value); getCaseDetailInfo(caseId.value);
} }
function syncHandler(id: string) { //
// TODO async function syncParamsHandler(mergedRequestParam: RequestParam) {
createAndEditCaseDrawerRef.value?.open(id, caseDetail.value, false); caseDetail.value = { ...caseDetail.value, ...mergedRequestParam };
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value, false);
} }
onBeforeMount(() => { onBeforeMount(() => {

View File

@ -74,7 +74,7 @@
:active-defined-id="activeDefinedId" :active-defined-id="activeDefinedId"
@close="closeDifferent" @close="closeDifferent"
@clear-this-change="clearThisChangeHandler" @clear-this-change="clearThisChangeHandler"
@sync="syncHandler" @sync="syncParamsHandler"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="reference" :title="t('apiTestManagement.reference')" class="px-[18px] py-[16px]"> <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); emit('loadCase', props.detail.id as string);
} }
function syncHandler(id: string) { //
// TODO function syncParamsHandler(mergedRequestParam: RequestParam) {
createAndEditCaseDrawerRef.value?.open(id, caseDetail.value, false); caseDetail.value = { ...caseDetail.value, ...mergedRequestParam };
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value, false);
} }
watch( watch(

View File

@ -315,7 +315,7 @@
:active-defined-id="activeDefinedId" :active-defined-id="activeDefinedId"
@close="closeDifferent" @close="closeDifferent"
@clear-this-change="handleClearThisChange" @clear-this-change="handleClearThisChange"
@sync="syncHandler" @sync="syncParamsHandler"
/> />
</template> </template>
@ -1034,10 +1034,12 @@
} }
// //
function syncHandler(definedId: string) { async function syncParamsHandler(mergedRequestParam: RequestParam) {
// TODO await getCaseDetailInfo(activeApiCaseId.value);
createAndEditCaseDrawerRef.value?.open(definedId, caseDetail.value as RequestParam, false); caseDetail.value = { ...caseDetail.value, ...mergedRequestParam };
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value as RequestParam, false);
} }
defineExpose({ defineExpose({
loadCaseList, loadCaseList,
}); });

View File

@ -1,6 +1,6 @@
<template> <template>
<div v-for="item of requestBodyList" :key="item.value"> <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"> <div class="title">
{{ item.title }} {{ item.title }}
</div> </div>
@ -47,7 +47,10 @@
{{ t('case.notSetData') }} {{ t('case.notSetData') }}
</div> </div>
</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 <div
v-if="!getBodyCaseCode(item.value) && !getBodyDefinedCode(item.value)" v-if="!getBodyCaseCode(item.value) && !getBodyDefinedCode(item.value)"
class="no-json-case-data no-case-data" class="no-json-case-data no-case-data"

View File

@ -13,12 +13,11 @@
<div class="flex w-full items-center justify-between"> <div class="flex w-full items-center justify-between">
<div>{{ t('case.apiAndCaseDiff') }}</div> <div>{{ t('case.apiAndCaseDiff') }}</div>
<div class="flex items-center text-[14px]"> <div class="flex items-center text-[14px]">
<div class="-mt-[2px] mr-[8px]"> {{ t('case.syncItem') }}</div> <div v-if="caseDetail.inconsistentWithApi" class="-mt-[2px] mr-[8px]"> {{ t('case.syncItem') }}</div>
<a-checkbox-group v-model="checkType"> <a-checkbox-group v-if="caseDetail.inconsistentWithApi" v-model="checkType">
<a-checkbox v-for="item of checkList" :key="item.value" :value="item.value"> <a-checkbox v-for="item of checkList" :key="item.value" :value="item.value">
<div class="flex items-center" <div class="flex items-center">
>{{ item.label }} {{ item.label }}
<a-tooltip v-if="item.tooltip" :content="item.tooltip" position="top"> <a-tooltip v-if="item.tooltip" :content="item.tooltip" position="top">
<div class="flex items-center"> <div class="flex items-center">
<icon-question-circle <icon-question-circle
@ -30,7 +29,7 @@
</div> </div>
</a-checkbox> </a-checkbox>
</a-checkbox-group> </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 <a-switch
v-model:model-value="form.ignoreApiChange" v-model:model-value="form.ignoreApiChange"
:before-change="(val) => changeIgnore(val)" :before-change="(val) => changeIgnore(val)"
@ -38,28 +37,41 @@
/> />
<div class="ml-[8px]">{{ t('case.ignoreAllChange') }}</div> <div class="ml-[8px]">{{ t('case.ignoreAllChange') }}</div>
<a-divider direction="vertical" :margin="8"></a-divider> <a-divider direction="vertical" :margin="8"></a-divider>
<a-switch v-model:model-value="form.deleteRedundantParam" size="small" /> <a-switch
<div class="ml-[8px] font-normal text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div> 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-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="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') }} {{ t('case.ignoreThisChange') }}
</a-button> </a-button>
<a-button type="primary" :loading="syncLoading" :disabled="!checkType.length" @click="confirmSync"> <a-button type="primary" :loading="syncLoading" :disabled="!checkType.length" @click="confirmSync">
{{ t('case.apiSyncChange') }} {{ caseDetail.inconsistentWithApi ? t('case.apiSyncChange') : t('common.confirm') }}
</a-button> </a-button>
</div> </div>
</div> </div>
</template> </template>
<!-- 图例 --> <!-- 图例 -->
<div class="legend-container"> <div class="legend-container">
<div class="item mr-[24px]"> <div class="flex items-center">
<div class="legend add"></div> <div class="item mr-[8px]">
{{ t('case.diffAdd') }} <div class="legend add"></div>
</div> {{ t('case.diffAdd') }}
<div class="item"> </div>
<div class="legend delete"></div> <div class="item">
{{ t('common.delete') }} <div class="legend delete"></div>
{{ t('common.delete') }}
</div>
</div> </div>
</div> </div>
<!-- 对比 --> <!-- 对比 -->
@ -81,7 +93,6 @@
<DiffItem :diff-distance-map="diffDistanceMap" mode="delete" :detail="caseDetail as RequestParam" /> <DiffItem :diff-distance-map="diffDistanceMap" mode="delete" :detail="caseDetail as RequestParam" />
</div> </div>
</div> </div>
<DiffRequestBody <DiffRequestBody
:defined-detail="apiDefinedRequest as RequestParam" :defined-detail="apiDefinedRequest as RequestParam"
:case-detail="caseDetail as RequestParam" :case-detail="caseDetail as RequestParam"
@ -108,12 +119,13 @@
diffDataRequest, diffDataRequest,
getCaseDetail, getCaseDetail,
getDefinitionDetail, getDefinitionDetail,
getSyncedCaseDetail,
ignoreEveryTimeChange, ignoreEveryTimeChange,
} from '@/api/modules/api-test/management'; } from '@/api/modules/api-test/management';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { EnableKeyValueParam, ExecuteRequestCommonParam } from '@/models/apiTest/common'; 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 { ApiDefinitionDetail } from '@/models/apiTest/management';
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum'; import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
@ -130,7 +142,7 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'close'): void; (e: 'close'): void;
(e: 'clearThisChange'): void; (e: 'clearThisChange'): void;
(e: 'sync', activeDefinedId: string): void; (e: 'sync', mergeRequest: RequestParam): void;
}>(); }>();
const showDiffVisible = defineModel<boolean>('visible', { const showDiffVisible = defineModel<boolean>('visible', {
@ -165,8 +177,6 @@
query: false, query: false,
rest: false, rest: false,
}, },
noticeApiCaseCreator: true,
noticeApiScenarioCreator: true,
ignoreApiChange: false, ignoreApiChange: false,
}; };
@ -175,21 +185,13 @@
const form = ref({ ...initForm }); const form = ref({ ...initForm });
function cancel() { function cancel() {
form.value = { ...initForm };
checkType.value = [];
showDiffVisible.value = false; showDiffVisible.value = false;
emit('close'); 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); const syncLoading = ref<boolean>(false);
showDiffVisible.value = false;
}
const defaultCaseParams = inject<RequestParam>('defaultCaseParams'); const defaultCaseParams = inject<RequestParam>('defaultCaseParams');
const caseDetail = ref<Record<string, any>>({}); const caseDetail = ref<Record<string, any>>({});
@ -318,10 +320,43 @@
getBodyData(RequestBodyFormat.FORM_DATA); getBodyData(RequestBodyFormat.FORM_DATA);
getBodyData(RequestBodyFormat.WWW_FORM); 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) { async function getCaseDetailInfo(id: string) {
try { try {
const res = await getCaseDetail(id); const res = await getCaseDetail(id);
syncCaseDetail.value = res;
const result = await diffDataRequest(id); const result = await diffDataRequest(id);
const { caseRequest, apiRequest } = result; const { caseRequest, apiRequest } = result;
caseDetail.value = { caseDetail.value = {

View File

@ -279,9 +279,8 @@
watch( watch(
[() => configList.value, () => cardItemList.value], [() => configList.value, () => cardItemList.value],
() => { () => {
const configValue = resetConfigEditList(configList.value); const configValue = resetConfigEditList(cloneDeep(configList.value));
const cardItemValue = resetConfigEditList(cardItemList.value); const cardItemValue = resetConfigEditList(cloneDeep(cardItemList.value));
const isisEqualList = props.isGroup ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig); const isisEqualList = props.isGroup ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig);
if (!isEqual(configValue, isisEqualList) || (!isEqual(cardItemValue, isisEqualList) && !isInit.value)) { if (!isEqual(configValue, isisEqualList) || (!isEqual(cardItemValue, isisEqualList) && !isInit.value)) {
if (isInit.value) { if (isInit.value) {

View File

@ -616,7 +616,6 @@
...currentItem, ...currentItem,
...formValue, ...formValue,
}; };
innerCardList.value = innerCardList.value.map((item: configItem) => { innerCardList.value = innerCardList.value.map((item: configItem) => {
if (item.id === currentItem.id) { if (item.id === currentItem.id) {
return { return {

View File

@ -61,7 +61,7 @@
apiBugCount: 0, apiBugCount: 0,
scenarioBugCount: 0, scenarioBugCount: 0,
testPlanName: '', testPlanName: '',
defaultLayout: true, defaultLayout: false,
}); });
const isGroup = computed(() => route.query.type === 'GROUP'); const isGroup = computed(() => route.query.type === 'GROUP');