feat(接口测试): 接口测试联调请求体展示

This commit is contained in:
xinxin.wu 2024-08-07 17:23:35 +08:00 committed by Craftsman
parent ec07c38eb1
commit e66789c0b5
5 changed files with 200 additions and 146 deletions

View File

@ -398,6 +398,23 @@
} }
); );
watch(
() => props.diffMode,
(newDiffMode) => {
if (newDiffMode) {
if (editor) {
editor.dispose();
}
initDiffEditor(props.originalValue, props.modelValue);
} else {
if (diffEditor) {
diffEditor.dispose();
}
init();
}
}
);
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (editor) { if (editor) {
editor.dispose(); editor.dispose();

View File

@ -564,10 +564,12 @@
() => attrs.data, () => attrs.data,
(val) => { (val) => {
if (val) { if (val) {
const minTagWidth = 60; // 60
currentColumns.value.forEach((column) => { currentColumns.value.forEach((column) => {
const dataIndex = column.dataIndex as string; const dataIndex = column.dataIndex as string;
if (column.isTag || column.isStringTag) { if (column.isTag || column.isStringTag) {
columnLastWidthMap.value[dataIndex] = getMaxRowTagWidth((val as TableData[]) || [], dataIndex); const lastWidth = getMaxRowTagWidth((val as TableData[]) || [], dataIndex);
columnLastWidthMap.value[dataIndex] = lastWidth < minTagWidth ? minTagWidth : lastWidth;
} }
}); });
} }

View File

@ -57,35 +57,33 @@
{{ t('case.notSetData') }} {{ t('case.notSetData') }}
</div> </div>
<!-- 请求体 --> <!-- 请求体 -->
<div class="title flex items-center justify-between"> <div v-for="item of requestBodyList" :key="item.value">
<div class="detail-item-title-text"> <div v-if="showDiff(item.value)" class="title">
{{ `${t('apiTestManagement.requestBody')}-${previewDetail?.body?.bodyType}` }} {{ item.title }}
</div>
<div
v-if="showDiff(item.value) && hiddenEmptyTable(item.value)"
:style="{ 'padding-bottom': `${getBottomDistance(item.value)}px` }"
>
<MsFormTable
:columns="getBodyColumns(item.value)"
:data="getBodyTableData(item.value)"
:selectable="false"
:show-setting="true"
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_DATA"
:diff-mode="props.mode"
/>
</div>
<div
v-if="showDiff(item.value) && !hiddenEmptyTable(item.value)"
class="not-setting-data"
:style="{ height: `${getBottomDistance(item.value, true)}px` }"
>
{{ t('case.notSetData') }}
</div> </div>
</div> </div>
<div <div class="title">
v-if=" {{ `${t('apiTestManagement.requestBody')}-JSON` }}
(previewDetail?.body?.bodyType === RequestBodyFormat.FORM_DATA ||
previewDetail?.body?.bodyType === RequestBodyFormat.WWW_FORM) &&
showDiff(previewDetail?.body?.bodyType) &&
hiddenEmptyTable(previewDetail?.body?.bodyType)
"
:style="{ 'padding-bottom': `${getBottomDistance(previewDetail.value?.body?.bodyType)}px` }"
>
<MsFormTable
:columns="bodyColumns"
:data="bodyTableData"
:selectable="false"
:show-setting="true"
:table-key="TableKeyEnum.API_TEST_DEBUG_FORM_DATA"
:diff-mode="props.mode"
/>
</div>
<div
v-if="showDiff(previewDetail?.body?.bodyType) && !hiddenEmptyTable(previewDetail?.body?.bodyType)"
class="not-setting-data"
:style="{ height: `${getBottomDistance(previewDetail?.body?.bodyType, true)}px` }"
>
{{ t('case.notSetData') }}
</div> </div>
</template> </template>
@ -113,6 +111,17 @@
const previewDetail = ref<RequestParam>(props.detail); const previewDetail = ref<RequestParam>(props.detail);
const requestBodyList = ref([
{
value: RequestBodyFormat.FORM_DATA,
title: `${t('apiTestManagement.requestBody')}-FORM_DATA`,
},
{
value: RequestBodyFormat.WWW_FORM,
title: `${t('apiTestManagement.requestBody')}-WWW_FORM`,
},
]);
/** /**
* 请求头 * 请求头
*/ */
@ -213,8 +222,9 @@
/** /**
* 请求体 * 请求体
*/ */
const bodyColumns = computed<FormTableColumn[]>(() => {
if ([RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(previewDetail.value?.body?.bodyType)) { function getBodyColumns(bodyType: RequestBodyFormat): FormTableColumn[] {
if ([RequestBodyFormat.FORM_DATA, RequestBodyFormat.WWW_FORM].includes(bodyType)) {
return [ return [
{ {
title: 'apiTestManagement.paramName', title: 'apiTestManagement.paramName',
@ -300,12 +310,12 @@
showTooltip: true, showTooltip: true,
}, },
]; ];
}); }
const bodyTableData = computed(() => { function getBodyTableData(bodyType: string) {
switch (previewDetail.value?.body?.bodyType) { switch (bodyType) {
case RequestBodyFormat.FORM_DATA: case RequestBodyFormat.FORM_DATA:
return (previewDetail.value.body.formDataBody?.formValues || []) return (previewDetail.value.body?.formDataBody?.formValues || [])
.map((e) => ({ .map((e) => ({
...e, ...e,
value: e.paramType === RequestParamsType.FILE ? e.files?.map((file) => file.fileName).join('、') : e.value, value: e.paramType === RequestParamsType.FILE ? e.files?.map((file) => file.fileName).join('、') : e.value,
@ -323,7 +333,7 @@
default: default:
return []; return [];
} }
}); }
const typeKey = computed(() => (props.isApi ? 'api' : 'case')); const typeKey = computed(() => (props.isApi ? 'api' : 'case'));

View File

@ -1,38 +1,46 @@
<template> <template>
<template <div v-if="showDiffEditor" :class="`${bodyCaseCode && bodyDefinedCode ? '' : 'grid grid-cols-2 gap-[24px]'}`">
v-if=" <div v-if="!bodyDefinedCode && bodyCaseCode" class="no-case-data h-full">
[RequestBodyFormat.JSON, RequestBodyFormat.RAW, RequestBodyFormat.XML].includes( {{ t('case.notSetData') }}
previewDefinedDetail?.body?.bodyType </div>
) <template v-if="showDiffEditor">
" <MsCodeEditor
> :model-value="showDiffEditor"
<MsCodeEditor theme="vs"
v-if="previewDefinedDetail?.body?.bodyType === RequestBodyFormat.JSON" height="200px"
:model-value="bodyCaseCode" :class="`${bodyCaseCode ? '' : 'no-case-data-bg'} w-full`"
theme="vs" :language="bodyCodeLanguage"
height="200px" :show-full-screen="false"
:language="bodyCodeLanguage" :show-theme-change="false"
:show-full-screen="false" read-only
:show-theme-change="false" is-adaptive
read-only :diff-mode="diffMode"
is-adaptive :original-value="bodyDefinedCode"
diff-mode >
:original-value="bodyDefinedCode" <template #rightTitle>
> <a-button
<template #rightTitle> type="outline"
<a-button class="arco-btn-outline--secondary p-[0_8px]"
type="outline" size="mini"
class="arco-btn-outline--secondary p-[0_8px]" @click="copyScript(bodyDefinedCode)"
size="mini" >
@click="copyScript(bodyDefinedCode)" <template #icon>
> <MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
<template #icon> </template>
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" /> </a-button>
</template> </template>
</a-button> </MsCodeEditor>
</template> </template>
</MsCodeEditor> <div v-if="!bodyCaseCode && bodyDefinedCode" class="no-case-data h-full"> {{ t('case.notSetData') }} </div>
</template> </div>
<div v-else class="grid grid-cols-2 gap-[24px]">
<div v-if="!bodyCaseCode && !bodyDefinedCode" class="no-json-case-data no-case-data">
{{ t('case.notSetData') }}
</div>
<div v-if="!bodyCaseCode && !bodyDefinedCode" class="no-json-case-data no-case-data">
{{ t('case.notSetData') }}
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -82,8 +90,8 @@
} }
} }
const getBodyCode = (body: ExecuteBody) => { const getBodyCode = (body: ExecuteBody, bodyType: RequestBodyFormat) => {
switch (body?.bodyType) { switch (bodyType) {
case RequestBodyFormat.FORM_DATA: case RequestBodyFormat.FORM_DATA:
return body.formDataBody?.formValues?.map((item: any) => `${item.key}:${item.value}`).join('\n'); return body.formDataBody?.formValues?.map((item: any) => `${item.key}:${item.value}`).join('\n');
case RequestBodyFormat.WWW_FORM: case RequestBodyFormat.WWW_FORM:
@ -91,7 +99,7 @@
case RequestBodyFormat.RAW: case RequestBodyFormat.RAW:
return body.rawBody?.value; return body.rawBody?.value;
case RequestBodyFormat.JSON: case RequestBodyFormat.JSON:
return body.jsonBody?.jsonValue; return body?.jsonBody?.jsonValue;
case RequestBodyFormat.XML: case RequestBodyFormat.XML:
return body.xmlBody?.value; return body.xmlBody?.value;
default: default:
@ -100,10 +108,16 @@
}; };
// Code // Code
const bodyDefinedCode = computed(() => getBodyCode(previewDefinedDetail.value?.body)); const bodyDefinedCode = computed(() => getBodyCode(previewDefinedDetail.value?.body, RequestBodyFormat.JSON));
// Code // Code
const bodyCaseCode = computed(() => getBodyCode(previewCaseDetail.value?.body)); const bodyCaseCode = computed(() => getBodyCode(previewCaseDetail.value?.body, RequestBodyFormat.JSON));
const diffMode = computed(() => {
return !!(bodyDefinedCode.value && bodyCaseCode.value) || (!bodyDefinedCode.value && !bodyCaseCode.value);
});
const showDiffEditor = computed(() => bodyCaseCode.value || bodyDefinedCode.value);
watchEffect(() => { watchEffect(() => {
if (props.definedDetail) { if (props.definedDetail) {
@ -115,4 +129,20 @@
}); });
</script> </script>
<style scoped></style> <style scoped lang="less">
.no-case-data {
height: 100% !important;
border: 1px solid var(--color-border-2);
border-radius: 4px;
@apply flex items-center justify-center;
&.no-json-case-data {
min-height: 200px;
@apply h-full;
}
}
.no-case-data-bg {
:deep(.view-line) {
background: rgb(var(--success-1)) !important;
}
}
</style>

View File

@ -64,27 +64,30 @@
</div> </div>
<!-- 对比 --> <!-- 对比 -->
<div class="diff-container"> <div class="diff-container">
<a-spin class="h-full w-full" :loading="loading"> <MsCard simple auto-height no-content-padding>
<div class="diff-normal"> <a-spin class="h-full w-full p-4" :loading="loading">
<div class="diff-item"> <div class="diff-normal">
<div class="title-type"> [{{ apiDetailInfo?.num }}] {{ apiDetailInfo?.name }} </div> <div class="diff-item">
<DiffItem <div class="title-type"> [{{ apiDetailInfo?.num }}] {{ apiDetailInfo?.name }} </div>
:diff-distance-map="diffDistanceMap" <DiffItem
mode="add" :diff-distance-map="diffDistanceMap"
is-api mode="add"
:detail="apiDefinedRequest as RequestParam" is-api
/> :detail="apiDefinedRequest as RequestParam"
/>
</div>
<div class="diff-item ml-[24px]">
<div class="title-type"> [{{ caseDetail?.num }}] {{ caseDetail?.name }} </div>
<DiffItem :diff-distance-map="diffDistanceMap" mode="delete" :detail="caseDetail as RequestParam" />
</div>
</div> </div>
<div class="diff-item ml-[24px]">
<div class="title-type"> [{{ caseDetail?.num }}] {{ caseDetail?.name }} </div> <DiffRequestBody
<DiffItem :diff-distance-map="diffDistanceMap" mode="delete" :detail="caseDetail as RequestParam" /> :defined-detail="apiDefinedRequest as RequestParam"
</div> :case-detail="caseDetail as RequestParam"
</div> />
<DiffRequestBody </a-spin>
:defined-detail="apiDefinedRequest as RequestParam" </MsCard>
:case-detail="caseDetail as RequestParam"
/>
</a-spin>
</div> </div>
</MsDrawer> </MsDrawer>
</template> </template>
@ -94,6 +97,7 @@
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { TabItem } from '@/components/pure/ms-editable-tab/types'; import { TabItem } from '@/components/pure/ms-editable-tab/types';
import DiffItem from './diffItem.vue'; import DiffItem from './diffItem.vue';
@ -239,7 +243,6 @@
// //
const disAbs = Math.abs(caseData.length - definedData.length); const disAbs = Math.abs(caseData.length - definedData.length);
diffDistanceMap.value[typeKey] = { diffDistanceMap.value[typeKey] = {
case: caseData.length < definedData.length ? disAbs : 0, case: caseData.length < definedData.length ? disAbs : 0,
api: caseData.length > definedData.length ? disAbs : 0, api: caseData.length > definedData.length ? disAbs : 0,
@ -254,6 +257,35 @@
}; };
} }
function getBodyData(bodyType: string) {
switch (bodyType) {
// FORM_DATA
case RequestBodyFormat.FORM_DATA:
const bodyFormDataDiffObj = setDiff(
apiDefinedRequest.value.body.formDataBody?.formValues as any,
caseDetail.value.body.formDataBody?.formValues,
RequestBodyFormat.FORM_DATA
);
apiDefinedRequest.value.body.formDataBody.formValues =
bodyFormDataDiffObj.definedData as ExecuteRequestCommonParam[];
caseDetail.value.body.formDataBody.formValues = bodyFormDataDiffObj.caseData;
break;
// WWW_FORM
case RequestBodyFormat.WWW_FORM:
const bodyWwwFormDiffObj = setDiff(
apiDefinedRequest.value.body.wwwFormBody?.formValues as any,
caseDetail.value.body.wwwFormBody?.formValues,
RequestBodyFormat.WWW_FORM
);
apiDefinedRequest.value.body.wwwFormBody.formValues =
bodyWwwFormDiffObj.definedData as ExecuteRequestCommonParam[];
caseDetail.value.body.wwwFormBody.formValues = bodyWwwFormDiffObj.caseData;
break;
default:
break;
}
}
// //
function processData() { function processData() {
// //
@ -283,39 +315,21 @@
caseDetail.value.rest = restDiffObj.caseData; caseDetail.value.rest = restDiffObj.caseData;
} }
// //
if (apiDefinedRequest.value?.body?.bodyType) { getBodyData(RequestBodyFormat.FORM_DATA);
switch (apiDefinedRequest.value?.body?.bodyType) { getBodyData(RequestBodyFormat.WWW_FORM);
// FORM_DATA
case RequestBodyFormat.FORM_DATA:
const bodyFormDataDiffObj = setDiff(
apiDefinedRequest.value.body.formDataBody?.formValues as any,
caseDetail.value.body.formDataBody?.formValues,
RequestBodyFormat.FORM_DATA
);
apiDefinedRequest.value.body.formDataBody.formValues =
bodyFormDataDiffObj.definedData as ExecuteRequestCommonParam[];
caseDetail.value.body.formDataBody.formValues = bodyFormDataDiffObj.caseData;
break;
// WWW_FORM
case RequestBodyFormat.WWW_FORM:
const bodyWwwFormDiffObj = setDiff(
apiDefinedRequest.value.body.wwwFormBody?.formValues as any,
caseDetail.value.body.wwwFormBody?.formValues,
RequestBodyFormat.WWW_FORM
);
apiDefinedRequest.value.body.wwwFormBody.formValues =
bodyWwwFormDiffObj.definedData as ExecuteRequestCommonParam[];
caseDetail.value.body.wwwFormBody.formValues = bodyWwwFormDiffObj.caseData;
break;
default:
break;
}
}
} }
// //
async function getCaseDetailInfo(id: string) { async function getCaseDetailInfo(id: string) {
try { try {
const res = await getCaseDetail(id); const res = await getCaseDetail(id);
const result = await diffDataRequest(id);
const { caseRequest, apiRequest } = result;
caseDetail.value = {
...caseDetail.value,
...caseRequest,
num: caseDetail.value.num,
};
apiDefinedRequest.value = apiRequest;
let parseRequestBodyResult; let parseRequestBodyResult;
if (res.protocol === 'HTTP') { if (res.protocol === 'HTTP') {
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // id parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // id
@ -324,6 +338,8 @@
...cloneDeep(defaultCaseParams as RequestParam), ...cloneDeep(defaultCaseParams as RequestParam),
...({ ...({
...res, ...res,
...caseRequest,
num: res.num,
url: res.path, url: res.path,
...parseRequestBodyResult, ...parseRequestBodyResult,
} as Partial<TabItem>), } as Partial<TabItem>),
@ -345,28 +361,11 @@
} }
} }
async function getDiffDataRequest(activeApiCaseId: string) {
try {
const result = await diffDataRequest(activeApiCaseId);
const { caseRequest, apiRequest } = result;
caseDetail.value = {
...caseDetail.value,
...caseRequest,
num: caseDetail.value.num,
};
apiDefinedRequest.value = apiRequest;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
async function getRequestDetail(definedId: string, apiCaseId: string) { async function getRequestDetail(definedId: string, apiCaseId: string) {
loading.value = true; loading.value = true;
try { try {
await Promise.all([getApiDetail(definedId), getCaseDetailInfo(apiCaseId)]); await Promise.all([getApiDetail(definedId), getCaseDetailInfo(apiCaseId)]);
await getDiffDataRequest(props.activeApiCaseId);
processData(); processData();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -430,12 +429,8 @@
} }
} }
.diff-container { .diff-container {
padding: 16px; padding: 0 16px;
min-height: calc(100vh - 110px); min-height: calc(100vh - 110px);
border-radius: 12px;
background: white;
box-shadow: 0 0 10px rgba(120 56 135/ 5%);
@apply mx-4;
.diff-normal { .diff-normal {
@apply flex; @apply flex;
.diff-item { .diff-item {