| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- /**
- * Payment Service 单元测试
- * 测试订单创建、支付回调、会员激活
- */
- import { describe, it, expect, beforeEach, vi } from 'vitest';
- import { generateOrderNo } from '@/modules/payment/payment.service';
- // 直接 Mock prisma 模型
- const mockOrderFindFirst = vi.fn();
- const mockOrderFindUnique = vi.fn();
- const mockOrderUpdate = vi.fn();
- const mockOrderCreate = vi.fn();
- const mockUserUpdate = vi.fn();
- const mockUserFindUnique = vi.fn();
- const mockPlanFindUnique = vi.fn();
- const mockSubscriptionFindFirst = vi.fn();
- const mockSubscriptionCreate = vi.fn();
- const mockSubscriptionUpdate = vi.fn();
- const mockTokenBalanceFindUnique = vi.fn();
- const mockTokenBalanceUpdate = vi.fn();
- const mockTokenBalanceCreate = vi.fn();
- vi.mock('@/models', () => ({
- prisma: {
- order: {
- findFirst: (...args: any[]) => mockOrderFindFirst(...args),
- findUnique: (...args: any[]) => mockOrderFindUnique(...args),
- update: (...args: any[]) => mockOrderUpdate(...args),
- create: (...args: any[]) => mockOrderCreate(...args),
- },
- user: {
- update: (...args: any[]) => mockUserUpdate(...args),
- findUnique: (...args: any[]) => mockUserFindUnique(...args),
- },
- subscriptionPlan: {
- findUnique: (...args: any[]) => mockPlanFindUnique(...args),
- },
- subscription: {
- findFirst: (...args: any[]) => mockSubscriptionFindFirst(...args),
- create: (...args: any[]) => mockSubscriptionCreate(...args),
- update: (...args: any[]) => mockSubscriptionUpdate(...args),
- },
- tokenBalance: {
- findUnique: (...args: any[]) => mockTokenBalanceFindUnique(...args),
- update: (...args: any[]) => mockTokenBalanceUpdate(...args),
- create: (...args: any[]) => mockTokenBalanceCreate(...args),
- },
- },
- }));
- describe('generateOrderNo', () => {
- it('生成格式正确的订单号', () => {
- const orderNo = generateOrderNo();
- expect(orderNo).toMatch(/^PAY\d{8}[A-Z0-9]{6}$/);
- });
- it('每次调用生成不同的订单号', () => {
- const orderNo1 = generateOrderNo();
- const orderNo2 = generateOrderNo();
- expect(orderNo1).not.toBe(orderNo2);
- });
- it('订单号包含当前日期', () => {
- const orderNo = generateOrderNo();
- const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
- expect(orderNo).toContain(today);
- });
- });
- describe('createPaymentOrder', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
- it('套餐不存在时抛出错误', async () => {
- const { createPaymentOrder } = await import('@/modules/payment/payment.service');
- mockPlanFindUnique.mockResolvedValue(null);
- await expect(
- createPaymentOrder(1, 999, 'mock')
- ).rejects.toThrow('套餐不存在');
- });
- it('成功创建 mock 订单', async () => {
- const { createPaymentOrder } = await import('@/modules/payment/payment.service');
- mockPlanFindUnique.mockResolvedValue({ id: 1, name: '专业版', priceMonthly: 59, priceYearly: 590 });
- mockOrderCreate.mockResolvedValue({ id: 1, status: 'pending' });
- const result = await createPaymentOrder(1, 1, 'mock');
- expect(result.planName).toBe('专业版');
- expect(result.amount).toBe(59);
- expect(result.orderNo).toBeDefined();
- });
- it('月付使用 monthly 价格', async () => {
- const { createPaymentOrder } = await import('@/modules/payment/payment.service');
- mockPlanFindUnique.mockResolvedValue({ id: 1, name: '专业版', priceMonthly: 59, priceYearly: 590 });
- mockOrderCreate.mockResolvedValue({ id: 1, status: 'pending' });
- const result = await createPaymentOrder(1, 1, 'mock', 'monthly');
- expect(result.amount).toBe(59);
- });
- it('年付使用 yearly 价格', async () => {
- const { createPaymentOrder } = await import('@/modules/payment/payment.service');
- mockPlanFindUnique.mockResolvedValue({ id: 1, name: '专业版', priceMonthly: 59, priceYearly: 590 });
- mockOrderCreate.mockResolvedValue({ id: 1, status: 'pending' });
- const result = await createPaymentOrder(1, 1, 'mock', 'yearly');
- expect(result.amount).toBe(590);
- });
- });
- describe('handlePaymentCallback', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
- it('订单不存在时抛出错误', async () => {
- const { handlePaymentCallback } = await import('@/modules/payment/payment.service');
- mockOrderFindFirst.mockResolvedValue(null);
- await expect(
- handlePaymentCallback('NONEXISTENT', 'PAY123', 'success')
- ).rejects.toThrow('订单不存在');
- });
- it('订单已支付时返回幂等成功', async () => {
- const { handlePaymentCallback } = await import('@/modules/payment/payment.service');
- mockOrderFindFirst.mockResolvedValue({
- id: 1,
- orderNo: 'PAY20260519000001',
- status: 'paid',
- userId: 1,
- });
- const result = await handlePaymentCallback('PAY20260519000001', 'PAY123', 'success');
- expect(result.success).toBe(true);
- expect(result.message).toContain('已支付');
- });
- it('支付失败时更新订单状态', async () => {
- const { handlePaymentCallback } = await import('@/modules/payment/payment.service');
- mockOrderFindFirst
- .mockResolvedValueOnce({
- id: 1,
- orderNo: 'PAY20260519000001',
- status: 'pending',
- userId: 1,
- })
- .mockResolvedValueOnce({
- id: 1,
- orderNo: 'PAY20260519000001',
- status: 'failed',
- userId: 1,
- });
- mockOrderUpdate.mockResolvedValue({ id: 1, status: 'failed' });
- const result = await handlePaymentCallback('PAY20260519000001', 'PAY123', 'failed');
- expect(result.success).toBe(false);
- expect(result.message).toContain('失败');
- });
- it('支付成功时调用 activateSubscription', async () => {
- const { handlePaymentCallback } = await import('@/modules/payment/payment.service');
- mockOrderFindFirst
- .mockResolvedValueOnce({
- id: 1,
- orderNo: 'PAY20260519000001',
- status: 'pending',
- userId: 1,
- planId: 2,
- productType: 'monthly',
- })
- .mockResolvedValueOnce({
- id: 1,
- orderNo: 'PAY20260519000001',
- status: 'paid',
- userId: 1,
- planId: 2,
- });
- mockOrderUpdate.mockResolvedValue({});
- mockPlanFindUnique.mockResolvedValue({ id: 2, level: 2, monthlyTokens: 15000 });
- mockSubscriptionFindFirst.mockResolvedValue(null);
- mockUserUpdate.mockResolvedValue({});
- mockTokenBalanceFindUnique.mockResolvedValue(null);
- mockTokenBalanceCreate.mockResolvedValue({});
- const result = await handlePaymentCallback('PAY20260519000001', 'PAY123', 'success');
- expect(result.success).toBe(true);
- expect(result.message).toContain('成功');
- });
- });
|