import assert from 'node:assert/strict'; import { MarketingBenefitError, MarketingBenefitService } from '../dist/wallets/marketing-benefit-service.js'; const validFrom = new Date(Date.now() - 86_400_000); const validTo = new Date(Date.now() + 86_400_000); function createHarness(overrides = {}) { const state = { coupon: { id: '401', status: 'AVAILABLE', validFrom, validTo, remainingUses: 1, couponType: 'FULL_REDUCTION', discountAmountCents: 3000, timeMinutes: 0, minOrderAmountCents: 10000, storeId: '11', roomCategoryId: null, roomId: null, weekdaysJson: null, holidayOnly: 0, ...overrides.coupon }, packageHolding: { id: '501', status: 'ACTIVE', validFrom, validTo, remainingMinutes: 180, remainingAmountCents: 6000, storeId: '11', roomCategoryId: null, roomId: null, weekdaysJson: null, holidayOnly: 0, ...overrides.packageHolding }, usages: [], nextUsageId: 700 }; const connection = { async beginTransaction() {}, async commit() {}, async rollback() {}, release() {}, async execute(sql, params) { if (sql.includes('FROM qipai_benefit_usages') && sql.includes('client_request_id')) { return [state.usages.filter((usage) => usage.tenantId === String(params[0]) && usage.userId === String(params[1]) && usage.clientRequestId === params[2] ).map(toUsageRow), []]; } if (sql.includes('FROM qipai_coupon_grants')) { if (state.coupon && String(params[2]) === state.coupon.id) { return [[state.coupon], []]; } return [[], []]; } if (sql.includes('FROM qipai_package_holdings')) { if (state.packageHolding && String(params[2]) === state.packageHolding.id) { return [[state.packageHolding], []]; } return [[], []]; } if (sql.includes('INSERT INTO qipai_benefit_usages')) { const usage = { id: String(state.nextUsageId++), tenantId: String(params[0]), userId: String(params[1]), orderId: String(params[2]), benefitType: params[3], benefitId: String(params[4]), clientRequestId: params[5], discountCents: Number(params[6]), packageCreditCents: Number(params[7]), minutes: Number(params[8]), traceId: params[9], status: 'FROZEN' }; state.usages.push(usage); return [{ insertId: usage.id, affectedRows: 1 }, []]; } if (sql.includes("SET status = 'FROZEN'") && sql.includes('qipai_coupon_grants')) { state.coupon.status = 'FROZEN'; state.coupon.frozenOrderId = String(params[0]); return [{ affectedRows: 1 }, []]; } if (sql.includes("SET status = 'FROZEN'") && sql.includes('qipai_package_holdings')) { state.packageHolding.status = 'FROZEN'; state.packageHolding.frozenOrderId = String(params[0]); return [{ affectedRows: 1 }, []]; } if (sql.includes('FROM qipai_benefit_usages') && sql.includes('order_id')) { return [state.usages.filter((usage) => usage.tenantId === String(params[0]) && usage.userId === String(params[1]) && usage.orderId === String(params[2]) ).map(toUsageRow), []]; } if (sql.includes('UPDATE qipai_coupon_grants') && sql.includes("used_count = used_count + 1")) { state.coupon.remainingUses = Math.max(state.coupon.remainingUses - 1, 0); state.coupon.status = state.coupon.remainingUses === 0 ? 'USED' : 'AVAILABLE'; state.coupon.frozenOrderId = null; return [{ affectedRows: 1 }, []]; } if (sql.includes('UPDATE qipai_package_holdings') && sql.includes('remaining_minutes')) { const usage = state.usages.find((item) => item.benefitType === 'PACKAGE' && item.benefitId === state.packageHolding.id ); state.packageHolding.remainingMinutes -= usage.minutes; state.packageHolding.remainingAmountCents -= usage.packageCreditCents; state.packageHolding.status = state.packageHolding.remainingMinutes === 0 && state.packageHolding.remainingAmountCents === 0 ? 'EXHAUSTED' : 'ACTIVE'; state.packageHolding.frozenOrderId = null; return [{ affectedRows: 1 }, []]; } if (sql.includes('UPDATE qipai_benefit_usages')) { const usage = state.usages.find((item) => item.tenantId === String(params[1]) && item.id === String(params[2]) ); usage.status = params[0]; return [{ affectedRows: 1 }, []]; } if (sql.includes("SET status = 'AVAILABLE'")) { state.coupon.status = 'AVAILABLE'; state.coupon.frozenOrderId = null; return [{ affectedRows: 1 }, []]; } if (sql.includes("SET status = 'ACTIVE'")) { state.packageHolding.status = 'ACTIVE'; state.packageHolding.frozenOrderId = null; return [{ affectedRows: 1 }, []]; } throw new Error(`Unexpected SQL: ${sql}`); } }; const pool = { async getConnection() { return connection; } }; return { service: new MarketingBenefitService(pool), state }; } function toUsageRow(usage) { return { id: usage.id, benefitType: usage.benefitType, benefitId: usage.benefitId, status: usage.status, discountCents: usage.discountCents, packageCreditCents: usage.packageCreditCents, minutes: usage.minutes }; } const baseInput = { tenantId: '7', userId: '21', orderId: '31', storeId: '11', orderAmountCents: 12000, orderStartAt: new Date(Date.now() + 3_600_000), couponGrantId: '401', packageHoldingId: '501', packageMinutes: 60, packageCreditCents: 2000, clientRequestId: 'benefit-001', traceId: 'benefit-test' }; { const { service, state } = createHarness(); const reserved = await service.reserveForOrder(baseInput); assert.equal(reserved.status, 'FROZEN'); assert.equal(reserved.discountCents, 3000); assert.equal(reserved.packageCreditCents, 2000); assert.equal(reserved.minutes, 60); assert.equal(state.coupon.status, 'FROZEN'); assert.equal(state.packageHolding.status, 'FROZEN'); const duplicate = await service.reserveForOrder(baseInput); assert.equal(duplicate.idempotent, true); assert.equal(state.usages.length, 2); const confirmed = await service.confirmReserved({ tenantId: '7', userId: '21', orderId: '31', traceId: 'benefit-test-confirm' }); assert.equal(confirmed.status, 'CONFIRMED'); assert.equal(state.coupon.status, 'USED'); assert.equal(state.packageHolding.status, 'ACTIVE'); assert.equal(state.packageHolding.remainingMinutes, 120); assert.equal(state.packageHolding.remainingAmountCents, 4000); const confirmAgain = await service.confirmReserved({ tenantId: '7', userId: '21', orderId: '31', traceId: 'benefit-test-confirm' }); assert.equal(confirmAgain.idempotent, true); } { const { service } = createHarness({ coupon: { minOrderAmountCents: 20000 } }); await assert.rejects( () => service.reserveForOrder(baseInput), (error) => error instanceof MarketingBenefitError && error.code === 'BENEFIT_COUPON_MIN_AMOUNT_NOT_MET' ); } { const { service } = createHarness({ packageHolding: { remainingMinutes: 30 } }); await assert.rejects( () => service.reserveForOrder(baseInput), (error) => error instanceof MarketingBenefitError && error.code === 'BENEFIT_PACKAGE_MINUTES_INSUFFICIENT' ); } { const { service, state } = createHarness(); await service.reserveForOrder({ ...baseInput, clientRequestId: 'benefit-release' }); const released = await service.releaseReserved({ tenantId: '7', userId: '21', orderId: '31', traceId: 'benefit-test-release' }); assert.equal(released.status, 'RELEASED'); assert.equal(state.coupon.status, 'AVAILABLE'); assert.equal(state.packageHolding.status, 'ACTIVE'); assert.equal(state.packageHolding.remainingMinutes, 180); } console.log('PASS: M07-C benefits freeze, confirm, release and validate coupon/package usage.');