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(() => {
if (editor) {
editor.dispose();

View File

@ -564,10 +564,12 @@
() => attrs.data,
(val) => {
if (val) {
const minTagWidth = 60; // 60
currentColumns.value.forEach((column) => {
const dataIndex = column.dataIndex as string;
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') }}
</div>
<!-- 请求体 -->
<div class="title flex items-center justify-between">
<div class="detail-item-title-text">
{{ `${t('apiTestManagement.requestBody')}-${previewDetail?.body?.bodyType}` }}
<div v-for="item of requestBodyList" :key="item.value">
<div v-if="showDiff(item.value)" class="title">
{{ 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
v-if="
(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 class="title">
{{ `${t('apiTestManagement.requestBody')}-JSON` }}
</div>
</template>
@ -113,6 +111,17 @@
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 [
{
title: 'apiTestManagement.paramName',
@ -300,12 +310,12 @@
showTooltip: true,
},
];
});
}
const bodyTableData = computed(() => {
switch (previewDetail.value?.body?.bodyType) {
function getBodyTableData(bodyType: string) {
switch (bodyType) {
case RequestBodyFormat.FORM_DATA:
return (previewDetail.value.body.formDataBody?.formValues || [])
return (previewDetail.value.body?.formDataBody?.formValues || [])
.map((e) => ({
...e,
value: e.paramType === RequestParamsType.FILE ? e.files?.map((file) => file.fileName).join('、') : e.value,
@ -323,7 +333,7 @@
default:
return [];
}
});
}
const typeKey = computed(() => (props.isApi ? 'api' : 'case'));

View File

@ -1,38 +1,46 @@
<template>
<template
v-if="
[RequestBodyFormat.JSON, RequestBodyFormat.RAW, RequestBodyFormat.XML].includes(
previewDefinedDetail?.body?.bodyType
)
"
>
<MsCodeEditor
v-if="previewDefinedDetail?.body?.bodyType === RequestBodyFormat.JSON"
:model-value="bodyCaseCode"
theme="vs"
height="200px"
:language="bodyCodeLanguage"
:show-full-screen="false"
:show-theme-change="false"
read-only
is-adaptive
diff-mode
:original-value="bodyDefinedCode"
>
<template #rightTitle>
<a-button
type="outline"
class="arco-btn-outline--secondary p-[0_8px]"
size="mini"
@click="copyScript(bodyDefinedCode)"
>
<template #icon>
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
</template>
</a-button>
</template>
</MsCodeEditor>
</template>
<div v-if="showDiffEditor" :class="`${bodyCaseCode && bodyDefinedCode ? '' : 'grid grid-cols-2 gap-[24px]'}`">
<div v-if="!bodyDefinedCode && bodyCaseCode" class="no-case-data h-full">
{{ t('case.notSetData') }}
</div>
<template v-if="showDiffEditor">
<MsCodeEditor
:model-value="showDiffEditor"
theme="vs"
height="200px"
:class="`${bodyCaseCode ? '' : 'no-case-data-bg'} w-full`"
:language="bodyCodeLanguage"
:show-full-screen="false"
:show-theme-change="false"
read-only
is-adaptive
:diff-mode="diffMode"
:original-value="bodyDefinedCode"
>
<template #rightTitle>
<a-button
type="outline"
class="arco-btn-outline--secondary p-[0_8px]"
size="mini"
@click="copyScript(bodyDefinedCode)"
>
<template #icon>
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
</template>
</a-button>
</template>
</MsCodeEditor>
</template>
<div v-if="!bodyCaseCode && bodyDefinedCode" class="no-case-data h-full"> {{ t('case.notSetData') }} </div>
</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>
<script setup lang="ts">
@ -82,8 +90,8 @@
}
}
const getBodyCode = (body: ExecuteBody) => {
switch (body?.bodyType) {
const getBodyCode = (body: ExecuteBody, bodyType: RequestBodyFormat) => {
switch (bodyType) {
case RequestBodyFormat.FORM_DATA:
return body.formDataBody?.formValues?.map((item: any) => `${item.key}:${item.value}`).join('\n');
case RequestBodyFormat.WWW_FORM:
@ -91,7 +99,7 @@
case RequestBodyFormat.RAW:
return body.rawBody?.value;
case RequestBodyFormat.JSON:
return body.jsonBody?.jsonValue;
return body?.jsonBody?.jsonValue;
case RequestBodyFormat.XML:
return body.xmlBody?.value;
default:
@ -100,10 +108,16 @@
};
// Code
const bodyDefinedCode = computed(() => getBodyCode(previewDefinedDetail.value?.body));
const bodyDefinedCode = computed(() => getBodyCode(previewDefinedDetail.value?.body, RequestBodyFormat.JSON));
// 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(() => {
if (props.definedDetail) {
@ -115,4 +129,20 @@
});
</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 class="diff-container">
<a-spin class="h-full w-full" :loading="loading">
<div class="diff-normal">
<div class="diff-item">
<div class="title-type"> [{{ apiDetailInfo?.num }}] {{ apiDetailInfo?.name }} </div>
<DiffItem
:diff-distance-map="diffDistanceMap"
mode="add"
is-api
:detail="apiDefinedRequest as RequestParam"
/>
<MsCard simple auto-height no-content-padding>
<a-spin class="h-full w-full p-4" :loading="loading">
<div class="diff-normal">
<div class="diff-item">
<div class="title-type"> [{{ apiDetailInfo?.num }}] {{ apiDetailInfo?.name }} </div>
<DiffItem
:diff-distance-map="diffDistanceMap"
mode="add"
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 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>
<DiffRequestBody
:defined-detail="apiDefinedRequest as RequestParam"
:case-detail="caseDetail as RequestParam"
/>
</a-spin>
<DiffRequestBody
:defined-detail="apiDefinedRequest as RequestParam"
:case-detail="caseDetail as RequestParam"
/>
</a-spin>
</MsCard>
</div>
</MsDrawer>
</template>
@ -94,6 +97,7 @@
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { TabItem } from '@/components/pure/ms-editable-tab/types';
import DiffItem from './diffItem.vue';
@ -239,7 +243,6 @@
//
const disAbs = Math.abs(caseData.length - definedData.length);
diffDistanceMap.value[typeKey] = {
case: 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() {
//
@ -283,39 +315,21 @@
caseDetail.value.rest = restDiffObj.caseData;
}
//
if (apiDefinedRequest.value?.body?.bodyType) {
switch (apiDefinedRequest.value?.body?.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;
}
}
getBodyData(RequestBodyFormat.FORM_DATA);
getBodyData(RequestBodyFormat.WWW_FORM);
}
//
async function getCaseDetailInfo(id: string) {
try {
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;
if (res.protocol === 'HTTP') {
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // id
@ -324,6 +338,8 @@
...cloneDeep(defaultCaseParams as RequestParam),
...({
...res,
...caseRequest,
num: res.num,
url: res.path,
...parseRequestBodyResult,
} 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);
async function getRequestDetail(definedId: string, apiCaseId: string) {
loading.value = true;
try {
await Promise.all([getApiDetail(definedId), getCaseDetailInfo(apiCaseId)]);
await getDiffDataRequest(props.activeApiCaseId);
processData();
} catch (error) {
// eslint-disable-next-line no-console
@ -430,12 +429,8 @@
}
}
.diff-container {
padding: 16px;
padding: 0 16px;
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 {
@apply flex;
.diff-item {