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' test('provides coherent default visitor ticket data', async () => { const data = await import('./data.js') const { AVAILABLE_DATES, ENTITLEMENT_STATUS_META, GROUPS, INITIAL_TRAVELERS, INSURANCE, ORDER_STATUS_META, PLACEHOLDER_IMAGE, PRODUCT_IMAGES, PRODUCTS, REFUND_REASONS, SCENIC_HERO_IMAGE, SCENIC_SPOT } = data const { INITIAL_ORDERS } = data assert.equal(PLACEHOLDER_IMAGE, 'https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com/oneFeel/2082673896938569729.jpg') assert.deepEqual(SCENIC_SPOT, { id: 'libo-xiaoqikong', name: '荔波小七孔景区', level: '国家5A级景区', heritage: '世界自然遗产', address: '贵州省黔南布依族苗族自治州荔波县', phone: '0854-3619810', latitude: 25.2534, longitude: 107.7255, heroImage: SCENIC_HERO_IMAGE, }) assert.deepEqual(GROUPS.map(({ id, name, sortOrder, enabled, productIds }) => ({ id, name, sortOrder, enabled, productIds })), [ { id: 'ticket', name: '景点门票', sortOrder: 1, enabled: true, productIds: ['adult', 'elderly', 'minor', 'free'] }, { id: 'vip', name: 'VIP速通', sortOrder: 2, enabled: true, productIds: ['vip-fast'] }, { id: 'bus', name: '直通车', sortOrder: 3, enabled: true, productIds: ['direct-bus'] }, { id: 'package', name: '门票+观光车', sortOrder: 4, enabled: true, productIds: ['classic'] }, { id: 'tour', name: '一日游', sortOrder: 5, enabled: true, productIds: ['day-tour'] }, { id: 'play', name: '玩乐体验', sortOrder: 6, enabled: true, productIds: ['boat', 'guide'] }, ]) assert.deepEqual(PRODUCTS.map(({ id }) => id), ['adult', 'elderly', 'minor', 'free', 'vip-fast', 'direct-bus', 'classic', 'day-tour', 'boat', 'guide']) const productsById = new Map(PRODUCTS.map((product) => [product.id, product])) assert.ok(GROUPS.every((group) => group.productIds.every((productId) => productsById.has(productId)))) assert.deepEqual(GROUPS.flatMap((group) => group.productIds).sort(), PRODUCTS.map(({ id }) => id).sort()) const productKeys = ['id', 'name', 'shortTitle', 'category', 'description', 'salePrice', 'marketPrice', 'stock', 'sold', 'status', 'clientVisible', 'highlights', 'ageRule', 'included', 'excluded', 'detail', 'purchaseNotice', 'images', 'reservation', 'entitlements'] const reservationKeys = ['requiresDate', 'advanceDays', 'requiresTimeSlot', 'timeSlots', 'realNameRequired', 'idTypes', 'maxQuantity', 'contactPhoneRequired'] const entitlementKeys = ['id', 'name', 'fulfillment', 'quantity', 'unit', 'refundAllocation'] assert.ok(PRODUCTS.every((product) => productKeys.every((key) => key in product) && ['on_sale', 'draft'].includes(product.status) && Number.isFinite(product.salePrice) && Number.isFinite(product.stock) && Number.isFinite(product.sold) && product.images.length === 1 && reservationKeys.every((key) => key in product.reservation) && product.entitlements.length && product.entitlements.every((entitlement) => entitlementKeys.every((key) => key in entitlement)))) assert.deepEqual(PRODUCTS.filter(({ status, clientVisible }) => status !== 'on_sale' || clientVisible === false).map(({ id }) => id), ['guide']) const visibleProducts = PRODUCTS.filter(({ status, clientVisible }) => status === 'on_sale' && clientVisible !== false) assert.deepEqual(Object.keys(PRODUCT_IMAGES), visibleProducts.map(({ id }) => id)) assert.ok(SCENIC_HERO_IMAGE.endsWith('/hero-bridge.jpg')) assert.notEqual(SCENIC_HERO_IMAGE, PLACEHOLDER_IMAGE) assert.ok(visibleProducts.every((product) => product.images[0] === PRODUCT_IMAGES[product.id] && product.images[0].startsWith('https://one-feel-config-images-bucket.oss-cn-chengdu.aliyuncs.com/xiaoqi/tickets/'))) assert.equal(new Set([SCENIC_HERO_IMAGE, ...visibleProducts.map(({ images }) => images[0])]).size, 10) assert.equal(productsById.get('guide').images[0], PLACEHOLDER_IMAGE) assert.deepEqual(PRODUCTS.map(({ id, salePrice, marketPrice, sold }) => [id, salePrice, marketPrice, sold]), [ ['adult', 160, 160, 2386], ['elderly', 100, 100, 816], ['minor', 100, 100, 629], ['free', 40, 40, 346], ['vip-fast', 98, 98, 126], ['direct-bus', 68, 68, 92], ['classic', 188, 210, 184], ['day-tour', 188, 188, 68], ['boat', 50, 60, 326], ['guide', 68, 88, 0], ]) assert.deepEqual(productsById.get('elderly').ageRule, { min: 60, max: 999 }) assert.deepEqual(productsById.get('minor').ageRule, { min: 6, max: 17 }) assert.deepEqual(productsById.get('free').ageRule, { min: 0, max: 120 }) assert.deepEqual(productsById.get('adult').entitlements.map(({ fulfillment, refundAllocation }) => [fulfillment, refundAllocation]), [['third_party', 120], ['third_party', 40]]) assert.deepEqual(productsById.get('classic').entitlements.map(({ fulfillment, refundAllocation }) => [fulfillment, refundAllocation]), [['third_party', 120], ['third_party', 40], ['self', 28]]) for (const productId of ['vip-fast', 'day-tour', 'boat', 'guide']) assert.ok(productsById.get(productId).entitlements.every(({ fulfillment }) => fulfillment === 'self')) for (const productId of ['direct-bus', 'free', 'elderly', 'minor']) assert.ok(productsById.get(productId).entitlements.every(({ fulfillment }) => fulfillment === 'third_party')) const standardSlots = ['08:00—09:30', '09:30—11:30', '11:30—13:30', '13:30—15:00', '15:00—16:00'] for (const productId of ['adult', 'elderly', 'minor', 'free', 'classic']) assert.deepEqual(productsById.get(productId).reservation.timeSlots, standardSlots) assert.deepEqual(productsById.get('vip-fast').reservation.timeSlots, ['08:00—10:00', '10:00—12:00', '12:00—14:00', '14:00—16:00']) assert.deepEqual(productsById.get('direct-bus').reservation.timeSlots, ['06:30—07:00 去程', '18:00—18:30 返程']) assert.deepEqual(productsById.get('day-tour').reservation.timeSlots, ['08:00—08:30 集合', '09:00—09:30 集合']) assert.deepEqual(productsById.get('guide').reservation.timeSlots, ['09:00—10:30', '13:30—15:00']) assert.equal(productsById.get('boat').reservation.requiresTimeSlot, false) assert.deepEqual(productsById.get('boat').reservation.timeSlots, []) assert.deepEqual(AVAILABLE_DATES, [ { date: '2026-08-01', week: '周六', label: '8月1日', stock: 120 }, { date: '2026-08-02', week: '周日', label: '8月2日', stock: 96 }, { date: '2026-08-03', week: '周一', label: '8月3日', stock: 68 }, { date: '2026-08-04', week: '周二', label: '8月4日', stock: 0 }, { date: '2026-08-05', week: '周三', label: '8月5日', stock: 42 }, ]) assert.deepEqual(INSURANCE, { name: '出行意外险', price: 5, description: '最高10万元意外保障' }) assert.deepEqual(REFUND_REASONS, ['行程有变,无法按计划出行', '重复购买', '选错日期或票种', '其他原因']) assert.deepEqual(INITIAL_TRAVELERS, [ { id: 'traveler-lin-xiao', name: '林晓', idType: '身份证', idNumber: '110101199001020010', phone: '13800138000' }, { id: 'traveler-zhou-ning', name: '周宁', idType: '身份证', idNumber: '110101196001020017', phone: '13900139000' }, ]) assert.deepEqual(productsById.get('adult').reservation, { requiresDate: true, advanceDays: 30, requiresTimeSlot: true, timeSlots: standardSlots, realNameRequired: true, idTypes: ['身份证', '护照'], maxQuantity: 5, contactPhoneRequired: true }) assert.deepEqual(productsById.get('adult').highlights, ['官方', '可订明日', '随时退', '无需取票']) assert.deepEqual(productsById.get('classic').reservation, { requiresDate: true, advanceDays: 30, requiresTimeSlot: true, timeSlots: standardSlots, realNameRequired: true, idTypes: ['身份证', '护照'], maxQuantity: 5, contactPhoneRequired: true }) assert.deepEqual(productsById.get('classic').highlights, ['官方', '实名预约', '整包退', '双凭证']) assert.deepEqual(INITIAL_TRAVELERS[0], { id: 'traveler-lin-xiao', name: '林晓', idType: '身份证', idNumber: '110101199001020010', phone: '13800138000' }) assert.deepEqual( Object.fromEntries(Object.entries(ORDER_STATUS_META).map(([status, meta]) => [status, meta.label])), { pending_payment: '待支付', closed: '已关闭', paid: '待使用', fulfilling: '出票中', partial_used: '部分使用', completed: '已完成', refunding: '退款中', partial_refunded: '部分退款', refunded: '已退款', exception: '异常', }, ) assert.deepEqual( Object.fromEntries(Object.entries(ENTITLEMENT_STATUS_META).map(([status, meta]) => [status, meta.label])), { pending: '待使用', used: '已使用', refunding: '退款中', refunded: '已退款' }, ) assert.equal(INITIAL_ORDERS.length, 11) assert.deepEqual( INITIAL_ORDERS.map(({ orderNo, productName, status, amount }) => [orderNo, productName, status, amount]), [ ['LB202607260001', '小七孔经典畅游套餐', 'partial_used', 188], ['LB202607260002', '小七孔经典畅游套餐', 'paid', 376], ['LB202607260003', '荔波小七孔景区成人票', 'exception', 120], ['LB202607260004', '鸳鸯湖游船票', 'paid', 100], ['LB202607260005', '荔波小七孔景区成人票', 'fulfilling', 120], ['LB202607260006', '鸳鸯湖游船票', 'completed', 50], ['LB202607260007', '小七孔观光车票', 'refunding', 80], ['LB202607260008', '精品讲解服务', 'refunded', 68], ['LB202607260009', '小七孔经典畅游套餐', 'pending_payment', 188], ['LB202607260010', '荔波小七孔景区成人票', 'closed', 120], ['LB202607260011', '鸳鸯湖游船票', 'partial_refunded', 100], ], ) assert.deepEqual(new Set(INITIAL_ORDERS.map(({ status }) => status)), new Set(Object.keys(ORDER_STATUS_META))) for (const order of INITIAL_ORDERS) { assert.equal(order.paidAmount, order.amount) assert.equal(order.productAmount, order.amount) assert.equal(order.insuranceAmount, 0) assert.equal(order.travelers[0].name, order.customerName) assert.ok(order.entitlements.every((entitlement) => ['pending', 'used', 'refunding', 'refunded'].includes(entitlement.status))) } }) test('honors each product reservation requirement flag when validating bookings', async () => { const { PRODUCTS } = await import('./data.js') const boat = PRODUCTS.find(({ id }) => id === 'boat') const boatDraft = { ...createBookingDraft(boat), visitDate: '2026-08-01', contactName: '林晓', phone: '13800138000', } assert.equal(validateBooking({ product: boat, draft: boatDraft, travelers: [] }), '') const noRequirements = { ...boat, reservation: { ...boat.reservation, requiresDate: false, requiresTimeSlot: false, realNameRequired: false, contactPhoneRequired: false, }, } assert.equal(validateBooking({ product: noRequirements, draft: createBookingDraft(noRequirements), travelers: [] }), '') const timeRequired = { ...noRequirements, reservation: { ...noRequirements.reservation, requiresTimeSlot: true } } assert.equal(validateBooking({ product: timeRequired, draft: createBookingDraft(timeRequired), travelers: [] }), '请选择使用场次') const contactRequired = { ...noRequirements, reservation: { ...noRequirements.reservation, contactPhoneRequired: true } } assert.equal(validateBooking({ product: contactRequired, draft: createBookingDraft(contactRequired), travelers: [] }), '请填写正确的联系人和手机号') }) test('keeps ticket source prototypes, valid traveler IDs, and entitlement allocations aligned', async () => { const { INITIAL_TRAVELERS, PRODUCTS } = await import('./data.js') const productsById = new Map(PRODUCTS.map((product) => [product.id, product])) assert.equal(productsById.get('adult').ageRule, null) assert.deepEqual(productsById.get('adult').name, '荔波小七孔成人票+观光车') assert.deepEqual(productsById.get('elderly').name, '荔波小七孔老年人票+观光车') assert.deepEqual(productsById.get('minor').name, '荔波小七孔未成年票+观光车') assert.deepEqual(productsById.get('free').name, '荔波小七孔免门票+观光车') assert.deepEqual(productsById.get('classic').name, '小七孔经典畅游套餐') assert.deepEqual(productsById.get('boat').name, '鸳鸯湖游船票') assert.deepEqual(productsById.get('day-tour').name, '荔波小七孔精品一日游') assert.deepEqual(productsById.get('guide').name, '小七孔精品讲解服务') assert.deepEqual(productsById.get('direct-bus').name, '贵阳往返小七孔景区直通车') assert.deepEqual(productsById.get('elderly').reservation.idTypes, ['身份证']) assert.deepEqual(productsById.get('minor').reservation.idTypes, ['身份证']) assert.ok(PRODUCTS.filter((product) => product.ageRule).every((product) => product.reservation.idTypes.length === 1 && product.reservation.idTypes[0] === '身份证')) assert.ok(productsById.get('direct-bus').included.every((item) => item.includes('往返'))) assert.match(productsById.get('direct-bus').detail, /乘车凭证/) assert.match(productsById.get('direct-bus').purchaseNotice, /提前20分钟/) assert.deepEqual(productsById.get('adult').entitlements.map(({ name, fulfillment, refundAllocation }) => [name, fulfillment, refundAllocation]), [['小七孔成人门票', 'third_party', 120], ['小七孔观光车票', 'third_party', 40]]) assert.deepEqual(productsById.get('elderly').entitlements.map(({ refundAllocation }) => refundAllocation), [60, 40]) assert.deepEqual(productsById.get('minor').entitlements.map(({ refundAllocation }) => refundAllocation), [60, 40]) assert.deepEqual(productsById.get('free').entitlements.map(({ refundAllocation }) => refundAllocation), [0, 40]) assert.deepEqual(productsById.get('classic').entitlements.map(({ name, fulfillment, refundAllocation }) => [name, fulfillment, refundAllocation]), [['小七孔成人门票', 'third_party', 120], ['观光车票', 'third_party', 40], ['鸳鸯湖游船', 'self', 28]]) assert.ok(PRODUCTS.every((product) => product.entitlements.every((entitlement) => Number.isFinite(entitlement.quantity) && entitlement.quantity > 0 && Number.isFinite(entitlement.refundAllocation)) && product.entitlements.reduce((total, entitlement) => total + entitlement.refundAllocation, 0) === product.salePrice)) assert.deepEqual(INITIAL_TRAVELERS[1], { id: 'traveler-zhou-ning', name: '周宁', idType: '身份证', idNumber: '110101196001020017', phone: '13900139000' }) }) 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, maxQuantity: 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' }) const zeroQuantityOrder = createPaidOrder({ product: { ...adultProduct, entitlements: [{ ...adultProduct.entitlements[0], quantity: 0 }] }, draft: { ...createBookingDraft(adultProduct), quantity: 2 }, travelers: [], now, }) assert.equal(zeroQuantityOrder.entitlements[0].quantity, 0) }) 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'] const originalOrder = structuredClone(order) 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(order, originalOrder) assert.notEqual(result.order.entitlements, order.entitlements) assert.notEqual(result.order.logs, order.logs) 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.order, result.order) assert.equal(repeatedRefund.refund, null) const emptyRefund = applyRefund(order, [], '空申请') assert.equal(emptyRefund.order, order) assert.equal(emptyRefund.refund, null) assert.deepEqual(popViewHistory(history), { view: 'booking', history: ['home', 'detail'] }) assert.deepEqual(history, ['home', 'detail', 'booking']) assert.deepEqual(popViewHistory([]), { view: 'home', history: [] }) })