package service import ( "strings" "testing" "affiliate_dash/internal/model" ) func TestCreateRechargeRequiresVoucher(t *testing.T) { db := newServiceTestDB(t) merchantID, _ := seedFulfillmentMerchant(t, db, "merchant-recharge-novoucher", 0, -1, 100) svc := NewRechargeService(db, NewFulfillmentService(db, nil), t.TempDir()) if _, err := svc.CreateRecharge(CreateRechargeInput{ MerchantID: merchantID, AmountCNY: 10000, }); err == nil || !strings.Contains(err.Error(), "凭证") { t.Fatalf("empty voucher should be rejected, got %v", err) } if _, err := svc.CreateRecharge(CreateRechargeInput{ MerchantID: merchantID, AmountCNY: 100, Vouchers: []string{"/uploads/a.png"}, }); err == nil || !strings.Contains(err.Error(), "10 元") { t.Fatalf("amount below minimum should be rejected, got %v", err) } app, err := svc.CreateRecharge(CreateRechargeInput{ MerchantID: merchantID, ActorUserID: 7, AmountCNY: 50000, Vouchers: []string{"/uploads/a.png"}, Note: "对公转账", }) if err != nil { t.Fatalf("create recharge: %v", err) } if app.Status != model.RechargeStatusPending || app.PointsAmount != 50000 { t.Fatalf("unexpected application: %+v", app) } } func TestReviewRechargeCreditsWalletOnce(t *testing.T) { db := newServiceTestDB(t) merchantID, _ := seedFulfillmentMerchant(t, db, "merchant-recharge-review", 0, -1, 100) fulfillment := NewFulfillmentService(db, nil) svc := NewRechargeService(db, fulfillment, t.TempDir()) app, err := svc.CreateRecharge(CreateRechargeInput{ MerchantID: merchantID, ActorUserID: 7, AmountCNY: 100000, // 1000 元 = 100000 积分 Vouchers: []string{"/uploads/a.png"}, }) if err != nil { t.Fatalf("create recharge: %v", err) } reviewed, err := svc.ReviewRecharge(ReviewRechargeInput{ ApplicationID: app.ID, Approved: true, ReviewNote: "已核实到账", ActorUserID: 1, }) if err != nil { t.Fatalf("review approve: %v", err) } if reviewed.Status != model.RechargeStatusApproved || reviewed.PointsAmount != 100000 { t.Fatalf("unexpected reviewed app: %+v", reviewed) } wallet, err := fulfillment.GetWallet(merchantID) if err != nil { t.Fatalf("get wallet: %v", err) } if wallet.AvailableBalance != 100000 { t.Fatalf("approved recharge should credit wallet, got %d", wallet.AvailableBalance) } // 重复审核应被拒绝且不再入账 if _, err := svc.ReviewRecharge(ReviewRechargeInput{ ApplicationID: app.ID, Approved: true, ActorUserID: 1, }); err == nil || !strings.Contains(err.Error(), "已审核") { t.Fatalf("repeat review should be rejected, got %v", err) } wallet, _ = fulfillment.GetWallet(merchantID) if wallet.AvailableBalance != 100000 { t.Fatalf("repeat review should not credit again, got %d", wallet.AvailableBalance) } } func TestDetectImageExt(t *testing.T) { cases := []struct { name string data []byte want string }{ {"jpeg", []byte{0xFF, 0xD8, 0xFF, 0xE0, 0x00}, "jpg"}, {"png", []byte{0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A}, "png"}, {"gif", []byte{'G', 'I', 'F', '8', '9', 'a'}, "gif"}, {"webp", []byte{'R', 'I', 'F', 'F', 0x00, 0x00, 0x00, 0x00, 'W', 'E', 'B', 'P'}, "webp"}, {"fake", []byte{'<', 's', 'c', 'r', 'i', 'p', 't', '>'}, ""}, {"empty", []byte{}, ""}, } for _, tc := range cases { if got := detectImageExt(tc.data); got != tc.want { t.Fatalf("%s: detectImageExt = %q, want %q", tc.name, got, tc.want) } } } func TestReviewRechargeReject(t *testing.T) { db := newServiceTestDB(t) merchantID, _ := seedFulfillmentMerchant(t, db, "merchant-recharge-reject", 0, -1, 100) fulfillment := NewFulfillmentService(db, nil) svc := NewRechargeService(db, fulfillment, t.TempDir()) app, err := svc.CreateRecharge(CreateRechargeInput{ MerchantID: merchantID, ActorUserID: 7, AmountCNY: 10000, Vouchers: []string{"/uploads/a.png"}, }) if err != nil { t.Fatalf("create recharge: %v", err) } reviewed, err := svc.ReviewRecharge(ReviewRechargeInput{ ApplicationID: app.ID, Approved: false, ReviewNote: "凭证不清晰", ActorUserID: 1, }) if err != nil { t.Fatalf("review reject: %v", err) } if reviewed.Status != model.RechargeStatusRejected { t.Fatalf("expected rejected, got %+v", reviewed) } wallet, _ := fulfillment.GetWallet(merchantID) if wallet.AvailableBalance != 0 { t.Fatalf("rejected recharge should not credit, got %d", wallet.AvailableBalance) } }