+
{
const defaultParams = cloneDeep(mockDefaultParams);
defaultParams.id = Date.now().toString();
- defaultParams.mockMatchRule.body.formDataMatch.matchRules.push({
- ...cloneDeep(defaultMatchRuleItem),
+ defaultParams.mockMatchRule.body.formDataBody.matchRules.push({
+ ...defaultMatchRuleItem,
id: Date.now().toString(),
});
- defaultParams.mockMatchRule.header.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
- defaultParams.mockMatchRule.query.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
- defaultParams.mockMatchRule.rest.matchRules.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
- defaultParams.response.headers.push({ ...cloneDeep(defaultMatchRuleItem), id: Date.now().toString() });
- return defaultParams;
+ defaultParams.mockMatchRule.body.wwwFormBody.matchRules.push({
+ ...defaultMatchRuleItem,
+ id: Date.now().toString(),
+ });
+ defaultParams.mockMatchRule.header.matchRules.push({ ...defaultMatchRuleItem, id: Date.now().toString() });
+ defaultParams.mockMatchRule.query.matchRules.push({ ...defaultMatchRuleItem, id: Date.now().toString() });
+ defaultParams.mockMatchRule.rest.matchRules.push({ ...defaultMatchRuleItem, id: Date.now().toString() });
+ defaultParams.response.headers.push({ ...defaultMatchRuleItem, id: Date.now().toString() });
+ return cloneDeep(defaultParams);
};
// mock 匹配规则选项
export const matchRuleOptions = [
diff --git a/frontend/src/views/api-test/components/requestComposition/body.vue b/frontend/src/views/api-test/components/requestComposition/body.vue
index 4c7d5e3d74..0cb7712822 100644
--- a/frontend/src/views/api-test/components/requestComposition/body.vue
+++ b/frontend/src/views/api-test/components/requestComposition/body.vue
@@ -84,11 +84,10 @@
-->
+
- {{ t(statusCodeOptions.find((item) => item.value === record.condition)?.label || '') }}
+ {{
+ record.assertionType === FullResponseAssertionType.RESPONSE_TIME
+ ? t('advanceFilter.operator.le')
+ : t(statusCodeOptions.find((item) => item.value === record.condition)?.label || '')
+ }}
@@ -33,6 +37,7 @@
import { useI18n } from '@/hooks/useI18n';
import { RequestResult, ResponseAssertionTableItem } from '@/models/apiTest/common';
+ import { FullResponseAssertionType } from '@/enums/apiEnum';
import { responseAssertionTypeMap } from '@/views/api-test/components/config';
diff --git a/frontend/src/views/api-test/components/utils.ts b/frontend/src/views/api-test/components/utils.ts
index 4cd885b03e..1a92055c3b 100644
--- a/frontend/src/views/api-test/components/utils.ts
+++ b/frontend/src/views/api-test/components/utils.ts
@@ -45,8 +45,9 @@ export function parseRequestBodyFiles(
const tempSaveLinkFileIds = new Set(); // 临时存储 body 内已保存的关联文件 id 集合,用于对比 saveLinkFileIds 以判断有哪些文件被取消关联
// 获取上传文件和关联文件
const formValues =
- ((body as ExecuteBody).formDataBody?.formValues || (body as MockBody).formDataMatch.matchRules).filter((e) => e) ||
- [];
+ ((body as ExecuteBody).formDataBody?.formValues || (body as MockBody).formDataBody?.matchRules || []).filter(
+ (e) => e
+ ) || [];
for (let i = 0; i < formValues.length; i++) {
const item = formValues[i];
if (item.paramType === RequestParamsType.FILE) {
diff --git a/frontend/src/views/api-test/management/components/management/api/index.vue b/frontend/src/views/api-test/management/components/management/api/index.vue
index d80243a175..46d2d9fe40 100644
--- a/frontend/src/views/api-test/management/components/management/api/index.vue
+++ b/frontend/src/views/api-test/management/components/management/api/index.vue
@@ -151,6 +151,7 @@
import { ProtocolItem } from '@/models/apiTest/common';
import { ApiDefinitionDetail } from '@/models/apiTest/management';
+ import { MockDetail } from '@/models/apiTest/mock';
import { ModuleTreeNode } from '@/models/common';
import {
RequestAuthType,
@@ -380,6 +381,10 @@
}
}
+ async function openApiTabAndDebugMock(mock: MockDetail) {
+ await openApiTab(mock.apiDefinitionId as string);
+ }
+
// 新建接口后没有创建人,创建时间,更新时间的信息。所以需要刷新数据
watch(
() => activeApiTab.value.isNew,
@@ -452,6 +457,7 @@
defineExpose({
openApiTab,
addApiTab,
+ openApiTabAndDebugMock,
refreshTable,
});
diff --git a/frontend/src/views/api-test/management/components/management/api/preview/detail.vue b/frontend/src/views/api-test/management/components/management/api/preview/detail.vue
index b86434d7cb..c5b447e194 100644
--- a/frontend/src/views/api-test/management/components/management/api/preview/detail.vue
+++ b/frontend/src/views/api-test/management/components/management/api/preview/detail.vue
@@ -31,7 +31,6 @@
@@ -88,6 +89,7 @@
import { hasAnyPermission } from '@/utils/permission';
import { ProtocolItem } from '@/models/apiTest/common';
+ import { MockDetail } from '@/models/apiTest/mock';
import { ModuleTreeNode } from '@/models/common';
import {
RequestAuthType,
@@ -323,6 +325,10 @@
}
}
+ function handleMockDebug(mock: MockDetail) {
+ apiRef.value?.openApiTabAndDebugMock(mock);
+ }
+
onBeforeMount(() => {
initMemberOptions();
initProtocolList();
diff --git a/frontend/src/views/api-test/management/components/management/mock/mockDetailDrawer.vue b/frontend/src/views/api-test/management/components/management/mock/mockDetailDrawer.vue
index 7cd9924884..d7987a53f4 100644
--- a/frontend/src/views/api-test/management/components/management/mock/mockDetailDrawer.vue
+++ b/frontend/src/views/api-test/management/components/management/mock/mockDetailDrawer.vue
@@ -20,7 +20,7 @@
v-permission="['PROJECT_API_DEFINITION_MOCK:READ+UPDATE']"
type="icon"
status="secondary"
- @click="isEdit = true"
+ @click="handleChangeEdit"
>
{{ t('common.edit') }}
@@ -37,7 +37,7 @@
-
+
{{ t('apiTestDebug.noneBody') }}
-
+
+
-
+
import { Message } from '@arco-design/web-vue';
+ import { cloneDeep } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
@@ -216,6 +222,7 @@
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
+ import { ResponseDefinition } from '@/models/apiTest/common';
import { MockParams } from '@/models/apiTest/mock';
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
@@ -231,6 +238,7 @@
definitionDetail: RequestParam;
detailId?: string;
isCopy?: boolean;
+ isEditMode?: boolean;
}>();
const emit = defineEmits<{
(e: 'delete'): void;
@@ -245,7 +253,7 @@
});
const loading = ref(false);
- const isEdit = ref(false);
+ const isEdit = ref(props.isEditMode);
const mockDetail = ref(makeDefaultParams());
const isReadOnly = computed(() => !mockDetail.value.isNew && !isEdit.value);
const title = computed(() => {
@@ -287,7 +295,7 @@
.validParams.length;
return `${headerNum > 0 ? headerNum : ''}`;
case RequestComposition.BODY:
- return mockDetail.value.mockMatchRule.body.paramType !== RequestBodyFormat.NONE ? '1' : '';
+ return mockDetail.value.mockMatchRule.body.bodyType !== RequestBodyFormat.NONE ? '1' : '';
case RequestComposition.QUERY:
const queryNum = filterKeyValParams(mockDetail.value.mockMatchRule.query.matchRules, defaultRequestParamsItem)
.validParams.length;
@@ -384,15 +392,16 @@
return [];
}
});
+ // 当前请求 body 的参数名选项集合
const currentBodyKeyOptions = computed(() => {
- switch (mockDetail.value.mockMatchRule.body.paramType) {
+ switch (mockDetail.value.mockMatchRule.body.bodyType) {
case RequestBodyFormat.FORM_DATA:
return filterKeyValParams(
props.definitionDetail.body.formDataBody.formValues,
defaultMatchRuleItem
).validParams.map((e) => ({
label: e.key,
- value: e.value,
+ value: e.key,
paramType: e.paramType,
}));
case RequestBodyFormat.WWW_FORM:
@@ -407,17 +416,71 @@
return [];
}
});
- // 当前代码编辑器的语言
+
+ // 当前请求 body 显示的代码
+ const currentBodyCode = computed({
+ get() {
+ if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.JSON) {
+ return mockDetail.value.mockMatchRule.body.jsonBody.jsonValue;
+ }
+ if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.XML) {
+ return mockDetail.value.mockMatchRule.body.xmlBody.value;
+ }
+ return mockDetail.value.mockMatchRule.body.rawBody.value;
+ },
+ set(val) {
+ if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.JSON) {
+ mockDetail.value.mockMatchRule.body.jsonBody.jsonValue = val;
+ } else if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.XML) {
+ mockDetail.value.mockMatchRule.body.xmlBody.value = val;
+ } else {
+ mockDetail.value.mockMatchRule.body.rawBody.value = val;
+ }
+ },
+ });
+ // 当前请求 body 代码编辑器的语言
const currentCodeLanguage = computed(() => {
- if (mockDetail.value.mockMatchRule.body.paramType === RequestBodyFormat.JSON) {
+ if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.JSON) {
return LanguageEnum.JSON;
}
- if (mockDetail.value.mockMatchRule.body.paramType === RequestBodyFormat.XML) {
+ if (mockDetail.value.mockMatchRule.body.bodyType === RequestBodyFormat.XML) {
return LanguageEnum.XML;
}
return LanguageEnum.PLAINTEXT;
});
+ /**
+ * 添加默认的匹配规则项
+ */
+ function appendDefaultMatchRuleItem() {
+ const { body } = mockDetail.value.mockMatchRule;
+ mockDetail.value.mockMatchRule.body = {
+ ...body,
+ formDataBody: {
+ matchAll: body.formDataBody.matchAll,
+ matchRules: [...body.formDataBody.matchRules, cloneDeep(defaultMatchRuleItem)],
+ },
+ wwwFormBody: {
+ matchAll: body.wwwFormBody.matchAll,
+ matchRules: [...body.wwwFormBody.matchRules, cloneDeep(defaultMatchRuleItem)],
+ },
+ };
+ mockDetail.value.mockMatchRule.header.matchRules = [
+ ...mockDetail.value.mockMatchRule.header.matchRules,
+ cloneDeep(defaultMatchRuleItem),
+ ];
+ mockDetail.value.mockMatchRule.query.matchRules = [
+ ...mockDetail.value.mockMatchRule.query.matchRules,
+ cloneDeep(defaultMatchRuleItem),
+ ];
+ mockDetail.value.mockMatchRule.rest.matchRules = [
+ ...mockDetail.value.mockMatchRule.rest.matchRules,
+ cloneDeep(defaultMatchRuleItem),
+ ];
+ }
+
+ const fileList = ref([]);
+
async function initMockDetail() {
try {
loading.value = true;
@@ -425,34 +488,45 @@
id: props.detailId || '',
projectId: appStore.currentProjectId,
});
- const parseFileResult = parseRequestBodyFiles(res.matching.body);
- const formDataMatch =
- res.matching.body.paramType === RequestBodyFormat.FORM_DATA
- ? res.matching.body.formDataMatch.matchRules.map((item) => {
- const newParamType =
- currentBodyKeyOptions.value.find((e) => e.value === item.key)?.paramType ||
- defaultMatchRuleItem.paramType;
- item.paramType = newParamType;
- item.files = item.files || [];
- return item;
- })
- : res.matching.body.formDataMatch.matchRules;
+ // form-data 的匹配规则含有文件类型,特殊处理
+ const formDataMatch = res.mockMatchRule.body.formDataBody.matchRules.map((item) => {
+ const newParamType =
+ currentBodyKeyOptions.value.find((e) => e.value === item.key)?.paramType || defaultMatchRuleItem.paramType;
+ item.paramType = newParamType;
+ item.files = item.files || [];
+ return item;
+ });
mockDetail.value = {
...res,
id: props.isCopy ? '' : res.id,
isNew: props.isCopy,
+ name: props.isCopy ? `${res.name}_copy` : res.name,
mockMatchRule: {
- ...res.matching,
+ ...res.mockMatchRule,
body: {
- ...res.matching.body,
- formDataMatch: {
- ...res.matching.body.formDataMatch,
+ ...res.mockMatchRule.body,
+ formDataBody: {
+ ...res.mockMatchRule.body.formDataBody,
matchRules: formDataMatch,
},
},
},
+ };
+ // 等formDataMatch解析完毕赋值后才能正确解析 body 内的文件类型值
+ const parseFileResult = parseRequestBodyFiles(mockDetail.value.mockMatchRule.body, [
+ res.response as unknown as ResponseDefinition,
+ ]);
+ mockDetail.value = {
+ ...mockDetail.value,
...parseFileResult,
};
+ fileList.value = mockDetail.value.mockMatchRule.body.binaryBody.file
+ ? [mockDetail.value.mockMatchRule.body.binaryBody.file as unknown as MsFileItem]
+ : [];
+ if (props.isCopy) {
+ appendDefaultMatchRuleItem();
+ }
+ isEdit.value = !!props.isEditMode;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@@ -464,8 +538,12 @@
watch(
() => visible.value,
(val) => {
- if (val && props.detailId) {
- initMockDetail();
+ if (val) {
+ if (props.detailId) {
+ initMockDetail();
+ } else {
+ fileList.value = [];
+ }
}
},
{
@@ -473,8 +551,6 @@
}
);
- const fileList = ref([]);
-
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
try {
if (file?.local && file.file) {
@@ -512,25 +588,17 @@
}
}
- watch(
- () => mockDetail.value.mockMatchRule.body.paramType,
- (val) => {
- if (val === RequestBodyFormat.JSON) {
- mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.jsonBody.jsonValue;
- } else if (val === RequestBodyFormat.XML) {
- mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.xmlBody.value || '';
- } else if (val === RequestBodyFormat.RAW) {
- mockDetail.value.mockMatchRule.body.raw = props.definitionDetail.body.rawBody.value || '';
- }
- }
- );
-
function handleCancel() {
mockDetail.value = makeDefaultParams();
isEdit.value = false;
visible.value = false;
}
+ function handleChangeEdit() {
+ appendDefaultMatchRuleItem();
+ isEdit.value = true;
+ }
+
function handleDelete() {
emit('delete');
handleCancel();
@@ -540,7 +608,14 @@
try {
loading.value = true;
const { body } = mockDetail.value.mockMatchRule;
- const validBodyMatchRules = filterKeyValParams(body.formDataMatch.matchRules, defaultMatchRuleItem).validParams;
+ const validFormDataBodyMatchRules = filterKeyValParams(
+ body.formDataBody.matchRules,
+ defaultMatchRuleItem
+ ).validParams;
+ const validWwwFormBodyMatchRules = filterKeyValParams(
+ body.wwwFormBody.matchRules,
+ defaultMatchRuleItem
+ ).validParams;
const validHeaderMatchRules = filterKeyValParams(
mockDetail.value.mockMatchRule.header.matchRules,
defaultMatchRuleItem
@@ -557,17 +632,26 @@
mockDetail.value.response.headers,
defaultHeaderParamsItem
).validParams;
- const parseFileResult = parseRequestBodyFiles(mockDetail.value.mockMatchRule.body);
- const params = {
+ const parseFileResult = parseRequestBodyFiles(
+ mockDetail.value.mockMatchRule.body,
+ [mockDetail.value.response as unknown as ResponseDefinition],
+ mockDetail.value.uploadFileIds,
+ mockDetail.value.linkFileIds
+ );
+ const params: MockParams = {
...mockDetail.value,
statusCode: mockDetail.value.response.statusCode,
mockMatchRule: {
...mockDetail.value.mockMatchRule,
body: {
...mockDetail.value.mockMatchRule.body,
- formDataMatch: {
- ...mockDetail.value.mockMatchRule.body.formDataMatch,
- matchRules: validBodyMatchRules,
+ formDataBody: {
+ ...mockDetail.value.mockMatchRule.body.formDataBody,
+ matchRules: validFormDataBodyMatchRules,
+ },
+ wwwFormBody: {
+ ...mockDetail.value.mockMatchRule.body.wwwFormBody,
+ matchRules: validWwwFormBodyMatchRules,
},
},
header: {
@@ -587,14 +671,16 @@
...mockDetail.value.response,
headers: validResponseHeaders,
},
- ...parseFileResult,
apiDefinitionId: props.definitionDetail.id,
projectId: appStore.currentProjectId,
+ uploadFileIds: parseFileResult.uploadFileIds,
+ linkFileIds: parseFileResult.linkFileIds,
};
if (isEdit.value) {
await updateMock({
id: mockDetail.value.id || '',
...params,
+ ...parseFileResult,
});
Message.success(t('common.updateSuccess'));
} else {
diff --git a/frontend/src/views/api-test/management/components/management/mock/mockResponse.vue b/frontend/src/views/api-test/management/components/management/mock/mockResponse.vue
index 2f3a10275f..ab6bfb6450 100644
--- a/frontend/src/views/api-test/management/components/management/mock/mockResponse.vue
+++ b/frontend/src/views/api-test/management/components/management/mock/mockResponse.vue
@@ -154,6 +154,7 @@
import paramTable, { ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
+ import { uploadMockTempFile } from '@/api/modules/api-test/management';
import { responseHeaderOption } from '@/config/apiTest';
import { useI18n } from '@/hooks/useI18n';
@@ -164,7 +165,6 @@
const props = defineProps<{
definitionResponses: ResponseItem[];
- uploadTempFileApi?: (...args: any) => Promise; // 上传临时文件接口
disabled: boolean;
}>();
const emit = defineEmits<{
@@ -267,16 +267,17 @@
const fileList = ref([]);
const loading = ref(false);
- async function handleFileChange() {
+
+ async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
try {
- if (fileList.value[0] && fileList.value[0].local && fileList.value[0].file && props.uploadTempFileApi) {
+ if (file?.local && file.file) {
loading.value = true;
- const res = await props.uploadTempFileApi(fileList.value[0].file);
+ const res = await uploadMockTempFile(file.file);
mockResponse.value.body.binaryBody.file = {
- ...fileList.value[0],
+ ...file,
fileId: res.data,
- fileName: fileList.value[0]?.name || '',
- fileAlias: fileList.value[0]?.name || '',
+ fileName: file?.name || '',
+ fileAlias: file?.name || '',
local: true,
};
loading.value = false;
@@ -298,6 +299,19 @@
loading.value = false;
}
}
+
+ watch(
+ () => mockResponse.value.body.binaryBody.file?.fileId,
+ () => {
+ fileList.value = mockResponse.value.body.binaryBody.file
+ ? [mockResponse.value.body.binaryBody.file as unknown as MsFileItem]
+ : [];
+ },
+ {
+ deep: true,
+ immediate: true,
+ }
+ );