mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-03-30 00:31:24 +00:00
Add group_id and validity_days fields to CreateAndRedeemCodeRequest, enabling subscription-type redemption codes to be created and redeemed in a single API call. - Type defaults to "balance" when omitted for backward compatibility - Subscription type requires group_id (non-nil) and validity_days (>0) - Existing balance/concurrency callers are unaffected
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")
|
||
}
|