user_handler_batch_limits_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package admin
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/gin-gonic/gin"
  11. "github.com/stretchr/testify/require"
  12. )
  13. type batchLimitsAdminServiceStub struct {
  14. *stubAdminService
  15. calls []batchLimitsAdminServiceCall
  16. }
  17. type batchLimitsAdminServiceCall struct {
  18. userIDs []int64
  19. concurrency *int
  20. rpmLimit *int
  21. }
  22. func cloneIntPointer(value *int) *int {
  23. if value == nil {
  24. return nil
  25. }
  26. cloned := *value
  27. return &cloned
  28. }
  29. func (s *batchLimitsAdminServiceStub) BatchUpdateLimits(_ context.Context, userIDs []int64, concurrency, rpmLimit *int) (int, error) {
  30. s.calls = append(s.calls, batchLimitsAdminServiceCall{
  31. userIDs: append([]int64(nil), userIDs...),
  32. concurrency: cloneIntPointer(concurrency),
  33. rpmLimit: cloneIntPointer(rpmLimit),
  34. })
  35. return len(userIDs), nil
  36. }
  37. func setupBatchLimitsRouter(serviceStub service.AdminService) *gin.Engine {
  38. gin.SetMode(gin.TestMode)
  39. router := gin.New()
  40. handler := NewUserHandler(serviceStub, nil, nil, nil, nil, nil, nil)
  41. router.POST("/api/v1/admin/users/batch-limits", handler.BatchUpdateLimits)
  42. return router
  43. }
  44. func postBatchLimits(t *testing.T, router *gin.Engine, body []byte) *httptest.ResponseRecorder {
  45. t.Helper()
  46. recorder := httptest.NewRecorder()
  47. request := httptest.NewRequest(
  48. http.MethodPost,
  49. "/api/v1/admin/users/batch-limits",
  50. bytes.NewReader(body),
  51. )
  52. request.Header.Set("Content-Type", "application/json")
  53. router.ServeHTTP(recorder, request)
  54. return recorder
  55. }
  56. func TestUserHandlerBatchUpdateLimitsAcceptsPartialAndZeroValues(t *testing.T) {
  57. tests := []struct {
  58. name string
  59. body string
  60. expectedConcurrency *int
  61. expectedRPMLimit *int
  62. }{
  63. {name: "concurrency only", body: `{"user_ids":[1,2],"concurrency":10}`, expectedConcurrency: pointerTo(10)},
  64. {name: "both limits", body: `{"user_ids":[1,2],"concurrency":8,"rpm_limit":60}`, expectedConcurrency: pointerTo(8), expectedRPMLimit: pointerTo(60)},
  65. {name: "explicit zero", body: `{"user_ids":[1,2],"concurrency":0,"rpm_limit":0}`, expectedConcurrency: pointerTo(0), expectedRPMLimit: pointerTo(0)},
  66. }
  67. for _, test := range tests {
  68. t.Run(test.name, func(t *testing.T) {
  69. serviceStub := &batchLimitsAdminServiceStub{stubAdminService: newStubAdminService()}
  70. recorder := postBatchLimits(t, setupBatchLimitsRouter(serviceStub), []byte(test.body))
  71. require.Equal(t, http.StatusOK, recorder.Code)
  72. require.Len(t, serviceStub.calls, 1)
  73. require.Equal(t, []int64{1, 2}, serviceStub.calls[0].userIDs)
  74. require.Equal(t, test.expectedConcurrency, serviceStub.calls[0].concurrency)
  75. require.Equal(t, test.expectedRPMLimit, serviceStub.calls[0].rpmLimit)
  76. var response struct {
  77. Data struct {
  78. Affected int `json:"affected"`
  79. } `json:"data"`
  80. }
  81. require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response))
  82. require.Equal(t, 2, response.Data.Affected)
  83. })
  84. }
  85. }
  86. func TestUserHandlerBatchUpdateLimitsRejectsInvalidRequests(t *testing.T) {
  87. tooManyIDs := make([]int64, 501)
  88. for index := range tooManyIDs {
  89. tooManyIDs[index] = int64(index + 1)
  90. }
  91. tooManyBody, err := json.Marshal(map[string]any{"user_ids": tooManyIDs, "rpm_limit": 10})
  92. require.NoError(t, err)
  93. tests := []struct {
  94. name string
  95. body []byte
  96. }{
  97. {name: "no limits", body: []byte(`{"user_ids":[1]}`)},
  98. {name: "invalid json", body: []byte(`{"user_ids":`)},
  99. {name: "missing user ids", body: []byte(`{"rpm_limit":10}`)},
  100. {name: "more than 500 ids", body: tooManyBody},
  101. }
  102. for _, test := range tests {
  103. t.Run(test.name, func(t *testing.T) {
  104. serviceStub := &batchLimitsAdminServiceStub{stubAdminService: newStubAdminService()}
  105. recorder := postBatchLimits(t, setupBatchLimitsRouter(serviceStub), test.body)
  106. require.Equal(t, http.StatusBadRequest, recorder.Code)
  107. require.Empty(t, serviceStub.calls)
  108. })
  109. }
  110. }
  111. func TestUserHandlerBatchUpdateLimitsAllUsesEveryListedUser(t *testing.T) {
  112. base := newStubAdminService()
  113. base.users = []service.User{{ID: 11}, {ID: 12}, {ID: 13}}
  114. serviceStub := &batchLimitsAdminServiceStub{stubAdminService: base}
  115. recorder := postBatchLimits(
  116. t,
  117. setupBatchLimitsRouter(serviceStub),
  118. []byte(`{"all":true,"user_ids":[999],"rpm_limit":0}`),
  119. )
  120. require.Equal(t, http.StatusOK, recorder.Code)
  121. require.Len(t, serviceStub.calls, 1)
  122. require.Equal(t, []int64{11, 12, 13}, serviceStub.calls[0].userIDs)
  123. require.Equal(t, 1, base.lastListUsers.calls)
  124. }
  125. func pointerTo(value int) *int {
  126. return &value
  127. }