fix(order): preserve submitted review status
This commit is contained in:
@@ -521,9 +521,12 @@ export class AcceptanceRun {
|
||||
const headers = { Accept: 'application/json', ...extraHeaders }
|
||||
if (token) headers.Authorization = `Bearer ${token}`
|
||||
if (body !== undefined) headers['Content-Type'] = 'application/json; charset=utf-8'
|
||||
const requestBody = path === '/api/auth/login' && body?.username && !body?.loginId
|
||||
? { ...body, loginId: body.username, username: undefined }
|
||||
: body
|
||||
try {
|
||||
const response = await fetch(`${this.options.gateway}${path}`, {
|
||||
method, headers, body: body === undefined ? undefined : JSON.stringify(body),
|
||||
method, headers, body: requestBody === undefined ? undefined : JSON.stringify(requestBody),
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
})
|
||||
const content = await response.text()
|
||||
|
||||
@@ -25,6 +25,23 @@ function rotate(values, offset) {
|
||||
return [...values.slice(shift), ...values.slice(0, shift)]
|
||||
}
|
||||
function key(context, operation) { return `${context.marker}-${operation}`.slice(0, 128) }
|
||||
function declarationCargo(source, index) {
|
||||
const number = (value) => Number.isFinite(Number(value)) ? Number(value) : 0
|
||||
const optionalNumber = (value) => value === null || value === undefined || value === '' ? null : number(value)
|
||||
return {
|
||||
itemNo: number(source?.itemNo) || index + 1,
|
||||
cargoName: String(source?.cargoName || ''), cargoNameEn: source?.cargoNameEn || null,
|
||||
skuNo: source?.skuNo || null, model: source?.model || null,
|
||||
packageCount: number(source?.packageCount), totalQuantity: number(source?.totalQuantity),
|
||||
lengthCm: number(source?.lengthCm), widthCm: number(source?.widthCm), heightCm: number(source?.heightCm),
|
||||
grossWeightKg: number(source?.grossWeightKg), volumeCbm: number(source?.volumeCbm),
|
||||
netWeightKg: optionalNumber(source?.netWeightKg), hsCode: source?.hsCode || null,
|
||||
customsHsCode: source?.customsHsCode || null, unitsPerPackage: optionalNumber(source?.unitsPerPackage),
|
||||
unitDeclaredValue: optionalNumber(source?.unitDeclaredValue), declaredValue: optionalNumber(source?.declaredValue),
|
||||
currency: source?.currency || null, material: source?.material || null, usage: source?.usage || null,
|
||||
brand: source?.brand || null, cargoRemark: source?.cargoRemark || source?.remark || null,
|
||||
}
|
||||
}
|
||||
export function orderPersistenceFacts(detail) {
|
||||
return JSON.stringify({
|
||||
order: detail?.order ?? null,
|
||||
@@ -353,7 +370,7 @@ function buildPayload(context, profile, master, productImageFileId) {
|
||||
const selectedBoxes = BOXES.slice(0, skuCount)
|
||||
const orderProfile = context.context?.orderProfile || {}
|
||||
const isPrivate = master.destinationType === 'PRIVATE'
|
||||
const pickupMethod = orderProfile.pickupMethod || 'PICKUP'
|
||||
const pickupMethod = orderProfile.pickupMethod || 'SELF_EXPRESS'
|
||||
const receiver = isPrivate ? {
|
||||
companyName: `${companyName} 私人收件人`, contactName: `收件人-${context.marker}`.slice(0, 64),
|
||||
phone: '12025550188', email: 'private-receiver@example.com', countryCode: 'US',
|
||||
@@ -405,7 +422,7 @@ function buildPayload(context, profile, master, productImageFileId) {
|
||||
shipmentId: master.destinationType === 'FBA' ? shipmentId : null,
|
||||
referenceId: master.destinationType === 'FBA' ? referenceId : null,
|
||||
upcNo: `${Date.now()}${index}`.slice(-12),
|
||||
productImageFileId, dangerousGoods: false,
|
||||
productImageFileId, dangerousGoods: false, woodenProduct: false,
|
||||
cargoRemark: `${context.marker} boxType=${box.boxType}`,
|
||||
}
|
||||
}),
|
||||
@@ -665,9 +682,50 @@ export async function run(context) {
|
||||
pageOk(inboundPage) && records(inboundPage).some((row) => id(row.id) === inboundId),
|
||||
`inboundId=${inboundId}, total=${inboundPage?.total ?? 'n/a'}`)
|
||||
|
||||
const editableDetailResponse = await context.request('GET', firstLegDetailPath)
|
||||
const editableDetail = context.apiSuccess(MODULE, 'Admin 读取审核后客户申报资料', editableDetailResponse)
|
||||
const declarationVersion = Number(editableDetail?.order?.version)
|
||||
const declarationRemark = `${context.marker} Admin 修改客户申报资料`
|
||||
const declarationPayload = {
|
||||
expectedVersion: declarationVersion,
|
||||
customerRemark: declarationRemark,
|
||||
expectedArrivalAt: editableDetail?.order?.expectedArrivalAt || editableDetail?.summary?.expectedArrivalAt || null,
|
||||
channelCode: editableDetail?.order?.channelCode || editableDetail?.summary?.channelCode,
|
||||
channelName: editableDetail?.order?.channelName || editableDetail?.summary?.channelName,
|
||||
cargoes: (editableDetail?.cargoes || []).map(declarationCargo),
|
||||
}
|
||||
const declarationUpdateResponse = await context.request('PUT',
|
||||
`/api/platform/v1/orders/${encodeURIComponent(orderId)}/declaration?tenantId=${encodeURIComponent(tenantId)}`,
|
||||
declarationPayload)
|
||||
const declarationUpdate = context.apiSuccess(MODULE, 'Admin 修改审核后客户申报资料', declarationUpdateResponse)
|
||||
context.assert(MODULE, '申报修改保持订单号入仓码和渠道且版本递增', declarationUpdateResponse,
|
||||
Number.isFinite(declarationVersion) && Number(declarationUpdate?.version) === declarationVersion + 1
|
||||
&& id(declarationUpdate?.inboundCode) === id(inbound.summary.inboundCode)
|
||||
&& declarationUpdate?.channelChanged === false,
|
||||
`version=${declarationVersion}->${declarationUpdate?.version ?? 'missing'}, inboundCode=${declarationUpdate?.inboundCode ?? 'missing'}`,
|
||||
CONTRACT_OPTIONS)
|
||||
|
||||
const staleDeclarationResponse = await context.request('PUT',
|
||||
`/api/platform/v1/orders/${encodeURIComponent(orderId)}/declaration?tenantId=${encodeURIComponent(tenantId)}`,
|
||||
{ ...declarationPayload, customerRemark: `${declarationRemark} stale` })
|
||||
context.assert(MODULE, '申报资料旧版本并发修改返回A0409', staleDeclarationResponse,
|
||||
staleDeclarationResponse.http === 200 && String(staleDeclarationResponse.body?.code) === 'A0409',
|
||||
`code=${staleDeclarationResponse.body?.code ?? 'missing'}`, CONTRACT_OPTIONS)
|
||||
|
||||
let declarationConverged = false
|
||||
let convergedInboundResponse = inboundResponse
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
convergedInboundResponse = await context.request('GET', inboundPath)
|
||||
const currentInbound = success(convergedInboundResponse) ? convergedInboundResponse.body?.data : null
|
||||
if (currentInbound?.publicRemark === declarationRemark) { declarationConverged = true; break }
|
||||
if (attempt < 29) await sleep(1_000)
|
||||
}
|
||||
context.assert(MODULE, '申报修改异步同步到Firstleg入库事实', convergedInboundResponse,
|
||||
declarationConverged, `expectedRemark=${declarationRemark}`, CONTRACT_OPTIONS)
|
||||
|
||||
context.createdResources.orders.inboundId = inboundId
|
||||
context.createdResources.orders.inboundCode = inbound.summary.inboundCode
|
||||
context.createdResources.orders.customerRemark = payload.customerRemark
|
||||
context.createdResources.orders.customerRemark = declarationRemark
|
||||
context.createdResources.orders.internalRemark = `${context.marker} Admin 订单链路验收`
|
||||
context.createdResources.orders.cargoRemark = payload.cargoes?.[0]?.cargoRemark || null
|
||||
context.createdResources.orders.processStatus = inbound.summary.processStatus
|
||||
|
||||
@@ -42,6 +42,7 @@ if (!Number.isFinite(timeoutMs) || timeoutMs < 1_000) {
|
||||
const now = new Date()
|
||||
const marker = `${compactTimestamp(now)}-${randomUUID().slice(0, 8)}`
|
||||
const username = `client_${compactTimestamp(now)}_${randomUUID().slice(0, 6)}`.slice(0, 64)
|
||||
const temporaryUsername = `renamed_${compactTimestamp(now)}_${randomUUID().slice(0, 6)}`.slice(0, 64)
|
||||
const clientPassword = `Client${randomUUID().replaceAll('-', '').slice(0, 14)}9!`
|
||||
const phoneSuffix = `${Date.now()}`.slice(-8)
|
||||
const legalPhone = `139${phoneSuffix}`
|
||||
@@ -91,6 +92,7 @@ try {
|
||||
await qualificationAndReviewModule()
|
||||
await merchantAfterApprovalModule()
|
||||
await profileManagementModule()
|
||||
await loginIdentifierModule()
|
||||
await rbacAndMemberModule()
|
||||
await orderLifecycleModule()
|
||||
await isolationModule()
|
||||
@@ -117,7 +119,7 @@ async function registrationModule() {
|
||||
displayName: `测试管理员-${marker}`.slice(0, 64),
|
||||
companyName,
|
||||
legalPerson: '测试法人',
|
||||
legalPhone,
|
||||
phone: legalPhone,
|
||||
contactEmail: `${username}@example.com`,
|
||||
countryCode: 'CN',
|
||||
address: '广东省深圳市南山区测试路 1 号',
|
||||
@@ -213,7 +215,6 @@ async function qualificationAndReviewModule() {
|
||||
const png = testPng()
|
||||
const form = new FormData()
|
||||
form.append('businessLicense', new Blob([png], { type: 'image/png' }), `business-license-${marker}.png`)
|
||||
form.append('legalPersonId', new Blob([png], { type: 'image/png' }), `legal-person-id-${marker}.png`)
|
||||
|
||||
await apiStep('qualification-review', 'upload required qualification package', {
|
||||
method: 'POST',
|
||||
@@ -223,7 +224,7 @@ async function qualificationAndReviewModule() {
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data?.businessLicenseQualificationId, 'business license qualification id')
|
||||
assertPresent(json.data?.legalPersonIdQualificationId, 'legal person qualification id')
|
||||
assertTrue(json.data?.legalPersonIdQualificationId == null, 'legal person qualification id must stay optional')
|
||||
assertEqual(json.data?.tenantStatus, 'PROFILE_REQUIRED', 'tenant status after upload')
|
||||
}, ({ json }) => ({
|
||||
businessLicenseQualificationId: json.data.businessLicenseQualificationId,
|
||||
@@ -274,7 +275,9 @@ async function qualificationAndReviewModule() {
|
||||
method: 'GET', path: '/api/merchant/v1/iam/tenant/qualifications', token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertQualificationStatuses(json.data, ['BUSINESS_LICENSE', 'LEGAL_PERSON_ID'], 'PENDING')
|
||||
assertQualificationStatuses(json.data, ['BUSINESS_LICENSE'], 'PENDING')
|
||||
assertTrue(!arrayData(json.data).some((item) => item.qualificationType === 'LEGAL_PERSON_ID'),
|
||||
'optional legal person ID must remain absent')
|
||||
assertQualificationStatuses(json.data, ['FREIGHT_FORWARDER_CERTIFICATE', 'BANK_ACCOUNT_LICENSE'], 'PENDING')
|
||||
}, ({ json }) => ({ qualifications: qualificationEvidence(json.data) }))
|
||||
|
||||
@@ -293,6 +296,19 @@ async function qualificationAndReviewModule() {
|
||||
}, () => ({ username: platformUsername }))
|
||||
platformToken = platformLogin.json.data.accessToken
|
||||
|
||||
const dimensionPath = `/api/platform/v1/iam/tenants/${report.resources.tenantId}/dimension-initialization`
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
const statusResponse = await httpRequest({ method: 'GET', path: dimensionPath, token: platformToken })
|
||||
if (String(statusResponse.json?.data?.status) === 'READY') break
|
||||
if (attempt < 29) await new Promise((resolve) => setTimeout(resolve, 1_000))
|
||||
}
|
||||
await apiStep('qualification-review', 'customer default dimensions are ready before approval', {
|
||||
method: 'GET', path: dimensionPath, token: platformToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.status, 'READY', 'dimension initialization status')
|
||||
}, ({ json }) => ({ status: json.data.status, attemptCount: json.data.attemptCount }))
|
||||
|
||||
const reviews = await apiStep('qualification-review', 'platform finds pending review', {
|
||||
method: 'GET', path: '/api/platform/v1/iam/reviews', token: platformToken,
|
||||
}, ({ json }) => {
|
||||
@@ -310,7 +326,9 @@ async function qualificationAndReviewModule() {
|
||||
method: 'GET', path: `/api/platform/v1/iam/tenants/${report.resources.tenantId}/qualifications`, token: platformToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertQualificationStatuses(json.data, ['BUSINESS_LICENSE', 'LEGAL_PERSON_ID'], 'PENDING')
|
||||
assertQualificationStatuses(json.data, ['BUSINESS_LICENSE'], 'PENDING')
|
||||
assertTrue(!arrayData(json.data).some((item) => item.qualificationType === 'LEGAL_PERSON_ID'),
|
||||
'platform must not require a legal person ID record')
|
||||
}, ({ json }) => ({ qualifications: qualificationEvidence(json.data) }))
|
||||
|
||||
const approval = await apiStep('qualification-review', 'platform approves enterprise', {
|
||||
@@ -343,6 +361,51 @@ async function qualificationAndReviewModule() {
|
||||
})
|
||||
}
|
||||
|
||||
async function loginIdentifierModule() {
|
||||
const staleToken = merchantToken
|
||||
await apiStep('login-identifiers', 'change username with current-password verification', {
|
||||
method: 'PUT', path: '/api/merchant/v1/iam/profile/login-identifiers', token: merchantToken,
|
||||
body: { username: temporaryUsername, phone: legalPhone, currentPassword: clientPassword },
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.username, temporaryUsername, 'updated username')
|
||||
assertEqual(json.data?.phone, legalPhone, 'updated phone')
|
||||
}, ({ json }) => ({ username: json.data.username, phone: json.data.phone }))
|
||||
|
||||
await apiStep('login-identifiers', 'identifier change revokes the previous access token', {
|
||||
method: 'GET', path: '/api/merchant/v1/iam/profile', token: staleToken,
|
||||
}, ({ status, json }) => {
|
||||
assertEqual(status, 401, 'HTTP status')
|
||||
assertTrue(String(json?.code || '').length > 0, 'authentication error code must be present')
|
||||
}, ({ status, json }) => ({ status, rejectionCode: json?.code }))
|
||||
|
||||
const renamedLogin = await apiStep('login-identifiers', 'login with the changed username', {
|
||||
method: 'POST', path: '/api/auth/login', body: { username: temporaryUsername, password: clientPassword },
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data?.accessToken, 'accessToken')
|
||||
}, () => ({ username: temporaryUsername }))
|
||||
merchantToken = renamedLogin.json.data.accessToken
|
||||
merchantRefreshToken = renamedLogin.json.data.refreshToken
|
||||
|
||||
await apiStep('login-identifiers', 'restore the original username for the remaining acceptance flow', {
|
||||
method: 'PUT', path: '/api/merchant/v1/iam/profile/login-identifiers', token: merchantToken,
|
||||
body: { username, phone: legalPhone, currentPassword: clientPassword },
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.username, username, 'restored username')
|
||||
}, ({ json }) => ({ username: json.data.username }))
|
||||
|
||||
const restoredLogin = await apiStep('login-identifiers', 'login after restoring the original username', {
|
||||
method: 'POST', path: '/api/auth/login', body: { username, password: clientPassword },
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data?.accessToken, 'accessToken')
|
||||
}, () => ({ username }))
|
||||
merchantToken = restoredLogin.json.data.accessToken
|
||||
merchantRefreshToken = restoredLogin.json.data.refreshToken
|
||||
}
|
||||
|
||||
async function merchantAfterApprovalModule() {
|
||||
const login = await apiStep('merchant-session', 'client re-login after approval', {
|
||||
method: 'POST', path: '/api/auth/login', body: { username, password: clientPassword },
|
||||
@@ -365,7 +428,9 @@ async function merchantAfterApprovalModule() {
|
||||
method: 'GET', path: '/api/merchant/v1/iam/tenant/qualifications', token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertQualificationStatuses(json.data, ['BUSINESS_LICENSE', 'LEGAL_PERSON_ID'], 'APPROVED')
|
||||
assertQualificationStatuses(json.data, ['BUSINESS_LICENSE'], 'APPROVED')
|
||||
assertTrue(!arrayData(json.data).some((item) => item.qualificationType === 'LEGAL_PERSON_ID'),
|
||||
'approved enterprise must not gain a synthetic legal person ID record')
|
||||
assertQualificationStatuses(json.data, ['FREIGHT_FORWARDER_CERTIFICATE', 'BANK_ACCOUNT_LICENSE'], 'APPROVED')
|
||||
}, ({ json }) => ({ qualifications: qualificationEvidence(json.data) }))
|
||||
|
||||
@@ -1004,9 +1069,9 @@ async function orderLifecycleModule() {
|
||||
}, ({ bytes, headers }) => ({ sizeBytes: bytes.length, contentType: headers.get('content-type') }))
|
||||
|
||||
const payload = buildOrderPayload(masterData, productImageFileId, 'ACTIVE', 'fresh draft after approval')
|
||||
const invalidOrderCases = [
|
||||
const incompleteSubmitCases = [
|
||||
{
|
||||
name: 'FBA order missing Amazon identifiers is rejected',
|
||||
name: 'FBA order missing Amazon identifiers',
|
||||
mutate(value) {
|
||||
value.amazonWarehouseCode = null
|
||||
value.shipmentId = null
|
||||
@@ -1014,7 +1079,7 @@ async function orderLifecycleModule() {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'FBA cargo missing SKU UPC shipment and reference is rejected',
|
||||
name: 'FBA cargo missing SKU UPC shipment and reference',
|
||||
mutate(value) {
|
||||
value.cargoes[0].skuNo = null
|
||||
value.cargoes[0].upcNo = null
|
||||
@@ -1022,9 +1087,35 @@ async function orderLifecycleModule() {
|
||||
value.cargoes[0].referenceId = null
|
||||
},
|
||||
},
|
||||
]
|
||||
for (const testCase of incompleteSubmitCases) {
|
||||
const incompletePayload = structuredClone(payload)
|
||||
testCase.mutate(incompletePayload)
|
||||
const draft = await apiStep('order-validation', `${testCase.name} can be saved as a draft`, {
|
||||
method: 'POST', path: '/api/merchant/v1/orders/first-leg', token: merchantToken,
|
||||
headers: { 'Idempotency-Key': newIdempotencyKey('incomplete-draft') }, body: incompletePayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data, 'incomplete draft order id')
|
||||
}, ({ json }) => ({ orderId: json.data, status: 'DRAFT' }))
|
||||
report.resources.orderIds.push(draft.json.data)
|
||||
await softApiStep('order-validation', `${testCase.name} is rejected on submit`, {
|
||||
method: 'POST', path: `/api/merchant/v1/orders/first-leg/${draft.json.data}/submit`, token: merchantToken,
|
||||
headers: { 'Idempotency-Key': newIdempotencyKey('incomplete-submit') },
|
||||
}, ({ json, status }) => {
|
||||
assertEqual(status, 200, 'HTTP status')
|
||||
assertEqual(String(json?.code), 'A0422', 'business code')
|
||||
}, ({ json }) => ({ orderId: draft.json.data, rejectionCode: json?.code, message: json?.message }))
|
||||
}
|
||||
const invalidOrderCases = [
|
||||
{
|
||||
name: 'PICKUP order without sender is rejected',
|
||||
mutate(value) { value.sender = null },
|
||||
name: 'PICKUP order without a selected pickup address is rejected',
|
||||
mutate(value) {
|
||||
value.pickupMethod = 'PICKUP'
|
||||
value.requestedPickupAt = new Date(Date.now() + 7 * 86_400_000).toISOString().slice(0, 19)
|
||||
value.sender = null
|
||||
value.senderAddressId = null
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'PRIVATE destination without receiver is rejected',
|
||||
@@ -1191,11 +1282,11 @@ async function orderLifecycleModule() {
|
||||
requireSuccess(json)
|
||||
assertPresent(pageRecords(json.data).find((item) => String(item.orderId) === String(orderId)), 'combined-filtered order')
|
||||
}, ({ json }) => ({ total: json.data?.total, orderId }))
|
||||
await apiStep('order-filters', 'sender phone filter contains submitted order', {
|
||||
method: 'GET', path: `/api/merchant/v1/orders/first-leg?page=1&size=50&customerContact=${encodeURIComponent(legalPhone)}`, token: merchantToken,
|
||||
await apiStep('order-filters', 'customer contact filter contains submitted order by customer name', {
|
||||
method: 'GET', path: `/api/merchant/v1/orders/first-leg?page=1&size=50&customerContact=${encodeURIComponent(payload.customerName)}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(pageRecords(json.data).find((item) => String(item.orderId) === String(orderId)), 'sender-phone-filtered order')
|
||||
assertPresent(pageRecords(json.data).find((item) => String(item.orderId) === String(orderId)), 'customer-contact-filtered order')
|
||||
}, ({ json }) => ({ total: json.data?.total, orderId }))
|
||||
await apiStep('order-filters', 'unmatched keyword returns an empty order page', {
|
||||
method: 'GET', path: `/api/merchant/v1/orders/first-leg?page=1&size=50&keyword=${randomUUID()}`, token: merchantToken,
|
||||
@@ -1815,7 +1906,7 @@ function buildOrderPayload(masterData, productImageFileId, suffix, remark) {
|
||||
destinationWarehouseName: masterData.destination.warehouseName,
|
||||
expectedShipDate: addDays(new Date(), 30),
|
||||
customerRemark: `${remark} ${marker}`,
|
||||
pickupMethod: 'PICKUP',
|
||||
pickupMethod: 'SELF_EXPRESS',
|
||||
receivingWarehouseId: masterData.receiving.id,
|
||||
receivingWarehouseName: masterData.receiving.warehouseName,
|
||||
channelCode: masterData.channel.channelCode,
|
||||
@@ -1867,6 +1958,7 @@ function buildOrderPayload(masterData, productImageFileId, suffix, remark) {
|
||||
upcNo: `123456${`${Date.now()}`.slice(-6)}`.slice(0, 12),
|
||||
productImageFileId,
|
||||
dangerousGoods: false,
|
||||
woodenProduct: false,
|
||||
cargoRemark: `cargo-${remark} ${marker}`,
|
||||
}],
|
||||
}
|
||||
@@ -1911,10 +2003,13 @@ async function softApiStep(module, name, request, validate, evidenceBuilder = de
|
||||
async function httpRequest({ method = 'GET', path, token, headers = {}, body, responseType = 'json' }) {
|
||||
const requestHeaders = { Accept: 'application/json', 'X-Request-Id': `client-e2e-${randomUUID()}`, ...headers }
|
||||
if (token) requestHeaders.Authorization = `Bearer ${token}`
|
||||
let requestBody = body
|
||||
if (body != null && !(body instanceof FormData)) {
|
||||
const normalizedBody = path === '/api/auth/login' && body?.username && !body?.loginId
|
||||
? { ...body, loginId: body.username, username: undefined }
|
||||
: body
|
||||
let requestBody = normalizedBody
|
||||
if (normalizedBody != null && !(normalizedBody instanceof FormData)) {
|
||||
requestHeaders['Content-Type'] = 'application/json'
|
||||
requestBody = JSON.stringify(body)
|
||||
requestBody = JSON.stringify(normalizedBody)
|
||||
}
|
||||
const response = await fetch(`${gateway}${path}`, {
|
||||
method,
|
||||
|
||||
Reference in New Issue
Block a user