global_pricing_handler_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package admin
  2. import (
  3. "context"
  4. "database/sql"
  5. "encoding/json"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "testing"
  10. "github.com/Wei-Shaw/sub2api/internal/service"
  11. "github.com/gin-gonic/gin"
  12. "github.com/stretchr/testify/require"
  13. )
  14. type stubGlobalPricingRepo struct {
  15. rows []service.GlobalModelPrice
  16. next int64
  17. }
  18. func (s *stubGlobalPricingRepo) ListEnabled(context.Context) ([]service.GlobalModelPrice, error) {
  19. out := make([]service.GlobalModelPrice, 0)
  20. for _, r := range s.rows {
  21. if r.Enabled {
  22. out = append(out, r)
  23. }
  24. }
  25. return out, nil
  26. }
  27. func (s *stubGlobalPricingRepo) ListAll(context.Context) ([]service.GlobalModelPrice, error) {
  28. return append([]service.GlobalModelPrice(nil), s.rows...), nil
  29. }
  30. func (s *stubGlobalPricingRepo) Create(_ context.Context, in service.GlobalModelPriceInput) (*service.GlobalModelPrice, error) {
  31. s.next++
  32. out := service.GlobalModelPrice{ID: s.next, ModelPattern: in.ModelPattern, BillingMode: in.BillingMode,
  33. InputPrice: in.InputPrice, OutputPrice: in.OutputPrice, Enabled: true}
  34. s.rows = append(s.rows, out)
  35. return &out, nil
  36. }
  37. func (s *stubGlobalPricingRepo) GetByID(_ context.Context, id int64) (*service.GlobalModelPrice, error) {
  38. for i := range s.rows {
  39. if s.rows[i].ID == id {
  40. cp := s.rows[i]
  41. return &cp, nil
  42. }
  43. }
  44. return nil, sql.ErrNoRows
  45. }
  46. func (s *stubGlobalPricingRepo) Update(_ context.Context, id int64, in service.GlobalModelPriceInput) (*service.GlobalModelPrice, error) {
  47. for i := range s.rows {
  48. if s.rows[i].ID == id {
  49. s.rows[i].ModelPattern = in.ModelPattern
  50. s.rows[i].Enabled = in.Enabled
  51. cp := s.rows[i]
  52. return &cp, nil
  53. }
  54. }
  55. return nil, sql.ErrNoRows
  56. }
  57. func (s *stubGlobalPricingRepo) Delete(_ context.Context, id int64) error {
  58. for i := range s.rows {
  59. if s.rows[i].ID == id {
  60. s.rows = append(s.rows[:i], s.rows[i+1:]...)
  61. return nil
  62. }
  63. }
  64. return sql.ErrNoRows
  65. }
  66. func (s *stubGlobalPricingRepo) SetEnabled(_ context.Context, id int64, enabled bool) error {
  67. for i := range s.rows {
  68. if s.rows[i].ID == id {
  69. s.rows[i].Enabled = enabled
  70. return nil
  71. }
  72. }
  73. return sql.ErrNoRows
  74. }
  75. func newGlobalPricingTestHandler() *GlobalPricingHandler {
  76. return NewGlobalPricingHandler(service.NewGlobalModelPricingService(&stubGlobalPricingRepo{}))
  77. }
  78. func serveGlobalPricing(h *GlobalPricingHandler, method, target, body string, params gin.Params) *httptest.ResponseRecorder {
  79. gin.SetMode(gin.TestMode)
  80. rec := httptest.NewRecorder()
  81. c, _ := gin.CreateTestContext(rec)
  82. var reader *strings.Reader
  83. if body == "" {
  84. reader = strings.NewReader("")
  85. } else {
  86. reader = strings.NewReader(body)
  87. }
  88. c.Request = httptest.NewRequest(method, target, reader)
  89. c.Request.Header.Set("Content-Type", "application/json")
  90. c.Params = params
  91. switch {
  92. case method == http.MethodGet:
  93. h.List(c)
  94. case method == http.MethodPost && strings.HasSuffix(target, "/enable"):
  95. h.SetEnabled(c)
  96. case method == http.MethodPost:
  97. h.Create(c)
  98. case method == http.MethodPut:
  99. h.Update(c)
  100. case method == http.MethodDelete:
  101. h.Delete(c)
  102. }
  103. return rec
  104. }
  105. func TestGlobalPricingHandlerListEmpty(t *testing.T) {
  106. rec := serveGlobalPricing(newGlobalPricingTestHandler(), http.MethodGet, "/api/v1/admin/global-pricing", "", nil)
  107. require.Equal(t, http.StatusOK, rec.Code)
  108. var resp struct {
  109. Code int `json:"code"`
  110. Data struct {
  111. Count int `json:"count"`
  112. } `json:"data"`
  113. }
  114. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
  115. require.Equal(t, 0, resp.Code)
  116. require.Equal(t, 0, resp.Data.Count)
  117. }
  118. func TestGlobalPricingHandlerCreateAndToggle(t *testing.T) {
  119. h := newGlobalPricingTestHandler()
  120. rec := serveGlobalPricing(h, http.MethodPost, "/api/v1/admin/global-pricing",
  121. `{"model_pattern":"gpt-5","billing_mode":"token","input_price":0.000001}`, nil)
  122. require.Equal(t, http.StatusOK, rec.Code)
  123. rec = serveGlobalPricing(h, http.MethodPost, "/api/v1/admin/global-pricing/1/enable",
  124. `{"enabled":false}`, gin.Params{{Key: "id", Value: "1"}})
  125. require.Equal(t, http.StatusOK, rec.Code)
  126. rec = serveGlobalPricing(h, http.MethodGet, "/api/v1/admin/global-pricing", "", nil)
  127. require.Equal(t, http.StatusOK, rec.Code)
  128. }
  129. func TestGlobalPricingHandlerRejectsInvalid(t *testing.T) {
  130. h := newGlobalPricingTestHandler()
  131. rec := serveGlobalPricing(h, http.MethodPost, "/api/v1/admin/global-pricing",
  132. `{"model_pattern":"*foo","billing_mode":"token"}`, nil)
  133. require.NotEqual(t, http.StatusOK, rec.Code)
  134. }
  135. func TestGlobalPricingHandlerSetEnabledRequiresFlag(t *testing.T) {
  136. h := newGlobalPricingTestHandler()
  137. rec := serveGlobalPricing(h, http.MethodPost, "/api/v1/admin/global-pricing/1/enable",
  138. `{}`, gin.Params{{Key: "id", Value: "1"}})
  139. require.Equal(t, http.StatusBadRequest, rec.Code)
  140. }
  141. func TestGlobalPricingHandlerNotFound(t *testing.T) {
  142. h := newGlobalPricingTestHandler()
  143. params := gin.Params{{Key: "id", Value: "999"}}
  144. rec := serveGlobalPricing(h, http.MethodPut, "/api/v1/admin/global-pricing/999",
  145. `{"model_pattern":"gpt-5"}`, params)
  146. require.Equal(t, http.StatusNotFound, rec.Code)
  147. rec = serveGlobalPricing(h, http.MethodDelete, "/api/v1/admin/global-pricing/999", "", params)
  148. require.Equal(t, http.StatusNotFound, rec.Code)
  149. }