130 lines
5.0 KiB
Go
130 lines
5.0 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/payments"
|
|
)
|
|
|
|
type paymentStub struct {
|
|
org, account, requestKey string
|
|
amount int64
|
|
notifications int
|
|
err error
|
|
}
|
|
|
|
func (s *paymentStub) Options() payments.Options {
|
|
return payments.Options{Enabled: true, PresetAmountsFen: []int64{10000}, MinAmountFen: 100, MaxAmountFen: 1000000}
|
|
}
|
|
func (s *paymentStub) Create(_ context.Context, org, account, key string, amount int64) (payments.Order, error) {
|
|
s.org, s.account, s.requestKey, s.amount = org, account, key, amount
|
|
return payments.Order{ID: "order1", OrganizationID: org, AmountFen: amount, Status: payments.StatusPending}, s.err
|
|
}
|
|
func (s *paymentStub) Get(_ context.Context, org, id string) (payments.Order, error) {
|
|
s.org = org
|
|
return payments.Order{ID: id, Status: payments.StatusPaid, CodeURL: "weixin://stale"}, s.err
|
|
}
|
|
func (s *paymentStub) List(_ context.Context, org string) ([]payments.Order, error) {
|
|
s.org = org
|
|
return nil, s.err
|
|
}
|
|
func (s *paymentStub) Notify(_ context.Context, _ *http.Request) error {
|
|
s.notifications++
|
|
return s.err
|
|
}
|
|
|
|
func paymentTestHandler(t *testing.T, role string, service PaymentService) http.Handler {
|
|
t.Helper()
|
|
auth, err := NewPlatformAuthorizer(AuthState{Required: true, Configured: true}, &fixedSessionResolver{session: identity.Session{AuthMode: identity.AuthModeAdmin, User: identity.User{ID: "account1", OrganizationID: "org1", Role: role}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return NewPaymentHandler(auth, service, "https://pay.example.test")
|
|
}
|
|
func paymentRequest(h http.Handler, method, path, body, origin string, authenticated bool) *httptest.ResponseRecorder {
|
|
r := httptest.NewRequest(method, path, strings.NewReader(body))
|
|
r.Header.Set("Content-Type", "application/json")
|
|
if origin != "" {
|
|
r.Header.Set("Origin", origin)
|
|
}
|
|
if authenticated {
|
|
r.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "valid"})
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
return w
|
|
}
|
|
func TestPaymentCreateUsesSessionAndRejectsMoneyTampering(t *testing.T) {
|
|
s := &paymentStub{}
|
|
h := paymentTestHandler(t, "organization_admin", s)
|
|
got := paymentRequest(h, "POST", "/api/payments/orders", `{"amountFen":12345,"requestKey":"retry-1"}`, "https://pay.example.test", true)
|
|
if got.Code != 200 || s.org != "org1" || s.account != "account1" || s.amount != 12345 {
|
|
t.Fatalf("status=%d scope=%s/%s amount=%d", got.Code, s.org, s.account, s.amount)
|
|
}
|
|
for _, body := range []string{
|
|
`{"amountFen":1.01,"requestKey":"x"}`, `{"amountFen":1e4,"requestKey":"x"}`,
|
|
`{"amountFen":100,"requestKey":"x","organizationId":"victim"}`,
|
|
`{"amountFen":100,"requestKey":"x"} {}`, `{"amountFen":100,"requestKey":"a/b"}`,
|
|
} {
|
|
if got := paymentRequest(h, "POST", "/api/payments/orders", body, "", true); got.Code != 400 {
|
|
t.Fatalf("body=%s status=%d", body, got.Code)
|
|
}
|
|
}
|
|
}
|
|
func TestPaymentAuthorizationAndOrigin(t *testing.T) {
|
|
for _, test := range []struct {
|
|
role, origin string
|
|
authenticated bool
|
|
want int
|
|
}{
|
|
{"user", "", true, 403}, {"organization_admin", "https://evil.example", true, 403},
|
|
{"organization_admin", "null", true, 403}, {"organization_admin", "", false, 401},
|
|
} {
|
|
s := &paymentStub{}
|
|
h := paymentTestHandler(t, test.role, s)
|
|
got := paymentRequest(h, "POST", "/api/payments/orders", `{"amountFen":100,"requestKey":"x"}`, test.origin, test.authenticated)
|
|
if got.Code != test.want || s.account != "" {
|
|
t.Fatalf("test=%+v status=%d", test, got.Code)
|
|
}
|
|
}
|
|
}
|
|
func TestPaymentCallbackUsesVerifierWithoutBrowserSessionAndRetriesFailures(t *testing.T) {
|
|
for _, test := range []struct {
|
|
err error
|
|
want int
|
|
}{
|
|
{nil, 204}, {payments.ErrVerification, 400}, {errors.New("secret error detail"), 500},
|
|
} {
|
|
s := &paymentStub{err: test.err}
|
|
h := paymentTestHandler(t, "user", s)
|
|
got := paymentRequest(h, "POST", "/api/payments/wechat/notify", `{}`, "", false)
|
|
if got.Code != test.want || s.notifications != 1 || strings.Contains(got.Body.String(), "secret") {
|
|
t.Fatalf("status=%d body=%s", got.Code, got.Body.String())
|
|
}
|
|
}
|
|
}
|
|
func TestPaymentDisabledOptionsAndScopedReads(t *testing.T) {
|
|
h := paymentTestHandler(t, "organization_admin", nil)
|
|
got := paymentRequest(h, "GET", "/api/payments/options", "", "", true)
|
|
if got.Code != 200 || !strings.Contains(got.Body.String(), `"enabled":false`) || got.Header().Get("Cache-Control") != "no-store" {
|
|
t.Fatal(got.Body.String())
|
|
}
|
|
s := &paymentStub{}
|
|
h = paymentTestHandler(t, "organization_admin", s)
|
|
got = paymentRequest(h, "GET", "/api/payments/orders/order1?organizationId=victim", "", "", true)
|
|
if got.Code != 200 || s.org != "org1" || strings.Contains(got.Body.String(), "weixin") {
|
|
t.Fatalf("status=%d body=%s", got.Code, got.Body.String())
|
|
}
|
|
h = paymentTestHandler(t, "user", s)
|
|
got = paymentRequest(h, "GET", "/api/payments/options", "", "", true)
|
|
if !strings.Contains(got.Body.String(), `"canRecharge":false`) {
|
|
t.Fatal(got.Body.String())
|
|
}
|
|
}
|