Files
YGChatCS/src/pages-service/tickets/model.test.mjs

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: [] })
})