优化
This commit is contained in:
@@ -1,16 +1,17 @@
|
||||
import { openFixedActor, requireFixedActors, UI_ACTOR_SLOTS } from './admin-test-env-modules/fixed-test-actors.mjs'
|
||||
import { mkdirSync, openSync, closeSync, writeFileSync, writeSync, ftruncateSync, fsyncSync, unlinkSync,
|
||||
readFileSync, realpathSync } from 'node:fs'
|
||||
import { resolve, dirname, join, isAbsolute, extname } from 'node:path'
|
||||
import { homedir } from 'node:os'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import { createHash, randomUUID } from 'node:crypto'
|
||||
import { AcceptanceRun, parseArguments, validateExecutionOptions, buildAcceptanceSummary, createFleetPoolGrayOptions } from './admin-test-env-acceptance.mjs'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { AcceptanceRun, parseArguments, validateExecutionOptions, buildAcceptanceSummary, createFleetPoolGrayOptions, grayFixedCustomerOptions } from './admin-test-env-acceptance.mjs'
|
||||
import { parseCrossCustomerArguments } from './fleet-cross-customer-test-env.mjs'
|
||||
import { createFleetCrossCustomerFixture } from './admin-test-env-modules/fleet-cross-customer-fixture.mjs'
|
||||
import { testPlannedCapacity } from './admin-test-env-modules/fleet-booking-reservation-acceptance.mjs'
|
||||
import { createTestEvidenceReaders } from './fba-core-regression-evidence.mjs'
|
||||
import { createGrayEvidenceReaders } from './gray-regression-evidence.mjs'
|
||||
import { guardFleetWrites, redactFleetWrite, findFleetActorPhone } from './fleet-test-env-acceptance.mjs'
|
||||
import { guardFleetWrites, redactFleetWrite } from './fleet-test-env-acceptance.mjs'
|
||||
import { createPicker, disablePicker, resolvePickerAccess } from './admin-test-env-modules/phase4-overseas-pda.mjs'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
@@ -139,19 +140,20 @@ async function runUiFixtureCore(config, { env = process.env, outputRoot = ROOT,
|
||||
const endpoint = new URL(env.CHONGCHONG_ADMIN_API_BASE_URL)
|
||||
demand((gray ? endpoint.protocol === 'https:' : ['http:', 'https:'].includes(endpoint.protocol)) && ['/api', '/api/'].includes(endpoint.pathname)
|
||||
&& !endpoint.username && !endpoint.password && !endpoint.hash && !endpoint.search, 'INVALID_UI_API_BASE')
|
||||
const fixedCustomers = gray ? grayFixedCustomerOptions(env) : {}
|
||||
if (gray) readers = createGrayEvidenceReaders(env)
|
||||
else readers ||= createTestEvidenceReaders(env)
|
||||
const reports = join(outputRoot, 'reports'), output = resolve(outputRoot, uiFixturePlan(config).output)
|
||||
const transport = { gateway: endpoint.origin, username: env.CHONGCHONG_ADMIN_USERNAME, password: env.CHONGCHONG_ADMIN_PASSWORD,
|
||||
output, markerNamespace: createHash('sha256').update(`${config.environment}:${config.runId}`).digest('hex').slice(0, 12) }
|
||||
const options = gray ? createFleetPoolGrayOptions(transport) : validateExecutionOptions({
|
||||
const options = gray ? createFleetPoolGrayOptions({ ...transport, ...fixedCustomers }) : validateExecutionOptions({
|
||||
...parseArguments(['--modules', 'merchant-qualification-fixture', '--quiet', 'true']), ...transport, environment: 'test' })
|
||||
const secretDocument = { schemaVersion: 1, environment: config.environment, runtimeProfile: env.CHONGCHONG_ENV, runId: config.runId, status: 'PREPARING', createdAt: new Date().toISOString(),
|
||||
gatewayOrigin: endpoint.origin, accounts: [], business: {} }
|
||||
const vault = createPrivateCredentialStore(privateFile, secretDocument)
|
||||
const schedule = gray ? grayUiSchedule() : null, draftCount = gray ? 4 : 24, perSales = draftCount / 2
|
||||
const secrets = Object.entries(env).filter(([key]) => /PASSWORD|TOKEN|SECRET/.test(key)).map(([, value]) => value).filter(Boolean)
|
||||
const sessions = new Map(), kinds = new Map()
|
||||
const sessions = new Map()
|
||||
let context, picker, lock, restore, initialized = false, requestNumber = 0, inventoryNumber = 0, report
|
||||
const save = (name, value) => writeFileSync(join(output, name), JSON.stringify(publicUiEvidence(value, secrets), null, 2) + '\n')
|
||||
const writePrivate = () => vault.save(secretDocument)
|
||||
@@ -169,6 +171,8 @@ async function runUiFixtureCore(config, { env = process.env, outputRoot = ROOT,
|
||||
save('manifest.json', { ...uiFixturePlan(config), sourceSha256: createHash('sha256').update(readFileSync(fileURLToPath(import.meta.url))).digest('hex') })
|
||||
await readers.preflight()
|
||||
context = contextFactory(options)
|
||||
requireFixedActors(context.options, UI_ACTOR_SLOTS)
|
||||
secrets.push(...Object.values(context.options.fixedActors).map(actor => actor.password), ...(context.options.fixedPdaActors || []).map(actor => actor.password))
|
||||
secretDocument.marker = context.marker; writePrivate()
|
||||
const proof = context.createdResources.fleetCapacityPoolUi = { marker: context.marker, status: 'PREPARING', actors: [], drafts: [], fullAcceptance: false }
|
||||
for (const method of ['request', 'requestWithBody']) {
|
||||
@@ -177,25 +181,16 @@ async function runUiFixtureCore(config, { env = process.env, outputRoot = ROOT,
|
||||
const sequence = ++requestNumber, [verb, path] = args
|
||||
if (gray) args[2] = applyGrayUiTransportWindow(verb, path, args[2], schedule)
|
||||
const body = args[2]
|
||||
if (verb === 'POST' && path === `${IAM}/users` && body?.username && body.password) {
|
||||
remember({ kind: kinds.get(body.username) || (body.username.startsWith('pda_overseas_') ? 'FIXTURE_PDA' : 'FIXTURE_USER'),
|
||||
username: body.username, password: body.password, roleIds: body.roleIds, warehouseId: body.overseasWarehouseId || null, status: 'PREPARED' })
|
||||
}
|
||||
save(`requests/${sequence}.json`, { stage: 'INTENT', sequence, method: verb, path, body: body instanceof FormData ? '[MULTIPART]' : body })
|
||||
const response = await original.apply(this, args)
|
||||
if (ok(response) && verb === 'POST' && path === `${IAM}/tenants`) {
|
||||
const data = response.body.data, customer = data.tenant || data.customer || data
|
||||
remember({ kind: 'CUSTOMER', customerId: id(customer.id || data.tenantId), customerName: customer.tenantName || customer.customerName,
|
||||
username: data.administratorUsername, password: data.temporaryPassword, status: 'CREATED' })
|
||||
}
|
||||
if (ok(response) && verb === 'POST' && path === `${IAM}/users`) {
|
||||
const data = response.body.data
|
||||
remember({ kind: kinds.get(body.username) || (body.username.startsWith('pda_overseas_') ? 'FIXTURE_PDA' : 'FIXTURE_USER'),
|
||||
username: body.username, password: body.password, membershipId: id(data.membershipId), roleIds: data.roleIds, status: 'ACTIVE' })
|
||||
}
|
||||
if (ok(response) && path === '/api/auth/login' && response.body.data?.accessToken) {
|
||||
const auth = response.body.data
|
||||
sessions.set(auth.accessToken, auth.refreshToken); secrets.push(auth.accessToken, auth.refreshToken)
|
||||
// 固定客户不经过建客回执;只将显式配置且成功登录的既有凭据交接到私有文件。
|
||||
const customer = [{ customerId: context.options.fixedCustomerId, username: context.options.merchantUsername, password: context.options.merchantPassword }, context.options.fixedSecondaryCustomer]
|
||||
.find(row => row?.customerId && row.username === body?.loginId && row.password === body?.password)
|
||||
if (customer) remember({ kind: 'CUSTOMER', customerId: String(customer.customerId).replace(/^TEN/, ''), username: customer.username,
|
||||
password: customer.password, status: 'REUSED' })
|
||||
}
|
||||
save(`requests/${sequence}.json`, { stage: 'RESPONSE', sequence, method: verb, path, body: body instanceof FormData ? '[MULTIPART]' : body, response })
|
||||
save('progress.json', { marker: context.marker, createdResources: context.createdResources, steps: context.steps })
|
||||
@@ -221,6 +216,10 @@ async function runUiFixtureCore(config, { env = process.env, outputRoot = ROOT,
|
||||
check('Fixture PDA role exists', Boolean(access))
|
||||
picker = await createPicker(context, 93, config.warehouseId, access)
|
||||
check('Fixture PDA authenticated', Boolean(picker?.token))
|
||||
const configuredPda = context.options.fixedPdaActors?.find(actor => String(actor.membershipId) === String(picker.membershipId))
|
||||
check('PDA handoff uses the configured existing account', picker.reused && configuredPda?.username === picker.username && configuredPda.password)
|
||||
remember({ kind: 'FIXTURE_PDA', membershipId: picker.membershipId, username: picker.username, password: configuredPda.password,
|
||||
warehouseId: config.warehouseId, status: 'REUSED' })
|
||||
const readInventory = async scope => {
|
||||
const snapshot = await readers.readFleetInventory(scope); save(`evidence/inventory-${++inventoryNumber}.json`, snapshot); return snapshot
|
||||
}
|
||||
@@ -257,25 +256,16 @@ async function runUiFixtureCore(config, { env = process.env, outputRoot = ROOT,
|
||||
await request('POST', `${tripPath}/capacity-plan`, { recordVersion: plan.recordVersion, manifestHash: plan.manifestHash,
|
||||
pallets: plannedPallets, reason: '仅UI验收:自有计划18/总30,不作为实际发车计量' })
|
||||
proof.planEvidence = { actual: false, pallets: plannedPallets, reason: 'TEST UI销售计划18/30,不是实载测量' }
|
||||
const permissions = await request('GET', `${IAM}/permissions`), actorTokens = []
|
||||
requireFixedActors(context.options, UI_ACTOR_SLOTS)
|
||||
const actorTokens = []
|
||||
const key = suffix => createHash('sha256').update(`${context.marker}:UI:${suffix}`).digest('hex')
|
||||
for (const kind of ['SALES_A', 'SALES_B', 'MANAGER']) {
|
||||
const suffix = key(kind).slice(0, 16), username = `pool_ui_${kind.toLowerCase()}_${suffix}`
|
||||
const expected = uiActorPermissions(kind, gray)
|
||||
const candidates = expected.map(code => permissions.filter(row => row.permissionCode === code && row.status === 'ACTIVE'))
|
||||
check('Exact fixture permissions are installed', candidates.every(rows => rows.length === 1 && rows[0].id))
|
||||
const role = await request('POST', `${IAM}/roles`, { roleCode: `POOL_UI_${kind}_${suffix}`.toUpperCase(), roleName: `${context.marker}-${kind}`, description: '仅本轮UI验收账号' })
|
||||
proof.actors.push({ kind, roleId: id(role.id) })
|
||||
await request('PUT', `${IAM}/roles/permissions`, { roleId: id(role.id), permissionIds: candidates.map(rows => id(rows[0].id)) })
|
||||
kinds.set(username, kind)
|
||||
const password = `Ui${randomUUID().replaceAll('-', '')}A9`; remember({ kind, username, password, roleIds: [id(role.id)], status: 'PREPARED' })
|
||||
const user = await request('POST', `${IAM}/users`, { username, password, displayName: `${context.marker}-同名销售`,
|
||||
phone: await findFleetActorPhone(context, suffix), email: `${username}@example.com`, tenantId: '0', roleIds: [id(role.id)], status: 'ACTIVE' })
|
||||
proof.actors.at(-1).membershipId = id(user.membershipId)
|
||||
const auth = await request('POST', '/api/auth/login', { loginId: username, password }, '')
|
||||
const effective = await request('GET', `${IAM}/authorization/permissions`, undefined, auth.accessToken)
|
||||
check('UI actor has only its exact minimal permissions', JSON.stringify([...effective].sort()) === JSON.stringify([...expected].sort()))
|
||||
actorTokens.push(auth.accessToken)
|
||||
const configured = context.options.fixedActors[`pool-ui.${kind}`]
|
||||
secrets.push(configured.password)
|
||||
const actor = await openFixedActor(context, `pool-ui.${kind}`, uiActorPermissions(kind, gray))
|
||||
proof.actors.push({ kind, roleId: actor.roleId, membershipId: actor.membershipId, username: actor.username, reused: true })
|
||||
remember({ kind, username: actor.username, password: configured.password, membershipId: actor.membershipId, roleIds: [actor.roleId], status: 'REUSED' })
|
||||
actorTokens.push(actor.token)
|
||||
}
|
||||
detail = await request('GET', tripPath)
|
||||
const draft = await request('POST', POOL, { tripNo: fixture.tripNo, tripRecordVersion: detail.trip.recordVersion,
|
||||
@@ -316,7 +306,8 @@ async function runUiFixtureCore(config, { env = process.env, outputRoot = ROOT,
|
||||
check('Customer login belongs to this fixture customer', id(profile.id) === member.customerId)
|
||||
for (const type of ['local-looseboard-orders', 'us-truck-orders']) {
|
||||
const page = await request('GET', `/api/merchant/v1/last-leg/${type}?page=1&size=20`, undefined, auth.accessToken)
|
||||
check('Fresh customer commercial order page provides real empty state before UI confirmation', Number(page.total) === 0 && page.records.length === 0)
|
||||
check('Existing customer commercial order page is valid without requiring an empty account', Array.isArray(page.records)
|
||||
&& Number.isSafeInteger(Number(page.total)) && Number(page.total) >= page.records.length)
|
||||
}
|
||||
account.status = 'ACTIVE'; writePrivate()
|
||||
}
|
||||
@@ -344,8 +335,8 @@ async function runUiFixtureCore(config, { env = process.env, outputRoot = ROOT,
|
||||
try {
|
||||
if (context) {
|
||||
const pdaAccount = secretDocument.accounts.find(row => row.kind === 'FIXTURE_PDA' && row.membershipId)
|
||||
if (gray && pdaAccount) {
|
||||
pdaAccount.status = 'RETAINED_FOR_UI'; pdaAccount.purpose = 'GRAY_WAREHOUSE_BOUND_PDA_H5_ACCEPTANCE'
|
||||
if (picker?.reused && pdaAccount) {
|
||||
pdaAccount.status = 'RETAINED_FOR_UI'; pdaAccount.purpose = `${config.environment}_WAREHOUSE_BOUND_PDA_H5_ACCEPTANCE`
|
||||
pdaAccount.warehouseId = config.warehouseId
|
||||
report.retainedPda = { membershipId: pdaAccount.membershipId, warehouseId: config.warehouseId, purpose: pdaAccount.purpose }
|
||||
} else if (!gray && (picker?.membershipId || pdaAccount?.membershipId)) {
|
||||
@@ -353,7 +344,7 @@ async function runUiFixtureCore(config, { env = process.env, outputRoot = ROOT,
|
||||
const previousSteps = context.steps.length
|
||||
await disablePicker(context, picker?.membershipId ? picker : pdaAccount, 93)
|
||||
demand(context.steps.length > previousSteps && context.steps.at(-1)?.passed === true, 'PDA_DISABLE_NOT_CONFIRMED')
|
||||
if (pdaAccount) { pdaAccount.status = 'DISABLED_AFTER_FIXTURE'; writePrivate() }
|
||||
if (pdaAccount) { pdaAccount.status = picker?.reused ? 'REUSED' : 'DISABLED_AFTER_FIXTURE'; writePrivate() }
|
||||
} catch {
|
||||
if (pdaAccount) pdaAccount.status = 'DISABLE_REQUIRES_REVIEW'
|
||||
report.success = false; report.cleanupError = 'PDA_DISABLE_REQUIRES_REVIEW'
|
||||
|
||||
Reference in New Issue
Block a user