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