286 lines
21 KiB
TypeScript
286 lines
21 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|||
|
|
import axios, { type AxiosInstance, type InternalAxiosRequestConfig } from 'axios'
|
||
|
|
import { mkdirSync, writeFileSync } from 'node:fs'
|
||
|
|
import { resolve } from 'node:path'
|
||
|
|
import { createPinia, setActivePinia } from 'pinia'
|
||
|
|
import { createMemoryHistory, createRouter } from 'vue-router'
|
||
|
|
import { defineComponent, nextTick } from 'vue'
|
||
|
|
import { mount, type VueWrapper } from '@vue/test-utils'
|
||
|
|
import ElementPlus from 'element-plus'
|
||
|
|
import { http, ApiResponseError } from '@/shared/api/http'
|
||
|
|
import { clearOverseasAccessToken, overseasHttp } from '@/shared/api/overseasHttp'
|
||
|
|
import { useAuthStore } from '@/core/auth/auth'
|
||
|
|
import { listPlatformTenants } from '@/modules/iam/api/platformIam'
|
||
|
|
import {
|
||
|
|
changeExchangeRateStatus, changeQuoteStatus, changeVehicleStatus, createExchangeRate, createQuote, createVehicle,
|
||
|
|
exportExchangeRates, exportQuotes, exportVehicles, getExchangeRate, getQuote, getVehicle, listExchangeRates,
|
||
|
|
listQuoteWarehouseOptions, listQuotes, listVehicles, updateExchangeRate, updateQuote, updateVehicle,
|
||
|
|
} from '@/modules/overseas-master-data/api/overseasMasterData'
|
||
|
|
import {
|
||
|
|
createLocalLooseboardOrder, createUsTruckOrder, getLocalLooseboardOrder, getUsTruckOrder,
|
||
|
|
listLocalLooseboardOrders, listOverseasPurchaseCustomerOptions, listOverseasPurchases,
|
||
|
|
listOverseasPurchaseWarehouseOptions, listUsTruckOrders, updateLocalLooseboardOrder, updateUsTruckOrder,
|
||
|
|
} from '@/modules/overseas-business/api/platformOverseasBusiness'
|
||
|
|
import { listLastMileOrders, getLastMileOrder } from '@/modules/overseas-warehouse/api/lastMileOrders'
|
||
|
|
import { listInboundOrders } from '@/modules/overseas-warehouse/api/inboundOrders'
|
||
|
|
import { getInboundPlan } from '@/modules/overseas-warehouse/api/inboundPlans'
|
||
|
|
import { listDispatchCandidates, listDispatchDrafts, listDispatches } from '@/modules/overseas-warehouse/api/dispatches'
|
||
|
|
import { getInventorySummary, listInventoryBalances, listInventoryLedger, listInventoryPositions } from '@/modules/overseas-warehouse/api/inventory'
|
||
|
|
import { createPallet, getPallet, listCustomerWarehouses, listPallets, updatePallet } from '@/modules/overseas-warehouse/api/pallets'
|
||
|
|
import { listRelabelOrders } from '@/modules/overseas-warehouse/api/relabelOrders'
|
||
|
|
import { getCarrierScheduleQuery, listCarrierScheduleQueries } from '@/modules/port-schedules/api/platformCarrierScheduleQueries'
|
||
|
|
import OverseasQuotesView from '@/modules/overseas-master-data/pages/OverseasQuotesView.vue'
|
||
|
|
import PurchaseOrdersView from '@/modules/overseas-business/pages/PurchaseOrdersView.vue'
|
||
|
|
import TransportOrdersView from '@/modules/overseas-business/pages/TransportOrdersView.vue'
|
||
|
|
import LastMileOrdersView from '@/modules/overseas-warehouse/pages/LastMileOrdersView.vue'
|
||
|
|
import InboundOrdersView from '@/modules/overseas-warehouse/pages/InboundOrdersView.vue'
|
||
|
|
import InventoryView from '@/modules/overseas-warehouse/pages/InventoryView.vue'
|
||
|
|
import DispatchesView from '@/modules/overseas-warehouse/pages/DispatchesView.vue'
|
||
|
|
import RelabelOrdersView from '@/modules/overseas-warehouse/pages/RelabelOrdersView.vue'
|
||
|
|
|
||
|
|
type Region = 'domestic' | 'overseas'
|
||
|
|
interface TraceStep { region: Region; method: string; path: string; params: unknown; body: unknown; httpStatus: number; code?: string; requestId?: string; traceId?: string; authorization: boolean }
|
||
|
|
interface CoverageGap { area: string; reason: string }
|
||
|
|
|
||
|
|
const enabled = process.env.ADMIN_PHASE4_LIVE === '1'
|
||
|
|
const marker = `ADMIN-FRONT-P4-${new Date().toISOString().replace(/\D/g, '').slice(0, 14)}`
|
||
|
|
const traces: TraceStep[] = []
|
||
|
|
const coverageGaps: CoverageGap[] = []
|
||
|
|
const createdResources: Record<string, unknown> = {}
|
||
|
|
const wrappers: VueWrapper[] = []
|
||
|
|
|
||
|
|
function redact(value: unknown): unknown {
|
||
|
|
if (Array.isArray(value)) return value.map(redact)
|
||
|
|
if (!value || typeof value !== 'object') return value
|
||
|
|
return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([key, item]) => [key, /password|token|authorization/i.test(key) ? '[REDACTED]' : redact(item)]))
|
||
|
|
}
|
||
|
|
function parsed(value: unknown) {
|
||
|
|
if (typeof value !== 'string') return value
|
||
|
|
try { return JSON.parse(value) } catch { return value }
|
||
|
|
}
|
||
|
|
function envelope(value: unknown) {
|
||
|
|
const body = parsed(value) as { code?: unknown; requestId?: unknown; traceId?: unknown } | null
|
||
|
|
return body && typeof body === 'object' ? body : null
|
||
|
|
}
|
||
|
|
function installTracingAdapter(client: AxiosInstance, region: Region) {
|
||
|
|
const nodeAdapter = axios.getAdapter('http')
|
||
|
|
client.defaults.adapter = async (config: InternalAxiosRequestConfig) => {
|
||
|
|
try {
|
||
|
|
const response = await nodeAdapter(config)
|
||
|
|
const body = envelope(response.data)
|
||
|
|
traces.push({ region, method: String(config.method || 'GET').toUpperCase(), path: String(config.url || ''), params: redact(config.params), body: redact(parsed(config.data)), httpStatus: response.status, code: body?.code == null ? undefined : String(body.code), requestId: body?.requestId == null ? undefined : String(body.requestId), traceId: body?.traceId == null ? undefined : String(body.traceId), authorization: Boolean(config.headers?.Authorization) })
|
||
|
|
return response
|
||
|
|
} catch (error) {
|
||
|
|
if (axios.isAxiosError(error)) {
|
||
|
|
const body = envelope(error.response?.data)
|
||
|
|
traces.push({ region, method: String(config.method || 'GET').toUpperCase(), path: String(config.url || ''), params: redact(config.params), body: redact(parsed(config.data)), httpStatus: error.response?.status || 0, code: body?.code == null ? undefined : String(body.code), requestId: body?.requestId == null ? undefined : String(body.requestId), traceId: body?.traceId == null ? undefined : String(body.traceId), authorization: Boolean(config.headers?.Authorization) })
|
||
|
|
}
|
||
|
|
throw error
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function gap(area: string, reason: string) { coverageGaps.push({ area, reason }) }
|
||
|
|
function numericId(value: unknown) { return /^[1-9]\d*$/.test(String(value ?? '')) }
|
||
|
|
function expectPage(value: { records: unknown[]; page: number; size: number; total: number; pages: number }) {
|
||
|
|
expect(Array.isArray(value.records)).toBe(true); expect(value.page).toBeGreaterThanOrEqual(1); expect(value.size).toBeGreaterThan(0); expect(value.total).toBeGreaterThanOrEqual(value.records.length); expect(value.pages).toBeGreaterThanOrEqual(0)
|
||
|
|
}
|
||
|
|
async function expectConflict(action: () => Promise<unknown>) {
|
||
|
|
await expect(action()).rejects.toSatisfy((error: unknown) => {
|
||
|
|
if (error instanceof ApiResponseError) return error.code.endsWith('409')
|
||
|
|
if (!axios.isAxiosError(error)) return false
|
||
|
|
const response = parsed(error.response?.data) as { code?: unknown } | undefined
|
||
|
|
return error.response?.status === 409 || String(response?.code || '').endsWith('409')
|
||
|
|
})
|
||
|
|
}
|
||
|
|
function futureDate(days: number) { return new Date(Date.now() + days * 86_400_000).toISOString().slice(0, 10) }
|
||
|
|
|
||
|
|
async function masterDataLifecycle() {
|
||
|
|
const options = await listQuoteWarehouseOptions()
|
||
|
|
const quotePage = await listQuotes({ page: 1, size: 20 })
|
||
|
|
expectPage(quotePage)
|
||
|
|
if (options.length) {
|
||
|
|
const quote = await createQuote({ name: `${marker} 报价`, warehouseId: options[0].warehouseId, providerName: `${marker} Provider`, currency: 'USD', effectiveStart: '2098-01-01', effectiveEnd: '2098-12-31', versionCode: marker.slice(-32), warehouseFees: [{ item: 'Storage QA', billingUnit: 'PALLET', unitPrice: 1.25, minimumCharge: 2.5, remark: marker }], transitPrices: [], remark: marker })
|
||
|
|
const id = quote.summary.id
|
||
|
|
createdResources.quote = id
|
||
|
|
let detail = await getQuote(id)
|
||
|
|
const stale = detail.summary.recordVersion
|
||
|
|
detail = await updateQuote(id, { recordVersion: stale, name: `${marker} 报价-编辑`, providerName: detail.summary.providerName, currency: detail.summary.currency, effectiveStart: detail.summary.effectiveStart, effectiveEnd: detail.summary.effectiveEnd || '2098-12-31', warehouseFees: detail.warehouseFees, transitPrices: detail.transitPrices, remark: `${marker} edited` })
|
||
|
|
await expectConflict(() => updateQuote(id, { recordVersion: stale, name: 'STALE', providerName: detail.summary.providerName, currency: detail.summary.currency, effectiveStart: detail.summary.effectiveStart, effectiveEnd: detail.summary.effectiveEnd || '2098-12-31', warehouseFees: detail.warehouseFees, transitPrices: detail.transitPrices, remark: marker }))
|
||
|
|
detail = await changeQuoteStatus(id, 'ACTIVE', detail.summary.recordVersion)
|
||
|
|
expect(detail.summary.status).toBe('ACTIVE')
|
||
|
|
expect((await exportQuotes({ keyword: marker })).size).toBeGreaterThan(0)
|
||
|
|
detail = await changeQuoteStatus(id, 'INACTIVE', detail.summary.recordVersion)
|
||
|
|
expect(detail.summary.status).toBe('INACTIVE')
|
||
|
|
} else gap('海外报价', 'warehouse-options 为空,未猜测仓库 ID,跳过报价写生命周期')
|
||
|
|
|
||
|
|
let exchangeMonth = ''
|
||
|
|
for (let index = 0; index < 24 && !exchangeMonth; index += 1) {
|
||
|
|
const month = `${2097 + Math.floor(index / 12)}-${String(index % 12 + 1).padStart(2, '0')}`
|
||
|
|
const page = await listExchangeRates({ page: 1, size: 20, monthFrom: month, monthTo: month })
|
||
|
|
if (!page.total) exchangeMonth = month
|
||
|
|
}
|
||
|
|
if (!exchangeMonth) throw new Error('安全汇率月份池已占满')
|
||
|
|
let rate = await createExchangeRate({ month: exchangeMonth, usdToCnyAverage: 7.123456, remark: marker })
|
||
|
|
createdResources.exchangeRate = exchangeMonth
|
||
|
|
const staleRate = rate.recordVersion
|
||
|
|
rate = await updateExchangeRate(exchangeMonth, { recordVersion: staleRate, usdToCnyAverage: 7.234567, remark: `${marker} edited` })
|
||
|
|
await expectConflict(() => updateExchangeRate(exchangeMonth, { recordVersion: staleRate, usdToCnyAverage: 7.9, remark: 'STALE' }))
|
||
|
|
rate = await getExchangeRate(exchangeMonth)
|
||
|
|
rate = await changeExchangeRateStatus(exchangeMonth, 'INACTIVE', rate.recordVersion, `${marker} cleanup`)
|
||
|
|
expect(rate.status).toBe('INACTIVE')
|
||
|
|
expect((await exportExchangeRates({ monthFrom: exchangeMonth, monthTo: exchangeMonth })).size).toBeGreaterThan(0)
|
||
|
|
|
||
|
|
let vehicle = await createVehicle({ plateNumber: `QA-${marker.slice(-18)}`, model: 'Phase4 Front QA', fuelConsumptionPer100Km: 18.5, mileageKm: 100, driverOrDispatchCompany: marker, phone: '+1-213-555-0188', region: 'OVERSEAS', ownership: 'OWNED', status: 'IN_SERVICE', remark: marker })
|
||
|
|
createdResources.vehicle = vehicle.id
|
||
|
|
const staleVehicle = vehicle.recordVersion
|
||
|
|
vehicle = await updateVehicle(vehicle.id, { recordVersion: staleVehicle, plateNumber: vehicle.plateNumber, model: 'Phase4 Front QA Edited', fuelConsumptionPer100Km: 18.75, mileageKm: 101, driverOrDispatchCompany: marker, phone: vehicle.phone, region: vehicle.region, ownership: vehicle.ownership, remark: `${marker} edited` })
|
||
|
|
await expectConflict(() => updateVehicle(vehicle.id, { recordVersion: staleVehicle, plateNumber: vehicle.plateNumber, model: 'STALE', fuelConsumptionPer100Km: 19, mileageKm: 102, driverOrDispatchCompany: marker, phone: vehicle.phone, region: vehicle.region, ownership: vehicle.ownership, remark: 'STALE' }))
|
||
|
|
vehicle = await getVehicle(vehicle.id)
|
||
|
|
vehicle = await changeVehicleStatus(vehicle.id, 'INACTIVE', vehicle.recordVersion)
|
||
|
|
expect(vehicle.status).toBe('INACTIVE')
|
||
|
|
expect((await exportVehicles({ keyword: vehicle.plateNumber })).size).toBeGreaterThan(0)
|
||
|
|
expectPage(await listVehicles({ page: 1, size: 20, keyword: marker }))
|
||
|
|
}
|
||
|
|
|
||
|
|
async function cleanupTransport(path: string, customerId: string, version: number) {
|
||
|
|
await overseasHttp.patch(`${path}/status`, { version, status: 'CANCELLED', remark: `${marker} cleanup` }, { params: { customerId } })
|
||
|
|
}
|
||
|
|
async function overseasBusinessLifecycle() {
|
||
|
|
const customers = (await listPlatformTenants({ page: 1, size: 200, status: 'ACTIVE' })).records.filter(item => item.status === 'ACTIVE')
|
||
|
|
const active = customers.find(item => numericId(item.id))
|
||
|
|
if (!active) { gap('卡车/散板', 'IAM 无 ACTIVE 客户'); return }
|
||
|
|
const customerId = String(active.id)
|
||
|
|
expect(Array.isArray(await listOverseasPurchaseCustomerOptions())).toBe(true)
|
||
|
|
expect(Array.isArray(await listOverseasPurchaseWarehouseOptions())).toBe(true)
|
||
|
|
expectPage(await listOverseasPurchases({ page: 1, size: 20 }))
|
||
|
|
const common = { origin: `${marker}-ORIGIN`, originAddress: 'Long Beach', destination: `${marker}-DEST`, destinationAddress: 'Ontario', containerType: '40HQ', packageCount: 2, totalWeightKg: 100, totalVolumeCbm: 2.5, budgetAmount: 88, currency: 'USD', contactName: '四期前端验收', contactInfo: '13800000000', requestedDate: futureDate(7), remark: marker }
|
||
|
|
let truck = await createUsTruckOrder(customerId, { ...common, transportType: 'PORT_PICKUP', palletCount: 1, idempotencyKey: `${marker}-truck` })
|
||
|
|
createdResources.truck = truck.orderNo
|
||
|
|
try {
|
||
|
|
truck = await getUsTruckOrder(truck.orderNo, customerId)
|
||
|
|
const stale = truck.version
|
||
|
|
truck = await updateUsTruckOrder(truck.orderNo, customerId, { ...common, transportType: 'PORT_PICKUP', palletCount: 1, destination: `${marker}-DEST-EDIT`, version: stale })
|
||
|
|
await expectConflict(() => updateUsTruckOrder(truck.orderNo, customerId, { ...common, transportType: 'PORT_PICKUP', palletCount: 1, destination: 'STALE', version: stale }))
|
||
|
|
} finally {
|
||
|
|
truck = await getUsTruckOrder(truck.orderNo, customerId)
|
||
|
|
if (truck.status === 'PENDING_REVIEW') await cleanupTransport(`/platform/v1/last-leg/us-truck-orders/${encodeURIComponent(truck.orderNo)}`, customerId, truck.version)
|
||
|
|
}
|
||
|
|
expectPage(await listUsTruckOrders({ page: 1, size: 20, customerId }))
|
||
|
|
|
||
|
|
let loose = await createLocalLooseboardOrder(customerId, { transportMode: 'SHARED_TRUCK', palletRequired: 'YES', transportType: 'WAREHOUSE_TO_WAREHOUSE', ...common, idempotencyKey: `${marker}-looseboard` })
|
||
|
|
createdResources.looseboard = loose.orderNo
|
||
|
|
try {
|
||
|
|
loose = await getLocalLooseboardOrder(loose.orderNo, customerId)
|
||
|
|
const stale = loose.version
|
||
|
|
loose = await updateLocalLooseboardOrder(loose.orderNo, customerId, { transportMode: loose.transportMode, palletRequired: loose.palletRequired, transportType: loose.transportType, ...common, destination: `${marker}-DEST-EDIT`, version: stale })
|
||
|
|
await expectConflict(() => updateLocalLooseboardOrder(loose.orderNo, customerId, { transportMode: loose.transportMode, palletRequired: loose.palletRequired, transportType: loose.transportType, ...common, destination: 'STALE', version: stale }))
|
||
|
|
} finally {
|
||
|
|
loose = await getLocalLooseboardOrder(loose.orderNo, customerId)
|
||
|
|
if (loose.status === 'PENDING_ACCEPTANCE') await cleanupTransport(`/platform/v1/last-leg/local-looseboard-orders/${encodeURIComponent(loose.orderNo)}`, customerId, loose.version)
|
||
|
|
}
|
||
|
|
expectPage(await listLocalLooseboardOrders({ page: 1, size: 20, customerId }))
|
||
|
|
}
|
||
|
|
|
||
|
|
async function overseasWarehouseCoverage() {
|
||
|
|
const lastMile = await listLastMileOrders({ page: 1, size: 20 }); expectPage(lastMile)
|
||
|
|
if (lastMile.records[0]) await getLastMileOrder(String(lastMile.records[0].id))
|
||
|
|
const inbound = await listInboundOrders({ page: 1, size: 20 }); expectPage(inbound)
|
||
|
|
if (inbound.records[0]) await getInboundPlan(inbound.records[0].planNo, inbound.records[0].customerId)
|
||
|
|
expectPage(await listDispatches({ page: 1, size: 20 }))
|
||
|
|
expect(Array.isArray(await listDispatchCandidates({}))).toBe(true)
|
||
|
|
expect(Array.isArray(await listDispatchDrafts())).toBe(true)
|
||
|
|
const inventoryQuery = { page: 1, size: 20 }
|
||
|
|
const balances = await listInventoryBalances(inventoryQuery); expectPage(balances)
|
||
|
|
await getInventorySummary(inventoryQuery)
|
||
|
|
await listInventoryLedger({ page: 1, size: 20 })
|
||
|
|
if (balances.records[0]) await listInventoryPositions(String(balances.records[0].id), 1, 20)
|
||
|
|
expectPage(await listRelabelOrders({ page: 1, size: 20 }))
|
||
|
|
const warehouses = (await listCustomerWarehouses()).records.filter(item => item.status === 'ACTIVE')
|
||
|
|
if (!warehouses.length) { gap('公共托盘', '实际海外仓为空,跳过前端托盘写生命周期'); return }
|
||
|
|
const warehouse = warehouses[0]
|
||
|
|
const payload = { palletCode: `QA-${marker.slice(-20)}`, palletType: 'STANDARD', fbaWarehouseCode: 'ONT8', capacityCbm: 2.5, unitPrice: 10, status: 'IDLE', remark: marker }
|
||
|
|
const palletId = await createPallet(warehouse.id, payload)
|
||
|
|
createdResources.pallet = palletId
|
||
|
|
try {
|
||
|
|
const pallet = await getPallet(warehouse.id, palletId)
|
||
|
|
await updatePallet(warehouse.id, palletId, { ...payload, capacityCbm: 2.75, remark: `${marker} edited`, version: pallet.version })
|
||
|
|
await expectConflict(() => updatePallet(warehouse.id, palletId, { ...payload, capacityCbm: 3, remark: 'STALE', version: pallet.version }))
|
||
|
|
expectPage(await listPallets(warehouse.id, { page: 1, size: 20, keyword: payload.palletCode }))
|
||
|
|
}
|
||
|
|
finally {
|
||
|
|
const current = await getPallet(warehouse.id, palletId)
|
||
|
|
if (current.status !== 'SCRAPPED') await updatePallet(warehouse.id, palletId, { ...payload, status: 'SCRAPPED', remark: `${marker} cleanup`, version: current.version })
|
||
|
|
}
|
||
|
|
gap('FBA预约/智能预排/换标新增', '没有本轮 marker 入仓计划和托盘候选;为避免操作历史数据,本轮只自动覆盖列表、详情、候选和草稿查询')
|
||
|
|
}
|
||
|
|
|
||
|
|
async function carrierCoverage() {
|
||
|
|
const page = await listCarrierScheduleQueries({ page: 1, size: 20 })
|
||
|
|
expectPage(page)
|
||
|
|
if (page.records[0]) await getCarrierScheduleQuery(page.records[0].query.id)
|
||
|
|
else gap('船节点人工补录', '环境无真实查询任务;未创建或修改历史任务')
|
||
|
|
}
|
||
|
|
|
||
|
|
async function waitForTrace(method: string, path: string, start: number) {
|
||
|
|
const deadline = Date.now() + 20_000
|
||
|
|
while (Date.now() < deadline) {
|
||
|
|
if (traces.slice(start).some(item => item.method === method && item.path.startsWith(path))) return
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 50))
|
||
|
|
}
|
||
|
|
throw new Error(`页面未发出 ${method} ${path}`)
|
||
|
|
}
|
||
|
|
async function mountPage(component: Parameters<typeof mount>[0], method: string, path: string, props?: Record<string, unknown>) {
|
||
|
|
const start = traces.length
|
||
|
|
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/:pathMatch(.*)*', component: defineComponent({ template: '<div />' }) }] })
|
||
|
|
await router.push('/'); await router.isReady()
|
||
|
|
const wrapper = mount(component, { attachTo: document.body, props, global: { plugins: [ElementPlus, router, setActivePinia(createPinia())] } })
|
||
|
|
wrappers.push(wrapper)
|
||
|
|
await waitForTrace(method, path, start)
|
||
|
|
await nextTick()
|
||
|
|
expect(wrapper.text()).not.toContain('服务暂时不可用')
|
||
|
|
}
|
||
|
|
async function pageRequestCoverage() {
|
||
|
|
await mountPage(OverseasQuotesView, 'GET', '/platform/v1/last-leg/master-data/overseas-quotes')
|
||
|
|
await mountPage(PurchaseOrdersView, 'GET', '/platform/v1/last-leg/overseas-procurements')
|
||
|
|
await mountPage(TransportOrdersView, 'GET', '/platform/v1/last-leg/us-truck-orders', { domain: 'truck' })
|
||
|
|
await mountPage(TransportOrdersView, 'GET', '/platform/v1/last-leg/local-looseboard-orders', { domain: 'looseboard' })
|
||
|
|
await mountPage(LastMileOrdersView, 'GET', '/platform/v1/last-leg/orders')
|
||
|
|
await mountPage(InboundOrdersView, 'GET', '/platform/v1/last-leg/inbound-plans')
|
||
|
|
await mountPage(InventoryView, 'GET', '/platform/v1/last-leg/inventory/balances')
|
||
|
|
await mountPage(DispatchesView, 'GET', '/platform/v1/last-leg/dispatches')
|
||
|
|
await mountPage(RelabelOrdersView, 'GET', '/platform/v1/last-leg/relabel-applications')
|
||
|
|
}
|
||
|
|
|
||
|
|
describe.skipIf(!enabled).sequential('phase4 development frontend acceptance', () => {
|
||
|
|
beforeAll(async () => {
|
||
|
|
Object.defineProperty(window, 'matchMedia', { writable: true, value: () => ({ matches: false, addEventListener() {}, removeEventListener() {} }) })
|
||
|
|
globalThis.ResizeObserver = class { observe() {}; unobserve() {}; disconnect() {} }
|
||
|
|
installTracingAdapter(http, 'domestic'); installTracingAdapter(overseasHttp, 'overseas')
|
||
|
|
const pinia = createPinia(); setActivePinia(pinia)
|
||
|
|
const auth = useAuthStore(pinia)
|
||
|
|
await auth.login(process.env.ADMIN_ACCEPTANCE_USERNAME || 'platform_admin', process.env.ADMIN_ACCEPTANCE_PASSWORD || '')
|
||
|
|
expect(localStorage.getItem('cc-admin-token')).toBeTruthy()
|
||
|
|
clearOverseasAccessToken()
|
||
|
|
await listVehicles({ page: 1, size: 1 })
|
||
|
|
expect(traces.some(item => item.region === 'domestic' && item.path === '/auth/refresh')).toBe(true)
|
||
|
|
expect(traces.some(item => item.region === 'overseas' && item.authorization)).toBe(true)
|
||
|
|
})
|
||
|
|
it('runs marker-owned adapter lifecycles and real page requests', async () => {
|
||
|
|
await masterDataLifecycle()
|
||
|
|
await overseasBusinessLifecycle()
|
||
|
|
await overseasWarehouseCoverage()
|
||
|
|
await carrierCoverage()
|
||
|
|
await pageRequestCoverage()
|
||
|
|
expect(traces.length).toBeGreaterThan(20)
|
||
|
|
})
|
||
|
|
afterAll(() => {
|
||
|
|
wrappers.splice(0).forEach(wrapper => wrapper.unmount())
|
||
|
|
const reportDir = resolve(process.cwd(), 'reports')
|
||
|
|
mkdirSync(reportDir, { recursive: true })
|
||
|
|
const report = { generatedAt: new Date().toISOString(), marker, summary: { requests: traces.length, coverageGaps: coverageGaps.length }, traces, coverageGaps, createdResources }
|
||
|
|
const file = resolve(reportDir, `phase4-frontend-development-${marker.replace(/[^A-Za-z0-9-]/g, '')}.json`)
|
||
|
|
writeFileSync(file, `${JSON.stringify(report, null, 2)}\n`, 'utf8')
|
||
|
|
console.info(`PHASE4_FRONTEND_REPORT=${file}`)
|
||
|
|
})
|
||
|
|
})
|