Files

114 lines
4.0 KiB
Go

package handler_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/wangjia/pay/config"
"github.com/wangjia/pay/internal/accounts"
"github.com/wangjia/pay/internal/gateway"
"github.com/wangjia/pay/internal/handler"
"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"
"github.com/wangjia/pay/internal/webhook"
)
// buildDevMockEngine 装配一个带 DevMockHandler 的最小 gin engine:与 main.go 的
// MOCK_CHANNEL_ENABLED 挂载点(POST /api/v2/dev/orders/:order_no/mark-paid)同形,
// 但走真实 webhook.Notifier + store.WebhookStore(而非 nopEnqueuer),这样才能断言
// mark-paid 触发的 webhook 出站 outbox 真的入队了一行——handler 包既有的
// buildEngine()/buildEngineWithStore() 都用 nopEnqueuer,查不到 outbox。
func buildDevMockEngine(t *testing.T) (*gin.Engine, *gateway.Gateway, *store.OrderStore, *store.WebhookStore) {
t.Helper()
gin.SetMode(gin.TestMode)
db := model.OpenTestDB(t)
orders := store.NewOrderStore(db)
refunds := store.NewRefundStore(db)
subs := store.NewSubscriptionStore(db)
chargebacks := store.NewChargebackStore(db)
webhookStore := store.NewWebhookStore(db)
preg := provider.NewRegistry()
preg.Register(fake.New())
areg := accounts.New([]config.AccountConfig{
{AccountID: "fake-a1", Channel: "fake", Region: "global", Enabled: true, Weight: 1},
})
picker := accounts.NewRouter(areg, nil, nil)
// bizConfig/orderPaid 只是 Notifier 投递(DeliverPending)才用得到;本测试只验证
// Enqueue 落 outbox 一行,不跑投递循环,给最简单的桩即可。
notifier := webhook.NewNotifier(webhookStore,
func(string) (config.BizSystemConfig, bool) { return config.BizSystemConfig{}, true },
func(string) (bool, error) { return true, nil },
)
g := gateway.New(orders, refunds, preg, picker, oneResolver{}, notifier, "global", subs, chargebacks)
devH := handler.NewDevMockHandler(db, g)
r := gin.New()
r.POST("/api/v2/dev/orders/:order_no/mark-paid", devH.MarkPaid)
return r, g, orders, webhookStore
}
// TestDevMockMarkPaid 覆盖 dev-only「模拟付款成功」端点的最小闭环:fake 渠道下单
// (带 biz_system,否则 Settle 不会入队 webhook,见 gateway/settle.go
// enqueuePaymentSucceeded)→ POST mark-paid → 200 + 订单翻 paid + webhook outbox
// 有一行 payment.succeeded 待投递。
func TestDevMockMarkPaid(t *testing.T) {
r, g, orders, webhookStore := buildDevMockEngine(t)
ctx := t.Context()
res, err := g.CreateOrder(ctx, gateway.CreateOrderInput{
SKU: "pro_year", Method: "fake", BizSystem: "pangolin", BizRef: "u-1",
})
if err != nil {
t.Fatalf("CreateOrder: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v2/dev/orders/"+res.OrderNo+"/mark-paid", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("mark-paid code=%d body=%s", w.Code, w.Body.String())
}
o, err := orders.GetOrder(res.OrderNo)
if err != nil {
t.Fatalf("GetOrder: %v", err)
}
if o.Status != model.OrderPaidV2 {
t.Fatalf("订单状态 = %v, want paid", o.Status)
}
rows, err := webhookStore.ListUndelivered(10)
if err != nil {
t.Fatalf("ListUndelivered: %v", err)
}
found := false
for _, row := range rows {
if row.OutTradeNo == res.OrderNo && row.EventType == "payment.succeeded" {
found = true
}
}
if !found {
t.Fatalf("webhook outbox 应有一行 %s/payment.succeeded 待投递, rows=%+v", res.OrderNo, rows)
}
}
// TestDevMockMarkPaidOrderNotFound 覆盖找不到订单(未走 fake 渠道下单/order_no 错)
// 时的 404 分支,不误判成 500。
func TestDevMockMarkPaidOrderNotFound(t *testing.T) {
r, _, _, _ := buildDevMockEngine(t)
req := httptest.NewRequest(http.MethodPost, "/api/v2/dev/orders/GHOST-ORDER/mark-paid", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Fatalf("未知订单 mark-paid code=%d, want 404, body=%s", w.Code, w.Body.String())
}
}