2026-10-07 16:46:56 +08:00
|
|
|
import test from 'node:test'
|
|
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import { parsePoolArguments, poolPlan, runPoolAcceptance, assertPoolRemaining, assertSingleCommercial,
|
2026-10-07 16:54:05 +08:00
|
|
|
shrinkingVehicleRequest, expiryWaitMs, poolLocalDateTime, POOL_COVERAGE, POOL_REMAINING } from './fleet-capacity-pool-test-env.mjs'
|
2026-10-07 16:46:56 +08:00
|
|
|
|
|
|
|
|
test('default and explicit plans make no network requests or invoke the mutating runner', async () => {
|
|
|
|
|
const original = globalThis.fetch
|
|
|
|
|
globalThis.fetch = async () => { throw Error('unexpected network') }
|
|
|
|
|
try {
|
|
|
|
|
for (const args of [[], ['--plan']]) {
|
|
|
|
|
const config = parsePoolArguments(args)
|
|
|
|
|
const plan = await runPoolAcceptance(config, { runner: async () => { throw Error('unexpected runner') } })
|
|
|
|
|
assert.equal(plan.execute, false)
|
|
|
|
|
assert.equal(plan.environment, 'TEST')
|
|
|
|
|
assert.equal(plan.runId, 'plan')
|
|
|
|
|
assert.match(plan.boundary, /no network or filesystem writes/)
|
|
|
|
|
}
|
|
|
|
|
} finally { globalThis.fetch = original }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('execution needs explicit TEST, fresh run ID and current warehouse identifiers', () => {
|
|
|
|
|
for (const args of [
|
|
|
|
|
['--execute'], ['--execute', '--run-id', 'fresh', '--warehouse-id', '10', '--destination-warehouse-id', '11'],
|
|
|
|
|
['--execute', '--environment', 'gray', '--run-id', 'fresh', '--warehouse-id', '10', '--destination-warehouse-id', '11'],
|
|
|
|
|
['--execute', '--environment', 'TEST', '--run-id', '../reuse', '--warehouse-id', '10', '--destination-warehouse-id', '11'],
|
|
|
|
|
['--execute', '--plan'], ['--resume', 'old-report'],
|
|
|
|
|
]) assert.throws(() => parsePoolArguments(args))
|
|
|
|
|
const parsed = parsePoolArguments(['--execute', '--environment', 'TEST', '--run-id', 'pool-r1', '--warehouse-id', '10', '--destination-warehouse-id', '11'])
|
|
|
|
|
assert.equal(parsed.execute, true)
|
|
|
|
|
assert.equal(parsed.runId, 'pool-r1')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('plan distinguishes implemented HTTP scope from remaining whole-feature acceptance', () => {
|
|
|
|
|
const plan = poolPlan(parsePoolArguments([]))
|
|
|
|
|
assert.deepEqual(plan.coveredByThisEntry, POOL_COVERAGE)
|
|
|
|
|
assert.deepEqual(plan.remainingSeparateAcceptance, POOL_REMAINING)
|
|
|
|
|
assert.ok(plan.coveredByThisEntry.includes('both-business-types-real-pda-receipt'))
|
|
|
|
|
assert.ok(plan.remainingSeparateAcceptance.includes('full-UI-1920x1080-and-network-request-budget'))
|
|
|
|
|
assert.ok(plan.coveredByThisEntry.includes('weight-and-volume-independent-capacity-rejections'))
|
|
|
|
|
assert.ok(plan.coveredByThisEntry.includes('paused-create-edit-confirm-rejected'))
|
|
|
|
|
assert.ok(plan.coveredByThisEntry.includes('expired-cutoff-create-edit-confirm-rejected'))
|
|
|
|
|
assert.ok(plan.coveredByThisEntry.includes('assigned-vehicle-disable-and-shrink-rejected'))
|
|
|
|
|
assert.ok(plan.remainingSeparateAcceptance.includes('out-of-band-assigned-vehicle-drift-not-constructible-through-supported-api'))
|
|
|
|
|
assert.match(plan.boundary, /unknown outcome/)
|
|
|
|
|
assert.match(plan.boundary, /No automatic rerun or resume/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('remaining capacity uses unique current destination and rejects unknown, other-stop or duplicate rows', () => {
|
|
|
|
|
const offer = { capacityKnown: true, destinations: [{ destinationKey: 'FBA:A', remaining: { palletSpaces: 4 } },
|
|
|
|
|
{ destinationKey: 'FBA:B', remaining: { palletSpaces: 30 } }] }
|
|
|
|
|
assert.doesNotThrow(() => assertPoolRemaining(offer, 'FBA:A', 4))
|
|
|
|
|
for (const change of [o => { o.capacityKnown = false }, o => { o.destinations[0].remaining = null },
|
|
|
|
|
o => { o.destinations[0].destinationKey = 'FBA:C' }, o => { o.destinations.push(structuredClone(o.destinations[0])) },
|
|
|
|
|
o => { o.destinations[0].remaining.palletSpaces = 5 }]) {
|
|
|
|
|
const altered = structuredClone(offer); change(altered)
|
|
|
|
|
assert.throws(() => assertPoolRemaining(altered, 'FBA:A', 4), /EXACT_SEGMENT/)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('successful confirmation replay must preserve commercial, booking, business type and customer identity', () => {
|
|
|
|
|
const before = { bookingNo: 'FB-1', businessOrderNo: 'LB-1', businessType: 'LOCAL_LOOSEBOARD', customerId: '9' }
|
|
|
|
|
assert.doesNotThrow(() => assertSingleCommercial(before, { ...before }))
|
|
|
|
|
for (const field of Object.keys(before)) assert.throws(() => assertSingleCommercial(before, { ...before, [field]: 'OTHER' }), /IDENTITY_CHANGED/)
|
|
|
|
|
assert.throws(() => assertSingleCommercial({ ...before, businessOrderNo: null }, { ...before }), /IDENTITY_CHANGED/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('default executor rejects non-TEST process configuration before any fixture writes', async () => {
|
|
|
|
|
const config = parsePoolArguments(['--execute', '--environment', 'TEST', '--run-id', 'pool-r1', '--warehouse-id', '10', '--destination-warehouse-id', '11'])
|
|
|
|
|
await assert.rejects(runPoolAcceptance(config, { env: { CHONGCHONG_ENV: 'prod' } }), /Explicit TEST configuration/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('vehicle shrink command preserves the authoritative full request and only changes capacity by one', () => {
|
|
|
|
|
const vehicle = { id: '7', recordVersion: 11, plateNumber: 'TEST-7', model: 'Box Truck', fuelConsumptionPer100Km: 12.5,
|
|
|
|
|
mileageKm: 100, driverOrDispatchCompany: 'TEST', phone: null, region: 'OVERSEAS', ownership: 'OWNED', remark: 'own fixture',
|
|
|
|
|
maxPalletSpaces: 30, maxLoadKg: 18000, maxVolumeCbm: 90, status: 'IN_SERVICE' }
|
|
|
|
|
const request = shrinkingVehicleRequest(vehicle)
|
|
|
|
|
assert.equal(request.maxPalletSpaces, 29)
|
|
|
|
|
for (const field of Object.keys(request).filter(name => name !== 'maxPalletSpaces')) assert.equal(request[field], vehicle[field])
|
|
|
|
|
assert.equal('status' in request, false)
|
|
|
|
|
assert.equal('id' in request, false)
|
|
|
|
|
assert.equal(vehicle.maxPalletSpaces, 30)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('vehicle matrix refuses incomplete snapshots instead of inventing capacity for a write', () => {
|
|
|
|
|
for (const input of [null, {}, { id: '7', recordVersion: 1, maxPalletSpaces: 30 },
|
|
|
|
|
{ id: '7', recordVersion: 1, maxPalletSpaces: 1, maxLoadKg: 18000, maxVolumeCbm: 90 }]) {
|
|
|
|
|
assert.throws(() => shrinkingVehicleRequest(input), /SNAPSHOT_REQUIRED/)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('cutoff wait is bounded and follows the returned instant without negative or unbounded sleeps', () => {
|
|
|
|
|
const now = Date.parse('2030-01-01T08:00:00Z')
|
|
|
|
|
assert.equal(expiryWaitMs('2030-01-01T08:00:08Z', now), 8300)
|
|
|
|
|
assert.equal(expiryWaitMs('2030-01-01T07:59:00Z', now), 0)
|
|
|
|
|
assert.throws(() => expiryWaitMs('invalid', now), /INVALID_SERVER_CUTOFF/)
|
|
|
|
|
assert.throws(() => expiryWaitMs('2030-01-01T08:10:00Z', now), /BOUNDED_WINDOW/)
|
|
|
|
|
})
|
2026-10-07 16:54:05 +08:00
|
|
|
|
|
|
|
|
test('LocalDateTime transport time keeps actual local wall clock while Instant dates remain zoned', () => {
|
|
|
|
|
class ShanghaiDate extends Date { getTimezoneOffset() { return -480 } }
|
|
|
|
|
class LosAngelesDate extends Date { getTimezoneOffset() { return 420 } }
|
|
|
|
|
const instant = '2030-01-01T20:30:45.123Z'
|
|
|
|
|
assert.equal(poolLocalDateTime(new ShanghaiDate(instant)), '2030-01-02T04:30:45')
|
|
|
|
|
assert.equal(poolLocalDateTime(new LosAngelesDate(instant)), '2030-01-01T13:30:45')
|
|
|
|
|
assert.equal(new ShanghaiDate(instant).toISOString(), instant)
|
|
|
|
|
assert.equal(new LosAngelesDate(instant).toISOString(), instant)
|
|
|
|
|
assert.throws(() => poolLocalDateTime(new Date('invalid')), /VALID_LOCAL_DATE_REQUIRED/)
|
|
|
|
|
})
|