This commit is contained in:
2026-09-03 18:13:00 +08:00
parent 880f923462
commit ca288dec72
8 changed files with 50 additions and 25 deletions
+3 -6
View File
@@ -530,14 +530,11 @@ 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
const maxAttempts = ['GET', 'HEAD'].includes(String(method).toUpperCase()) ? 3 : 1
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
const response = await fetch(`${this.options.gateway}${path}`, {
method, headers, body: requestBody === undefined ? undefined : JSON.stringify(requestBody),
method, headers, body: body === undefined ? undefined : JSON.stringify(body),
signal: AbortSignal.timeout(30_000),
})
const content = await response.text()
@@ -913,8 +910,8 @@ export class AcceptanceRun {
async findAvailablePhone(module = 'customers') {
this.phoneSequence = Number(this.phoneSequence || 0) + 1
const markerDigits = this.marker.replace(/\D/g, '').slice(-8).padStart(8, '0')
const phone = `18${markerDigits}${String(this.phoneSequence % 10).padStart(1, '0')}`
const markerDigits = this.marker.replace(/\D/g, '').slice(-7).padStart(7, '0')
const phone = `18${markerDigits}${String(this.phoneSequence % 100).padStart(2, '0')}`
this.addStep(module, '生成本轮隔离测试联系电话', null, /^1\d{10}$/.test(phone),
`sequence=${this.phoneSequence}`)
return phone
@@ -244,7 +244,7 @@ async function createSecondCustomer(context) {
const fixture = context.context?.merchantQualificationFixture
if (!fixture?.tenantId || !fixture?.username || !fixture?.password || fixture.marker !== context.marker) return null
const loginResponse = await context.request('POST', '/api/auth/login',
{ username: fixture.username, password: fixture.password }, '')
{ loginId: fixture.username, password: fixture.password }, '')
const login = context.apiSuccess(MODULE, '第二隔离客户登录', loginResponse, diagnostic(loginResponse), OPTIONS)
return { customerId: id(fixture.tenantId), token: login?.accessToken }
} finally {
@@ -8,6 +8,8 @@ const CONTAINER_STATUSES = ['WAIT_LOADING', 'EDITING', 'LOADING', 'COMPLETED']
const PRELOAD_STATUSES = ['EDITING', 'LOADING']
const PDA_STATUSES = ['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW', 'COMPLETED']
import { resolveOceanBookingAgent } from './ocean-booking-fixture.mjs'
function id(value) { return value == null ? '' : String(value) }
function records(value) { return Array.isArray(value?.records) ? value.records : [] }
function suffix(context, label) {
@@ -128,14 +130,11 @@ async function createConfirmedContainer(context, source, label) {
const value = suffix(context, label)
const containerNo = iso6346ContainerNumber(`${context.marker}-manual-${label}`)
const summary = source.detail.summary
const agentResponse = await context.request('GET',
'/api/platform/v1/suppliers?tenantId=0&category=BOOKING&status=ACTIVE&page=1&size=20')
const agentPage = context.apiSuccess(MODULE, `${label} 读取 ACTIVE 订舱代理`, agentResponse, '', OPTIONS)
const agent = agentPage?.records?.find?.((item) => item?.id && item?.name)
if (!agent) throw new Error(`${label} 未取得 ACTIVE 订舱代理`)
const { ensureMatsonCarrierPartner } = await import('./carrier-partner-fixture.mjs')
const carrier = await ensureMatsonCarrierPartner(context, MODULE, OPTIONS)
if (!carrier?.id) throw new Error(`${label} 未取得 ACTIVE 船公司`)
const agent = await resolveOceanBookingAgent(context, MODULE, carrier.carrierCode, OPTIONS)
if (!agent) throw new Error(`${label} 未取得当前船公司关联的 ACTIVE 订舱代理`)
const draft = {
customerId: source.tenantId, customerName: summary.customerName || 'IT 测试客户',
channelCode: summary.channelCode, warehouseId: source.warehouseId,
@@ -85,3 +85,9 @@ test('all production booking fixtures use DRAFT release and the shared requireme
assert.match(source, /containerRequirements/, name)
}
})
test('manual PDA state fixture resolves an agent bound to the selected carrier', () => {
const source = read('manual-state-loading-pda.mjs')
assert.match(source, /resolveOceanBookingAgent\(context, MODULE, carrier\.carrierCode, OPTIONS\)/)
assert.doesNotMatch(source, /suppliers\?tenantId=0&category=BOOKING/)
})
@@ -284,7 +284,7 @@ export async function run(context) {
|| !Number.isInteger(Number(carryingSlice?.packageCount)) || Number(carryingSlice.packageCount) <= 0) return
const loginResponse = await request(context, 'POST', '/api/auth/login', {
username: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
loginId: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
}, '')
const merchantLogin = context.apiSuccess(MODULE, '客户账号登录用于真实报关文件协同', loginResponse,
`username=${MERCHANT_USERNAME}`, OPTIONS)
@@ -694,7 +694,7 @@ export async function run(context) {
`taskVersion=${unchangedAfterInvalid?.version ?? 'missing'}, items=${taskItemsSignature(unchangedAfterInvalid)}, operations=${afterInvalidOperations?.length ?? 'missing'}`, OPTIONS)
const merchantLogin = await context.request('POST', '/api/auth/login', {
username: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
loginId: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
}, '')
const merchantToken = merchantLogin.http === 200 && codeOf(merchantLogin) === '200'
? merchantLogin.body?.data?.accessToken : null
@@ -0,0 +1,11 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const source = readFileSync(new URL('./admin-test-env-acceptance.mjs', import.meta.url), 'utf8')
test('admin acceptance phone fixture keeps more than ten generated customers distinct', () => {
assert.match(source, /slice\(-7\)\.padStart\(7, '0'\)/)
assert.match(source, /phoneSequence % 100/)
assert.doesNotMatch(source, /phoneSequence % 10\)/)
})
+23 -11
View File
@@ -380,7 +380,7 @@ async function loginIdentifierModule() {
}, ({ 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 },
method: 'POST', path: '/api/auth/login', body: { loginId: temporaryUsername, password: clientPassword },
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.accessToken, 'accessToken')
@@ -397,7 +397,7 @@ async function loginIdentifierModule() {
}, ({ 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 },
method: 'POST', path: '/api/auth/login', body: { loginId: username, password: clientPassword },
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.accessToken, 'accessToken')
@@ -1770,7 +1770,7 @@ async function loadOrderMasterData(token, module) {
requireSuccess(json)
assertTrue(pageRecords(json.data).length > 0, 'at least one active DOMESTIC warehouse is required')
}, ({ json }) => ({ total: json.data?.total, warehouseCount: pageRecords(json.data).length }))
const receiving = pageRecords(receivingWarehouses.json.data)[0]
const receivingCandidates = pageRecords(receivingWarehouses.json.data)
const destinationWarehouses = await apiStep(module, 'load active FBA destination warehouses', {
method: 'GET', path: '/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA', token,
@@ -1779,6 +1779,21 @@ async function loadOrderMasterData(token, module) {
assertTrue(pageRecords(json.data).length > 0, 'at least one active FBA warehouse is required')
}, ({ json }) => ({ total: json.data?.total, warehouseCount: pageRecords(json.data).length }))
const destination = pageRecords(destinationWarehouses.json.data)[0]
let receiving = null
let pricedChannels = []
for (const candidate of receivingCandidates) {
const response = await httpRequest({
method: 'GET', path: `/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${candidate.id}`, token,
})
const candidates = String(response?.json?.code) === '200' ? arrayData(response.json.data) : []
const available = candidates.filter((item) => arrayData(item.pricingTaxModes).length > 0)
if (available.length > 0) {
receiving = candidate
pricedChannels = available
break
}
}
assertPresent(receiving, 'active DOMESTIC warehouse with pricing channels')
report.resources.receivingWarehouseId = receiving.id
report.resources.destinationWarehouseId = destination.id
@@ -1796,7 +1811,7 @@ async function loadOrderMasterData(token, module) {
requireSuccess(json)
assertTrue(arrayData(json.data).length > 0, 'at least one active pricing channel is required')
}, ({ json }) => ({ channels: arrayData(json.data).map((item) => ({ channelCode: item.channelCode, priceModel: item.priceModel, pricingTaxModes: item.pricingTaxModes })) }))
const channel = arrayData(channels.json.data).find((item) => arrayData(item.pricingTaxModes).length > 0)
const channel = pricedChannels.find((item) => arrayData(item.pricingTaxModes).length > 0)
assertPresent(channel, 'pricing channel with pricingTaxModes')
report.resources.channelCode = channel.channelCode
@@ -1812,7 +1827,7 @@ async function loadOrderMasterData(token, module) {
destination,
destinationCandidates: pageRecords(destinationWarehouses.json.data),
channel,
channelCandidates: arrayData(channels.json.data).filter((item) => arrayData(item.pricingTaxModes).length > 0),
channelCandidates: pricedChannels,
partner,
}
}
@@ -2003,13 +2018,10 @@ 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}`
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)) {
let requestBody = body
if (body != null && !(body instanceof FormData)) {
requestHeaders['Content-Type'] = 'application/json'
requestBody = JSON.stringify(normalizedBody)
requestBody = JSON.stringify(body)
}
const response = await fetch(`${gateway}${path}`, {
method,