Files
pay/internal/gateway/gateway_test.go
T

121 lines
3.9 KiB
Go

package gateway_test
import (
"context"
"errors"
"testing"
"github.com/wangjia/pay/config"
"github.com/wangjia/pay/internal/accounts"
"github.com/wangjia/pay/internal/gateway"
"github.com/wangjia/pay/internal/model"
"github.com/wangjia/pay/internal/provider"
"github.com/wangjia/pay/internal/provider/fake"
"github.com/wangjia/pay/internal/store"
)
// --- 测试替身 ---
type stubResolver struct{}
func (stubResolver) Resolve(sku string) (int64, string, string, string, error) {
if sku != "pro_year" {
return 0, "", "", "", gateway.ErrProductNotFound
}
return 29990000, "USDT", "Pro 年付", "pro_year", nil
}
type spyEnqueuer struct {
calls []map[string]any
failNext bool // 置 true 模拟 outbox 入队失败(settle 崩溃窗口测试用)
}
func (s *spyEnqueuer) Enqueue(outTradeNo, bizSystem, eventType string, data map[string]any) error {
if s.failNext {
s.failNext = false
return errors.New("outbox down")
}
s.calls = append(s.calls, data)
return nil
}
func newGateway(t *testing.T) (*gateway.Gateway, *fake.Provider, *spyEnqueuer, *store.OrderStore) {
t.Helper()
orders := store.NewOrderStore(model.OpenTestDB(t))
preg := provider.NewRegistry()
fp := fake.New()
preg.Register(fp)
// fake 注册在 method "fake";账户按 channel="fake" region="global" 配。
areg := accounts.New([]config.AccountConfig{
{AccountID: "fake-a1", Channel: "fake", Region: "global", Enabled: true, Weight: 1},
})
spy := &spyEnqueuer{}
g := gateway.New(orders, preg, areg, stubResolver{}, spy, "global")
return g, fp, spy, orders
}
func TestCreateOrderPipeline(t *testing.T) {
g, _, _, orders := newGateway(t)
res, err := g.CreateOrder(context.Background(), gateway.CreateOrderInput{
SKU: "pro_year", Method: "fake", BizSystem: "pangolin", BizRef: "u-1",
})
if err != nil {
t.Fatalf("CreateOrder: %v", err)
}
if res.OrderNo == "" || res.Session.RenderType != string(provider.RenderCryptoAddress) {
t.Fatalf("result = %+v", res)
}
if res.Session.Payload["currency"] != "USDT" {
t.Fatalf("payload = %+v", res.Session.Payload)
}
// 落库:order pending + attempt pending 带 provider_ref。
o, err := orders.GetOrder(res.OrderNo)
if err != nil || o.Status != model.OrderPendingV2 || o.AmountMinor != 29990000 {
t.Fatalf("order = %+v, %v", o, err)
}
atts, _ := orders.ListAttemptsByStatus(model.AttemptPending, 10)
if len(atts) != 1 || atts[0].Channel != "fake" || atts[0].AccountID != "fake-a1" {
t.Fatalf("attempt = %+v", atts)
}
}
func TestCreateOrderErrors(t *testing.T) {
g, _, _, _ := newGateway(t)
ctx := context.Background()
if _, err := g.CreateOrder(ctx, gateway.CreateOrderInput{SKU: "nope", Method: "fake"}); err != gateway.ErrProductNotFound {
t.Fatalf("未知 sku 应 ErrProductNotFound, got %v", err)
}
if _, err := g.CreateOrder(ctx, gateway.CreateOrderInput{SKU: "pro_year", Method: "ghost"}); err == nil {
t.Fatalf("未知 method 应报错")
}
}
func TestRetryAndCancel(t *testing.T) {
g, _, _, orders := newGateway(t)
ctx := context.Background()
res, _ := g.CreateOrder(ctx, gateway.CreateOrderInput{SKU: "pro_year", Method: "fake", BizSystem: "pangolin", BizRef: "u-1"})
// retry:弃旧尝试 + 建新尝试(order 仍 pending)。
r2, err := g.RetryOrder(ctx, res.OrderNo, "fake")
if err != nil || r2.OrderNo != res.OrderNo {
t.Fatalf("retry = %+v, %v", r2, err)
}
pend, _ := orders.ListAttemptsByStatus(model.AttemptPending, 10)
if len(pend) != 1 {
t.Fatalf("retry 后应恰 1 个 pending 尝试, got %d", len(pend))
}
// cancel pending → true;再 cancel → false。
ok, err := g.CancelOrder(res.OrderNo)
if err != nil || !ok {
t.Fatalf("cancel = %v, %v", ok, err)
}
if ok2, _ := g.CancelOrder(res.OrderNo); ok2 {
t.Fatalf("已取消单再取消应 false")
}
// canceled 单不可 retry。
if _, err := g.RetryOrder(ctx, res.OrderNo, "fake"); err != gateway.ErrOrderNotPending {
t.Fatalf("canceled 单 retry 应 ErrOrderNotPending, got %v", err)
}
}