mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-04 07:22:13 +08:00
136 lines
4.0 KiB
Go
136 lines
4.0 KiB
Go
|
|
package admin
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bytes"
|
|||
|
|
"encoding/json"
|
|||
|
|
"net/http"
|
|||
|
|
"net/http/httptest"
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|||
|
|
"github.com/gin-gonic/gin"
|
|||
|
|
"github.com/stretchr/testify/assert"
|
|||
|
|
"github.com/stretchr/testify/require"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// newCreateAndRedeemHandler creates a RedeemHandler with a non-nil (but minimal)
|
|||
|
|
// RedeemService so that CreateAndRedeem's nil guard passes and we can test the
|
|||
|
|
// parameter-validation layer that runs before any service call.
|
|||
|
|
func newCreateAndRedeemHandler() *RedeemHandler {
|
|||
|
|
return &RedeemHandler{
|
|||
|
|
adminService: newStubAdminService(),
|
|||
|
|
redeemService: &service.RedeemService{}, // non-nil to pass nil guard
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// postCreateAndRedeemValidation calls CreateAndRedeem and returns the response
|
|||
|
|
// status code. For cases that pass validation and proceed into the service layer,
|
|||
|
|
// a panic may occur (because RedeemService internals are nil); this is expected
|
|||
|
|
// and treated as "validation passed" (returns 0 to indicate panic).
|
|||
|
|
func postCreateAndRedeemValidation(t *testing.T, handler *RedeemHandler, body any) (code int) {
|
|||
|
|
t.Helper()
|
|||
|
|
gin.SetMode(gin.TestMode)
|
|||
|
|
w := httptest.NewRecorder()
|
|||
|
|
c, _ := gin.CreateTestContext(w)
|
|||
|
|
|
|||
|
|
jsonBytes, err := json.Marshal(body)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
c.Request, _ = http.NewRequest(http.MethodPost, "/api/v1/admin/redeem-codes/create-and-redeem", bytes.NewReader(jsonBytes))
|
|||
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|||
|
|
|
|||
|
|
defer func() {
|
|||
|
|
if r := recover(); r != nil {
|
|||
|
|
// Panic means we passed validation and entered service layer (expected for minimal stub).
|
|||
|
|
code = 0
|
|||
|
|
}
|
|||
|
|
}()
|
|||
|
|
handler.CreateAndRedeem(c)
|
|||
|
|
return w.Code
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCreateAndRedeem_TypeDefaultsToBalance(t *testing.T) {
|
|||
|
|
// 不传 type 字段时应默认 balance,不触发 subscription 校验。
|
|||
|
|
// 验证通过后进入 service 层会 panic(返回 0),说明默认值生效。
|
|||
|
|
h := newCreateAndRedeemHandler()
|
|||
|
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
|||
|
|
"code": "test-balance-default",
|
|||
|
|
"value": 10.0,
|
|||
|
|
"user_id": 1,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
assert.NotEqual(t, http.StatusBadRequest, code,
|
|||
|
|
"omitting type should default to balance and pass validation")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCreateAndRedeem_SubscriptionRequiresGroupID(t *testing.T) {
|
|||
|
|
h := newCreateAndRedeemHandler()
|
|||
|
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
|||
|
|
"code": "test-sub-no-group",
|
|||
|
|
"type": "subscription",
|
|||
|
|
"value": 29.9,
|
|||
|
|
"user_id": 1,
|
|||
|
|
"validity_days": 30,
|
|||
|
|
// group_id 缺失
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
assert.Equal(t, http.StatusBadRequest, code)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCreateAndRedeem_SubscriptionRequiresPositiveValidityDays(t *testing.T) {
|
|||
|
|
groupID := int64(5)
|
|||
|
|
h := newCreateAndRedeemHandler()
|
|||
|
|
|
|||
|
|
cases := []struct {
|
|||
|
|
name string
|
|||
|
|
validityDays int
|
|||
|
|
}{
|
|||
|
|
{"zero", 0},
|
|||
|
|
{"negative", -1},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tc := range cases {
|
|||
|
|
t.Run(tc.name, func(t *testing.T) {
|
|||
|
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
|||
|
|
"code": "test-sub-bad-days-" + tc.name,
|
|||
|
|
"type": "subscription",
|
|||
|
|
"value": 29.9,
|
|||
|
|
"user_id": 1,
|
|||
|
|
"group_id": groupID,
|
|||
|
|
"validity_days": tc.validityDays,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
assert.Equal(t, http.StatusBadRequest, code)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCreateAndRedeem_SubscriptionValidParamsPassValidation(t *testing.T) {
|
|||
|
|
groupID := int64(5)
|
|||
|
|
h := newCreateAndRedeemHandler()
|
|||
|
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
|||
|
|
"code": "test-sub-valid",
|
|||
|
|
"type": "subscription",
|
|||
|
|
"value": 29.9,
|
|||
|
|
"user_id": 1,
|
|||
|
|
"group_id": groupID,
|
|||
|
|
"validity_days": 31,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
assert.NotEqual(t, http.StatusBadRequest, code,
|
|||
|
|
"valid subscription params should pass validation")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCreateAndRedeem_BalanceIgnoresSubscriptionFields(t *testing.T) {
|
|||
|
|
h := newCreateAndRedeemHandler()
|
|||
|
|
// balance 类型不传 group_id 和 validity_days,不应报 400
|
|||
|
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
|||
|
|
"code": "test-balance-no-extras",
|
|||
|
|
"type": "balance",
|
|||
|
|
"value": 50.0,
|
|||
|
|
"user_id": 1,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
assert.NotEqual(t, http.StatusBadRequest, code,
|
|||
|
|
"balance type should not require group_id or validity_days")
|
|||
|
|
}
|