248 lines
9.4 KiB
JavaScript
248 lines
9.4 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
|
|
import {
|
|
TICKET_VIEWS,
|
|
applyRefund,
|
|
calculateAge,
|
|
calculateBookingTotals,
|
|
calculateRefundAmount,
|
|
createBookingDraft,
|
|
createPaidOrder,
|
|
isTravelerEligible,
|
|
popViewHistory,
|
|
validateBooking,
|
|
} from './model.mjs'
|
|
|
|
const adultProduct = {
|
|
id: 'adult',
|
|
name: '成人票',
|
|
salePrice: 160,
|
|
reservation: {
|
|
maxQuantity: 5,
|
|
},
|
|
ageRule: {
|
|
min: 18,
|
|
max: 59,
|
|
},
|
|
entitlements: [
|
|
{
|
|
id: 'adult-ticket',
|
|
name: '成人门票',
|
|
fulfillment: 'third_party',
|
|
quantity: 1,
|
|
unit: '张',
|
|
refundAllocation: 120,
|
|
},
|
|
{
|
|
id: 'shuttle',
|
|
name: '观光车票',
|
|
fulfillment: 'self',
|
|
quantity: 1,
|
|
unit: '张',
|
|
refundAllocation: 40,
|
|
},
|
|
],
|
|
}
|
|
|
|
const eligibleTraveler = {
|
|
id: 't-1',
|
|
name: '林晓',
|
|
idType: '身份证',
|
|
idNumber: '110101199001020010',
|
|
}
|
|
|
|
test('exposes the ticket views', () => {
|
|
assert.deepEqual(TICKET_VIEWS, {
|
|
HOME: 'home',
|
|
DETAIL: 'detail',
|
|
BOOKING: 'booking',
|
|
TRAVELERS: 'travelers',
|
|
PAID: 'paid',
|
|
ORDERS: 'orders',
|
|
ORDER_DETAIL: 'orderDetail',
|
|
REFUND: 'refundApply',
|
|
REFUND_RESULT: 'refundResult',
|
|
CREDENTIALS: 'credentials',
|
|
})
|
|
})
|
|
|
|
test('calculates age from a mainland ID at the visit date', () => {
|
|
assert.equal(calculateAge('110101196001010010', '2026-08-01'), 66)
|
|
assert.equal(calculateAge('110101201001020010', '2026-01-01'), 15)
|
|
assert.equal(calculateAge('not-an-id', '2026-08-01'), null)
|
|
})
|
|
|
|
test('checks traveler eligibility against an age rule', () => {
|
|
const traveler = {
|
|
idType: '身份证',
|
|
idNumber: '110101201001020010',
|
|
}
|
|
|
|
assert.equal(isTravelerEligible(traveler, { ageRule: { min: 6, max: 17 } }, '2026-08-01'), true)
|
|
assert.equal(isTravelerEligible(traveler, { ageRule: { min: 60, max: 120 } }, '2026-08-01'), false)
|
|
assert.equal(isTravelerEligible({ idType: '护照' }, {}, '2026-08-01'), true)
|
|
assert.equal(isTravelerEligible({ idType: '护照' }, { ageRule: { min: 6, max: 17 } }, '2026-08-01'), false)
|
|
assert.equal(isTravelerEligible({ idType: '身份证', idNumber: '110101202008010010' }, { ageRule: { min: 6, max: 17 } }, '2026-08-01'), true)
|
|
assert.equal(isTravelerEligible({ idType: '身份证', idNumber: '110101200908010010' }, { ageRule: { min: 6, max: 17 } }, '2026-08-01'), true)
|
|
})
|
|
|
|
test('calculates booking totals including selected insurance', () => {
|
|
assert.deepEqual(calculateBookingTotals(adultProduct, 2, true, 5), {
|
|
productAmount: 320,
|
|
insuranceAmount: 10,
|
|
paidAmount: 330,
|
|
})
|
|
})
|
|
|
|
test('validates booking draft fields and selected travelers in order', () => {
|
|
const draft = createBookingDraft(adultProduct)
|
|
const incompleteTraveler = { id: 't-1', idType: '身份证', idNumber: '' }
|
|
const childTraveler = { id: 't-1', name: '小林', idType: '身份证', idNumber: '110101201001020010' }
|
|
|
|
assert.deepEqual(draft, {
|
|
visitDate: '',
|
|
timeSlot: '',
|
|
quantity: 1,
|
|
travelerIds: [],
|
|
contactName: '',
|
|
phone: '',
|
|
insuranceSelected: false,
|
|
insurancePrice: 5,
|
|
maxQuantity: 5,
|
|
})
|
|
assert.equal(validateBooking({ product: adultProduct, draft, travelers: [] }), '请选择使用日期')
|
|
|
|
const completeDraft = {
|
|
...draft,
|
|
visitDate: '2026-08-01',
|
|
timeSlot: '08:00—09:30',
|
|
quantity: 1,
|
|
travelerIds: ['t-1'],
|
|
contactName: '林晓',
|
|
phone: '13800138000',
|
|
}
|
|
|
|
assert.equal(validateBooking({ product: adultProduct, draft: { ...completeDraft, timeSlot: '' }, travelers: [eligibleTraveler] }), '请选择使用场次')
|
|
assert.equal(validateBooking({ product: adultProduct, draft: { ...completeDraft, quantity: 0 }, travelers: [eligibleTraveler] }), '购买数量无效')
|
|
assert.equal(validateBooking({ product: adultProduct, draft: { ...completeDraft, travelerIds: [] }, travelers: [eligibleTraveler] }), '请选择足够数量的出行人')
|
|
assert.equal(validateBooking({ product: adultProduct, draft: { ...completeDraft, quantity: 2, travelerIds: ['t-1', 't-1'] }, travelers: [eligibleTraveler] }), '请选择足够数量的出行人')
|
|
assert.equal(validateBooking({ product: adultProduct, draft: completeDraft, travelers: [incompleteTraveler] }), '请完善出行人实名信息')
|
|
assert.equal(validateBooking({ product: adultProduct, draft: completeDraft, travelers: [childTraveler] }), '出行人年龄不符合票种要求')
|
|
assert.equal(validateBooking({ product: adultProduct, draft: { ...completeDraft, phone: '123' }, travelers: [eligibleTraveler] }), '请填写正确的联系人和手机号')
|
|
assert.equal(validateBooking({ product: adultProduct, draft: completeDraft, travelers: [eligibleTraveler] }), '')
|
|
})
|
|
|
|
test('creates a paid order with entitlements and credentials', () => {
|
|
const now = new Date('2026-07-30T10:00:00+08:00')
|
|
const order = createPaidOrder({
|
|
product: adultProduct,
|
|
draft: {
|
|
...createBookingDraft(adultProduct),
|
|
visitDate: '2026-08-01',
|
|
timeSlot: '08:00—09:30',
|
|
travelerIds: ['t-1'],
|
|
contactName: '林晓',
|
|
phone: '13800138000',
|
|
insuranceSelected: true,
|
|
},
|
|
travelers: [eligibleTraveler],
|
|
now,
|
|
})
|
|
|
|
assert.equal(order.status, 'paid')
|
|
assert.equal(order.paidAmount, 165)
|
|
assert.equal(order.entitlements.length, 2)
|
|
assert.equal(order.product, undefined)
|
|
assert.equal(order.productId, 'adult')
|
|
assert.equal(order.productName, '成人票')
|
|
assert.equal(order.id, `ORDER-${now.getTime()}`)
|
|
assert.equal(order.orderNo, order.id)
|
|
assert.equal(order.paidAt, '2026-07-30 10:00:00')
|
|
assert.deepEqual(order.gateCredential, { status: 'issued', qrToken: `GATE-${order.orderNo}` })
|
|
assert.deepEqual(order.selfCredential, { status: 'issued', qrToken: `SELF-${order.orderNo}` })
|
|
assert.deepEqual(order.insurance, { name: '出行意外险', price: 5, quantity: 1, status: 'active' })
|
|
assert.deepEqual(order.logs[0], { time: order.paidAt, text: '订单支付成功', operator: '系统' })
|
|
|
|
const secondTraveler = { ...eligibleTraveler, id: 't-2', idNumber: '110101199101020010' }
|
|
const thirdTraveler = { ...eligibleTraveler, id: 't-3', idNumber: '110101199201020010' }
|
|
const uninsuredOrder = createPaidOrder({
|
|
product: adultProduct,
|
|
draft: {
|
|
...createBookingDraft(adultProduct),
|
|
visitDate: '2026-08-01',
|
|
timeSlot: '08:00—09:30',
|
|
quantity: 2,
|
|
travelerIds: ['t-1', 't-2'],
|
|
contactName: '林晓',
|
|
phone: '13800138000',
|
|
},
|
|
travelers: [eligibleTraveler, secondTraveler],
|
|
now,
|
|
})
|
|
|
|
assert.equal(uninsuredOrder.travelers.length, 2)
|
|
assert.deepEqual(uninsuredOrder.entitlements.map((entitlement) => entitlement.quantity), [2, 2])
|
|
assert.deepEqual(uninsuredOrder.entitlements.map((entitlement) => entitlement.status), ['pending', 'pending'])
|
|
assert.equal(uninsuredOrder.insurance, null)
|
|
|
|
const insuredOrder = createPaidOrder({
|
|
product: adultProduct,
|
|
draft: {
|
|
...createBookingDraft(adultProduct),
|
|
visitDate: '2026-08-01',
|
|
timeSlot: '08:00—09:30',
|
|
quantity: 2,
|
|
travelerIds: ['t-1', 't-2', 't-3'],
|
|
contactName: '林晓',
|
|
phone: '13800138000',
|
|
insuranceSelected: true,
|
|
},
|
|
travelers: [eligibleTraveler, secondTraveler, thirdTraveler],
|
|
now,
|
|
})
|
|
|
|
assert.deepEqual(insuredOrder.travelers.map((traveler) => traveler.id), ['t-1', 't-2'])
|
|
assert.deepEqual(insuredOrder.entitlements.map((entitlement) => entitlement.quantity), [2, 2])
|
|
assert.deepEqual(insuredOrder.entitlements.map((entitlement) => entitlement.refundAllocation), [120, 40])
|
|
assert.deepEqual(insuredOrder.entitlements.map((entitlement) => entitlement.usedQuantity), [0, 0])
|
|
assert.equal(calculateRefundAmount(insuredOrder, [insuredOrder.entitlements[0].id]), 240)
|
|
assert.notEqual(insuredOrder.entitlements[0], adultProduct.entitlements[0])
|
|
assert.deepEqual(adultProduct.entitlements, [
|
|
{ id: 'adult-ticket', name: '成人门票', fulfillment: 'third_party', quantity: 1, unit: '张', refundAllocation: 120 },
|
|
{ id: 'shuttle', name: '观光车票', fulfillment: 'self', quantity: 1, unit: '张', refundAllocation: 40 },
|
|
])
|
|
assert.deepEqual(insuredOrder.insurance, { name: '出行意外险', price: 5, quantity: 2, status: 'active' })
|
|
})
|
|
|
|
test('applies refunds and pops view history without mutation', () => {
|
|
const order = createPaidOrder({
|
|
product: adultProduct,
|
|
draft: {
|
|
...createBookingDraft(adultProduct),
|
|
visitDate: '2026-08-01',
|
|
timeSlot: '08:00—09:30',
|
|
travelerIds: ['t-1'],
|
|
contactName: '林晓',
|
|
phone: '13800138000',
|
|
},
|
|
travelers: [eligibleTraveler],
|
|
})
|
|
const entitlementId = order.entitlements[0].id
|
|
const history = ['home', 'detail', 'booking']
|
|
|
|
assert.equal(calculateRefundAmount(order, [entitlementId]), 120)
|
|
|
|
const result = applyRefund(order, [entitlementId], '行程有变', new Date('2026-07-30T10:01:00+08:00'))
|
|
assert.equal(result.order.status, 'refunding')
|
|
assert.equal(result.refund.amount, 120)
|
|
assert.equal(result.order.entitlements[0].status, 'refunding')
|
|
assert.deepEqual(result.order.logs.at(-1), { time: '2026-07-30 10:01:00', text: '已申请退款', operator: '用户' })
|
|
const repeatedRefund = applyRefund(result.order, [entitlementId, 'missing-entitlement'], '重复申请')
|
|
assert.equal(repeatedRefund.refund.amount, 0)
|
|
assert.deepEqual(repeatedRefund.refund.entitlementIds, [])
|
|
assert.deepEqual(popViewHistory(history), { view: 'booking', history: ['home', 'detail'] })
|
|
assert.deepEqual(history, ['home', 'detail', 'booking'])
|
|
assert.deepEqual(popViewHistory([]), { view: 'home', history: [] })
|
|
})
|