channel_monitor_feature_gate_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package routes
  2. import (
  3. "context"
  4. "net/http"
  5. "net/http/httptest"
  6. "testing"
  7. "github.com/Wei-Shaw/sub2api/internal/config"
  8. "github.com/Wei-Shaw/sub2api/internal/service"
  9. "github.com/gin-gonic/gin"
  10. "github.com/stretchr/testify/require"
  11. )
  12. // channelMonitorRouteSettingRepoStub is a minimal SettingRepository for route guards.
  13. type channelMonitorRouteSettingRepoStub struct {
  14. values map[string]string
  15. }
  16. func (s *channelMonitorRouteSettingRepoStub) Get(context.Context, string) (*service.Setting, error) {
  17. panic("unexpected Get call")
  18. }
  19. func (s *channelMonitorRouteSettingRepoStub) GetValue(_ context.Context, key string) (string, error) {
  20. return s.values[key], nil
  21. }
  22. func (s *channelMonitorRouteSettingRepoStub) Set(context.Context, string, string) error {
  23. panic("unexpected Set call")
  24. }
  25. func (s *channelMonitorRouteSettingRepoStub) GetMultiple(_ context.Context, keys []string) (map[string]string, error) {
  26. out := make(map[string]string, len(keys))
  27. for _, key := range keys {
  28. if value, ok := s.values[key]; ok {
  29. out[key] = value
  30. }
  31. }
  32. return out, nil
  33. }
  34. func (s *channelMonitorRouteSettingRepoStub) SetMultiple(context.Context, map[string]string) error {
  35. panic("unexpected SetMultiple call")
  36. }
  37. func (s *channelMonitorRouteSettingRepoStub) GetAll(context.Context) (map[string]string, error) {
  38. panic("unexpected GetAll call")
  39. }
  40. func (s *channelMonitorRouteSettingRepoStub) Delete(context.Context, string) error {
  41. panic("unexpected Delete call")
  42. }
  43. func newChannelMonitorRouteSettings(enabled bool) *service.SettingService {
  44. value := "false"
  45. if enabled {
  46. value = "true"
  47. }
  48. return service.NewSettingService(&channelMonitorRouteSettingRepoStub{
  49. values: map[string]string{
  50. service.SettingKeyChannelMonitorEnabled: value,
  51. },
  52. }, &config.Config{})
  53. }
  54. func newChannelMonitorModeSettings(enabled bool, mode string) *service.SettingService {
  55. enabledVal := "false"
  56. if enabled {
  57. enabledVal = "true"
  58. }
  59. return service.NewSettingService(&channelMonitorRouteSettingRepoStub{
  60. values: map[string]string{
  61. service.SettingKeyChannelMonitorEnabled: enabledVal,
  62. service.SettingKeyChannelMonitorMode: mode,
  63. },
  64. }, &config.Config{})
  65. }
  66. func TestChannelMonitorAdminFeatureGuard(t *testing.T) {
  67. tests := []struct {
  68. name string
  69. svc *service.SettingService
  70. wantStatus int
  71. }{
  72. {
  73. name: "nil setting service blocks",
  74. svc: nil,
  75. wantStatus: http.StatusForbidden,
  76. },
  77. {
  78. name: "disabled blocks",
  79. svc: newChannelMonitorRouteSettings(false),
  80. wantStatus: http.StatusForbidden,
  81. },
  82. {
  83. name: "enabled allows",
  84. svc: newChannelMonitorRouteSettings(true),
  85. wantStatus: http.StatusOK,
  86. },
  87. }
  88. for _, tt := range tests {
  89. t.Run(tt.name, func(t *testing.T) {
  90. gin.SetMode(gin.TestMode)
  91. router := gin.New()
  92. router.Use(channelMonitorAdminFeatureGuard(tt.svc))
  93. router.GET("/test", func(c *gin.Context) {
  94. c.JSON(http.StatusOK, gin.H{"ok": true})
  95. })
  96. rec := httptest.NewRecorder()
  97. req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/test", nil)
  98. router.ServeHTTP(rec, req)
  99. require.Equal(t, tt.wantStatus, rec.Code)
  100. if tt.wantStatus == http.StatusForbidden {
  101. require.Contains(t, rec.Body.String(), "CHANNEL_MONITOR_DISABLED")
  102. }
  103. })
  104. }
  105. }
  106. func TestChannelMonitorModeV2Guard(t *testing.T) {
  107. tests := []struct {
  108. name string
  109. svc *service.SettingService
  110. wantStatus int
  111. wantCode string
  112. }{
  113. {
  114. name: "nil blocks as disabled",
  115. svc: nil,
  116. wantStatus: http.StatusForbidden,
  117. wantCode: "CHANNEL_MONITOR_DISABLED",
  118. },
  119. {
  120. name: "feature off blocks",
  121. svc: newChannelMonitorModeSettings(false, service.ChannelMonitorModeV2),
  122. wantStatus: http.StatusForbidden,
  123. wantCode: "CHANNEL_MONITOR_DISABLED",
  124. },
  125. {
  126. name: "mode v1 blocks with mode mismatch",
  127. svc: newChannelMonitorModeSettings(true, service.ChannelMonitorModeV1),
  128. wantStatus: http.StatusForbidden,
  129. wantCode: "CHANNEL_MONITOR_MODE_MISMATCH",
  130. },
  131. {
  132. name: "mode v2 allows",
  133. svc: newChannelMonitorModeSettings(true, service.ChannelMonitorModeV2),
  134. wantStatus: http.StatusOK,
  135. },
  136. }
  137. for _, tt := range tests {
  138. t.Run(tt.name, func(t *testing.T) {
  139. gin.SetMode(gin.TestMode)
  140. router := gin.New()
  141. router.Use(channelMonitorModeV2Guard(tt.svc))
  142. router.GET("/test", func(c *gin.Context) {
  143. c.JSON(http.StatusOK, gin.H{"ok": true})
  144. })
  145. rec := httptest.NewRecorder()
  146. req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/test", nil)
  147. router.ServeHTTP(rec, req)
  148. require.Equal(t, tt.wantStatus, rec.Code)
  149. if tt.wantCode != "" {
  150. require.Contains(t, rec.Body.String(), tt.wantCode)
  151. }
  152. })
  153. }
  154. }