import { createHash } from 'node:crypto' import { run as createQualifiedMerchant } from './merchant-qualification-fixture.mjs' import { prepareFleetTrip } from './fleet-trip-fulfillment.mjs' import { fixtureContract } from './fleet-cross-customer-acceptance.mjs' const MODULE = 'fleet-cross-customer-fixture' const ROOT = '/api/platform/v1/last-leg' const MERCHANT = '/api/merchant/v1/last-leg/warehouse-transfers' const OPTIONS = { scenario: MODULE, deploymentBlocker: true } const id = value => value == null ? '' : String(value) const encode = encodeURIComponent const ok = response => response?.http === 200 && id(response.body?.code) === '200' const buckets = ['availablePackages', 'holdPackages', 'allocatedPackages', 'pickedPackages', 'receivingPackages'] const balanceBuckets = [...buckets, 'inTransitPackages', 'procurementPackages'] const testImage = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aH1kAAAAASUVORK5CYII=', 'base64') // LocalDateTime API fields use the runner's local wall clock, with no misleading UTC suffix. const localTime = delay => { const date = new Date(Date.now() + delay) return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, 19) } export const preparationContract = Object.freeze({ stage: fixtureContract.stage, inputs: 'execute:true, current-run context, warehouseId (public active OVERSEAS with RECEIVING), destinationWarehouseId (public active FBA), pdaToken bound to warehouseId, readInventory(scope) returning all raw balances/positions/ledger with CUSTOMER_WAREHOUSE_COMPLETE scope', writes: 'Two fresh qualified customers; one ordinary WarehouseTransfer and one PDA-measured pallet/box per customer; two dispatches, one shared trip with a test vehicle/qualified driver, two outbounds and deliveries. No picking.', lifecycle: 'Caller creates/disables PDA identity and supplies raw readFleetInventory for verification. This helper logs out its own merchant sessions, retains all business fixtures, and refuses automatic retry. No SQL, environment files or external bookings.', }) /** Preparation is opt-in and only for separately authorized TEST execution. Import/default call is offline. */ export async function createFleetCrossCustomerFixture(context, { execute = false, warehouseId, destinationWarehouseId, pdaToken, readInventory } = {}) { if (!execute) return preparationContract if (!context?.marker || !id(warehouseId) || !id(destinationWarehouseId) || !pdaToken || typeof readInventory !== 'function' || ['request', 'requestWithBody', 'requestMultipart', 'findAvailablePhone', 'apiSuccess', 'assert'].some(name => typeof context[name] !== 'function')) throw new Error('Missing current-run context, public warehouse/destination, caller-owned PDA identity or raw inventory reader') context.createdResources ??= {} if (context.createdResources.fleetCrossCustomerFixture) throw new Error('Fixture preparation already started; retain partial resources and reconcile explicitly') const proof = context.createdResources.fleetCrossCustomerFixture = { marker: context.marker, warehouseId: id(warehouseId), destinationWarehouseId: id(destinationWarehouseId), status: 'PREPARING', members: [], requests: [], sessionCleanup: [], lifecycle: preparationContract.lifecycle, } const sessions = [] const check = (label, passed) => { context.assert(MODULE, label, null, Boolean(passed), '', OPTIONS) if (!passed) throw new Error(label) } const key = label => createHash('sha256').update(`${context.marker}:CROSS-FIXTURE:${label}`).digest('hex') const record = (method, path, body, response, label) => { // Auth payloads contain credentials; never persist them or response tokens. proof.requests.push({ method, path, body: path.startsWith('/api/auth/') ? undefined : body, label, http: response?.http, code: response?.body?.code, requestId: response?.requestId, traceId: response?.traceId }) check(label, ok(response) && response.requestId && response.traceId) return response.body.data } const request = async (method, path, body, label, token = context.token) => record(method, path, body, await context.request(method, path, body, token), label) const own = member => new URLSearchParams({ customerId: member.customerId }) const stock = async member => { const query = new URLSearchParams({ customerId: member.customerId, overseasWarehouseId: id(warehouseId), inboundCode: member.incomingNo, page: '1', size: '200' }) const grouped = await request('GET', `${ROOT}/inventory/balances?${query}`, undefined, 'Read exact fresh receipt balance page') const ledger = await request('GET', `${ROOT}/inventory/ledger?${query}&businessOrderNo=${encode(member.incomingNo)}`, undefined, 'Read exact receipt movement page') check('Fresh one-box receipt has one complete balance and movement page', grouped?.records?.length === 1 && Number(grouped.total) === 1 && ledger?.records?.length === 1 && Number(ledger.total) === 1 && id(grouped.records[0].id)) const path = `${ROOT}/inventory/balances/${encode(grouped.records[0].id)}` const balance = await request('GET', `${path}?${own(member)}`, undefined, 'Read original raw balance identity') const positions = await request('GET', `${path}/positions?${own(member)}`, undefined, 'Read original raw physical position') const sourceMatches = row => id(row.customerId) === member.customerId && id(row.overseasWarehouseId) === id(warehouseId) && row.inboundCode === member.incomingNo check('Receipt raw balance and position belong to exactly this customer and physical pallet', balance && sourceMatches(balance) && id(balance.id) === id(grouped.records[0].id) && positions?.length === 1 && id(positions[0].id) && sourceMatches(positions[0]) && id(positions[0].balanceId) === id(balance.id) && id(positions[0].palletId) === member.palletId && positions[0].palletNo === member.palletNo && balanceBuckets.every(field => String(balance[field]) === (field === 'availablePackages' ? '1' : '0')) && buckets.every(field => String(positions[0][field]) === (field === 'availablePackages' ? '1' : '0'))) const movement = ledger.records[0] check('Exactly one immutable STOCK_IN movement proves the real receipt', sourceMatches(movement) && id(movement.id) && movement.businessType === 'STOCK_IN' && movement.businessOrderNo === member.incomingNo && movement.palletNo === member.palletNo && String(movement.beforePackages) === '0' && String(movement.deltaPackages) === '1' && String(movement.afterPackages) === '1') // InventoryLedgerView intentionally exposes no balanceId/positionId. Only the complete raw reader proves those links. const scope = { marker: context.marker, customerId: member.customerId, warehouseId: id(warehouseId) } const raw = await readInventory(scope) check('Raw receipt reader proves complete current customer and warehouse scope', raw?.scope?.marker === scope.marker && raw.scope.customerId === scope.customerId && raw.scope.warehouseId === scope.warehouseId && raw.scope.coverage === 'CUSTOMER_WAREHOUSE_COMPLETE') for (const collection of ['balances', 'positions', 'ledger']) check(`Fresh customer raw ${collection} has exactly one original receipt row`, Array.isArray(raw[collection]) && raw[collection].length === 1 && id(raw[collection][0].id) && id(raw[collection][0].customerId) === member.customerId && id(raw[collection][0].overseasWarehouseId) === id(warehouseId) && (collection === 'positions' || raw[collection][0].inboundCode === member.incomingNo)) const rawBalance = raw.balances[0], rawPosition = raw.positions[0], rawMovement = raw.ledger[0] check('Raw receipt identities and quantities match the public evidence', id(rawBalance.id) === id(balance.id) && id(rawPosition.id) === id(positions[0].id) && id(rawPosition.balanceId) === id(balance.id) && id(rawPosition.palletId) === member.palletId && rawPosition.palletNo === member.palletNo && balanceBuckets.every(field => String(rawBalance[field]) === String(balance[field])) && buckets.every(field => String(rawPosition[field]) === String(positions[0][field])) && id(rawMovement.id) === id(movement.id) && id(rawMovement.balanceId) === id(balance.id) && id(rawMovement.positionId) === id(positions[0].id) && rawMovement.businessType === 'STOCK_IN' && rawMovement.businessOrderNo === member.incomingNo && rawMovement.palletNo === member.palletNo && ['beforePackages', 'deltaPackages', 'afterPackages'].every(field => String(rawMovement[field]) === String(movement[field]))) return { balance, positions, ledger: ledger.records, raw } } let fixture, failure try { // Explicit IDs are re-read through the public master-data API before any fixture writes. const origin = await request('GET', `/api/platform/v1/warehouse-admin/${encode(warehouseId)}?tenantId=0`, undefined, 'Verify receiving warehouse') const destination = await request('GET', `/api/platform/v1/warehouse-admin/${encode(destinationWarehouseId)}?tenantId=0`, undefined, 'Verify FBA destination') const validMaster = (detail, expectedId, type) => id(detail?.warehouse?.id) === id(expectedId) && id(detail.warehouse.tenantId) === '0' && detail.warehouse.status === 'ACTIVE' && detail.warehouse.warehouseType === type && detail.warehouse.warehouseCode && detail.warehouse.warehouseName check('Origin and destination are current public active warehouse masters', validMaster(origin, warehouseId, 'OVERSEAS') && origin.capabilities?.some(row => row.capabilityCode === 'RECEIVING' && row.enabled === true) && validMaster(destination, destinationWarehouseId, 'FBA')) const address = destination.addresses?.find(row => row.addressLine1) check('FBA destination has a real address', Boolean(address)) const deliveryAddress = [address.countryCode, address.stateProvince, address.city, address.district, address.addressLine1, address.addressLine2, address.postalCode].filter(Boolean).join(' ') for (const suffix of ['A', 'B']) { const member = { marker: context.marker, customerMarker: `${context.marker}-CROSS-${suffix}`, receiptVerified: false } proof.members.push(member) // Qualification helper's mutable fixture, options and resources must not touch the parent's merchant slot. const child = Object.create(context) child.marker = member.customerMarker child.options = { ...context.options, customerName: `跨客户车次-${key(suffix).slice(0, 12)}-${suffix}` } child.context = {}; child.createdResources = {} child.request = async (method, path, body, token = context.token) => { const response = await context.request(method, path, body, token) record(method, path, body, response, `Qualify fresh customer ${suffix}`) if (method === 'POST' && path === '/api/platform/v1/iam/tenants') member.customerId = id((response.body.data?.tenant || response.body.data?.customer || response.body.data)?.id || response.body.data?.tenantId) return response } await createQualifiedMerchant(child, { publishEnvironment: false }) const credentials = child.context.merchantQualificationFixture check('Qualification belongs to this independent current-run context', credentials?.marker === member.customerMarker && id(credentials.tenantId) === member.customerId && proof.members.filter(row => row.customerId === member.customerId).length === 1) member.qualification = child.createdResources.merchantQualificationFixture let auth try { auth = await request('POST', '/api/auth/login', { loginId: credentials.username, password: credentials.password }, `Login fresh customer ${suffix}`, '') } finally { delete child.context.merchantQualificationFixture } // Retain a returned session for cleanup even if the login contract is incomplete. if (auth?.accessToken) sessions.push({ customerId: member.customerId, accessToken: auth.accessToken, refreshToken: auth.refreshToken }) check('Fresh customer session is complete', auth?.accessToken && auth.refreshToken) const profile = await request('GET', '/api/merchant/v1/iam/tenant/profile', undefined, 'Verify newly authenticated customer identity', auth.accessToken) check('Merchant session belongs to the newly qualified customer', id(profile?.id) === member.customerId) const createBody = { sourceLastlegOrderIds: [], operatingWarehouseId: id(warehouseId), fbaWarehouseCode: destination.warehouse.warehouseCode, destinationType: 'FBA', destinationWarehouseId: id(destinationWarehouseId), destinationWarehouseCode: destination.warehouse.warehouseCode, destinationWarehouseName: destination.warehouse.warehouseName, deliveryAddress, packageCount: 1, volumeCbm: 1, deliveryMethod: 'CUSTOMER_DELIVERY', estimatedArrivalAt: localTime(1800000), needRelabel: false, remark: member.customerMarker, idempotencyKey: key(`CREATE-${suffix}`), items: [{ skuCode: `CROSS-${key(suffix).slice(0, 16)}`, productNameZh: '受控测试一箱货物', packageCount: 1 }] } let incoming = await request('POST', MERCHANT, createBody, `Create new incoming ${suffix}`, auth.accessToken) member.incomingNo = incoming?.transferNo check('Ordinary transfer creates fresh incoming without borrowed source inventory', member.incomingNo && incoming.inboundCode === member.incomingNo && incoming.status === 'DRAFT' && id(incoming.operatingWarehouseId) === id(warehouseId)) const merchantPath = `${MERCHANT}/${encode(member.incomingNo)}` incoming = await request('POST', `${merchantPath}/submit`, undefined, `Submit own incoming ${suffix}`, auth.accessToken) check('Own incoming submitted for review', incoming?.status === 'PENDING_REVIEW') incoming = await request('POST', `${ROOT}/warehouse-transfers/${encode(member.incomingNo)}/review?${own(member)}`, { decision: 'APPROVE', remark: member.customerMarker }, `Approve incoming ${suffix}`) check('Approved fresh incoming awaits real PDA receipt', incoming?.status === 'WAIT_PDA_RECEIVING') const pdaPath = `/api/pda/v1/last-leg/warehouse-transfers/${encode(member.incomingNo)}` const task = await request('GET', pdaPath, undefined, 'PDA warehouse binding resolves exact new incoming', pdaToken) check('PDA task is untouched and belongs to the requested receiving warehouse', task?.transferNo === member.incomingNo && task.inboundCode === member.incomingNo && id(task.operatingWarehouseId) === id(warehouseId) && task.status === 'WAIT_PDA_RECEIVING' && task.pallets?.length === 0) const measured = await request('POST', `${pdaPath}/pallets/drafts`, { idempotencyKey: key(`MEASURE-${suffix}`), cartonCount: 1, lengthCm: 100, widthCm: 100, heightCm: 100, volumeCbm: 1, grossWeightKg: 100 }, 'Measure one real test pallet', pdaToken) check('Measurement created one pallet draft', measured?.pallets?.length === 1 && measured.pallets[0].id && measured.pallets[0].status === 'DRAFT') const palletPath = `${pdaPath}/pallets/${encode(measured.pallets[0].id)}` const generated = await request('POST', `${palletPath}/generate`, { idempotencyKey: key(`GENERATE-${suffix}`) }, 'Generate authoritative warehouse pallet', pdaToken) const pallet = generated?.pallets?.[0] check('Generated physical pallet has real references', generated?.pallets?.length === 1 && pallet.status === 'GENERATED' && pallet.warehousePalletId && pallet.palletNo) member.palletId = id(pallet.warehousePalletId); member.palletNo = pallet.palletNo const bound = await request('POST', `${palletPath}/bind`, { palletCode: member.palletNo, idempotencyKey: key(`BIND-${suffix}`) }, 'Bind scanned physical pallet', pdaToken) check('Bound pallet is exactly the measured one-box pallet', bound?.pallets?.length === 1 && bound.pallets[0].status === 'BOUND' && id(bound.pallets[0].warehousePalletId) === member.palletId && Number(bound.pallets[0].cartonCount) === 1) const photoPath = `${pdaPath}/measurement-photos/files` const photo = record('POST', photoPath, undefined, await context.requestMultipart('POST', photoPath, { name: `${member.customerMarker}-TEST-MEASUREMENT.png`, type: 'image/png', content: testImage }, pdaToken), 'Upload TEST measurement evidence') check('Measurement evidence is a stored file', photo?.fileId && Number(photo.sizeBytes) > 0) incoming = await request('POST', `${pdaPath}/palletize`, { idempotencyKey: key(`PALLETIZE-${suffix}`), photoFileIds: [id(photo.fileId)] }, 'Complete real PDA receipt', pdaToken) check('Real PDA receipt completed with original physical pallet', incoming?.status === 'WAIT_CUSTOMER_CONFIRMATION' && incoming.pallets?.length === 1 && incoming.pallets[0].status === 'COMPLETED' && id(incoming.pallets[0].warehousePalletId) === member.palletId) incoming = await request('POST', `${merchantPath}/confirm-measurement`, { idempotencyKey: key(`CONFIRM-${suffix}`), remark: member.customerMarker }, 'Customer confirms measured incoming', auth.accessToken) check('Customer confirmation makes ordinary incoming dispatchable', incoming?.transferNo === member.incomingNo && incoming.status === 'READY_FOR_DISPATCH') member.receipt = await stock(member) member.balanceId = id(member.receipt.balance.id); member.positionId = id(member.receipt.positions[0].id); member.receiptVerified = true const dispatchBase = `${ROOT}/dispatches` const candidates = await request('GET', `${dispatchBase}/candidates?${new URLSearchParams({ sourceStage: 'RECEIVED', orderNo: member.incomingNo })}`, undefined, 'Read own real received dispatch candidate') check('Candidate has exact customer, incoming, warehouse and physical pallet ownership', candidates?.length === 1 && id(candidates[0].customerId) === member.customerId && id(candidates[0].overseasWarehouseId) === id(warehouseId) && candidates[0].inboundCode === member.incomingNo && id(candidates[0].warehousePalletId) === member.palletId && candidates[0].palletNo === member.palletNo && candidates[0].sourceStage === 'RECEIVED' && Number(candidates[0].packageCount) === 1 && candidates[0].planPalletId) member.draftToken = key(`DRAFT-${suffix}`) const container = await request('POST', `${dispatchBase}/drafts/containers`, { draftToken: member.draftToken, capacity: 30 }, 'Create isolated dispatch draft') check('Dispatch draft has explicit container identity', container?.id) member.draftContainerId = id(container.id) await request('POST', `${dispatchBase}/draft-locks/lock`, { planPalletId: id(candidates[0].planPalletId), draftToken: member.draftToken, draftContainerId: member.draftContainerId }, 'Lock this customer received pallet') const dispatch = await request('POST', `${dispatchBase}/pre-arrange`, { planPalletIds: [id(candidates[0].planPalletId)], draftToken: member.draftToken }, 'Prearrange one independent customer dispatch') check('Dispatch contains exactly this unpicked pallet', dispatch?.dispatchNo && dispatch.status === 'PENDING_DEPARTURE' && dispatch.items?.length === 1 && id(dispatch.items[0].warehousePalletId) === member.palletId && Number(dispatch.packageCount) === 1 && Number(dispatch.pickedPackageCount) === 0 && Number(dispatch.shippedPackageCount) === 0) member.dispatchNo = dispatch.dispatchNo } for (const field of ['customerId', 'incomingNo', 'palletId', 'positionId', 'balanceId', 'dispatchNo']) check(`Both customers have independent ${field}`, new Set(proof.members.map(row => row[field])).size === 2) const tripProof = await prepareFleetTrip(context, MODULE, proof.members[0].dispatchNo, id(warehouseId)) proof.tripNo = tripProof.tripNo const tripPath = `${ROOT}/fleet/trips/${encode(proof.tripNo)}` let detail = await request('GET', tripPath, undefined, 'Read first member trip version') detail = await request('POST', `${tripPath}/dispatches/bind`, { recordVersion: detail.trip.recordVersion, dispatchNo: proof.members[1].dispatchNo, reason: context.marker }, 'Bind second independent customer dispatch') const loading = await request('GET', `${tripPath}/loading`, undefined, 'Read complete two-customer cargo destinations') check('Shared trip cargo consists of exactly both owned physical pallets', loading?.cargo?.length === 2 && proof.members.every(member => loading.cargo.some(row => row.dispatchNo === member.dispatchNo && id(row.customerId) === member.customerId && id(row.warehousePalletId) === member.palletId && row.palletNo === member.palletNo && row.destinationKey && Number(row.packages) === 1))) const destinations = new Map(loading.cargo.map(row => [row.destinationKey, { name: row.destinationLabel, destinationKey: row.destinationKey }])) detail = await request('PUT', `${tripPath}/plan`, { recordVersion: detail.trip.recordVersion, plannedDepartureAt: detail.trip.plannedDepartureAt, expectedDriverEndAt: detail.trip.expectedDriverEndAt, stops: [{ name: origin.warehouse.warehouseName }, ...destinations.values()], reason: context.marker }, 'Replace route with complete actual destinations') tripProof.stops = detail.trip.stops check('Route includes every actual destination', [...destinations.keys()].every(destinationKey => detail.trip.stops?.slice(1).some(row => row.destinationKey === destinationKey))) for (const [index, member] of proof.members.entries()) { const path = `${ROOT}/dispatches/${encode(member.dispatchNo)}` const freight = await request('GET', `${path}/external-freight`, undefined, 'Verify ordinary incoming has no external quote') check('Ordinary incoming requires its own explicit delivery fee', freight?.requiresDeliveryFee === true && freight.quotes?.length === 0) const transferred = await request('POST', `${path}/transfer-delivery`, { idempotencyKey: key(`DELIVERY-${index}`), plannedDeliveryAt: localTime(21600000), deliveryMethod: 'TRUCK_PALLET', pickupMethod: 'WAREHOUSE_PICKUP', loadingType: 'PALLET', vehicleType: 'TRUCK', vehicleSize: Number(detail.trip.maxPalletSpaces) <= 15 ? '15托' : '30托', vehiclePlate: detail.trip.vehiclePlate, driverName: detail.trip.driverName, driverPhone: detail.trip.driverPhone, appointmentAccount: 'SELF', remark: member.customerMarker, fees: [{ feeType: 'DELIVERY_FEE', feeName: '受控测试派送费', direction: 'RECEIVABLE', currency: 'CNY', amount: 30 }] }, 'Create this customer outbound and delivery') check('One customer dispatch produces one outbound and one delivery', transferred?.dispatchNo === member.dispatchNo && transferred.outboundNo && transferred.deliveries?.length === 1 && transferred.deliveries[0].deliveryNo && Number(transferred.packageCount) === 1) member.outboundNo = transferred.outboundNo; member.deliveryNo = transferred.deliveries[0].deliveryNo const delivery = await request('GET', `${ROOT}/delivery-orders/${encode(member.deliveryNo)}?${own(member)}`, undefined, 'Verify independently owned delivery') const outbound = await request('GET', `${ROOT}/outbound-orders/${encode(member.outboundNo)}?${own(member)}`, undefined, 'Verify independently owned outbound') const dispatch = await request('GET', path, undefined, 'Verify customer dispatch remains unpicked') check('Customer execution records are distinct and untouched', id(delivery?.order?.customerId) === member.customerId && delivery.order.deliveryNo === member.deliveryNo && delivery.order.dispatchNo === member.dispatchNo && delivery.order.outboundNo === member.outboundNo && delivery.order.status === 'WAIT_DELIVERY' && outbound?.outbound?.outboundNo === member.outboundNo && outbound.outbound.dispatchNo === member.dispatchNo && id(outbound.outbound.overseasWarehouseId) === id(warehouseId) && outbound.outbound.status === 'WAIT_OUTBOUND' && outbound.deliveries?.length === 1 && id(outbound.deliveries[0].customerId) === member.customerId && outbound.deliveries[0].deliveryNo === member.deliveryNo && dispatch?.status === 'PENDING_DEPARTURE' && Number(dispatch.pickedPackageCount) === 0 && Number(dispatch.shippedPackageCount) === 0) const finalStock = await stock(member) check('Trip preparation preserves original available receipt records', JSON.stringify(finalStock) === JSON.stringify(member.receipt)) } detail = await request('GET', tripPath, undefined, 'Verify final two-customer shared trip membership') check('Final trip has only the two fresh customers and remains planned', detail?.trip?.status === 'PLANNED' && detail.dispatches?.length === 2 && proof.members.every(member => detail.dispatches.some(row => row.dispatchNo === member.dispatchNo && id(row.customerId) === member.customerId))) for (const field of ['outboundNo', 'deliveryNo']) check(`Independent ${field}`, new Set(proof.members.map(row => row[field])).size === 2) fixture = { marker: context.marker, stage: fixtureContract.stage, tripNo: proof.tripNo, warehouseId: id(warehouseId), pdaToken, members: proof.members.map(({ marker, customerId, incomingNo, dispatchNo, outboundNo, deliveryNo, balanceId, positionId, palletId, palletNo, receiptVerified }) => ({ marker, customerId, incomingNo, dispatchNo, outboundNo, deliveryNo, balanceId, positionId, palletId, palletNo, receiptVerified })) } } catch (error) { failure = error } // Always attempt every successfully opened session, including partial fixture failures. PDA lifecycle belongs to caller. const cleanupErrors = [] for (const session of sessions) { try { await request('POST', '/api/auth/logout', { refreshToken: session.refreshToken }, 'Logout owned merchant fixture session', session.accessToken) proof.sessionCleanup.push({ customerId: session.customerId, status: 'LOGGED_OUT' }) } catch (error) { cleanupErrors.push(error); proof.sessionCleanup.push({ customerId: session.customerId, status: 'FAILED' }) } } proof.status = failure || cleanupErrors.length ? 'FAILED' : 'READY' if (failure && !cleanupErrors.length) throw failure if (cleanupErrors.length) throw new AggregateError([...(failure ? [failure] : []), ...cleanupErrors], 'Fixture/session cleanup failed; retain recorded resources') return fixture }