account_handler_mixed_channel_test.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. package admin
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "github.com/Wei-Shaw/sub2api/internal/service"
  9. "github.com/gin-gonic/gin"
  10. "github.com/stretchr/testify/require"
  11. )
  12. func setupAccountMixedChannelRouter(adminSvc *stubAdminService) *gin.Engine {
  13. gin.SetMode(gin.TestMode)
  14. router := gin.New()
  15. accountHandler := NewAccountHandler(adminSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  16. router.POST("/api/v1/admin/accounts/check-mixed-channel", accountHandler.CheckMixedChannel)
  17. router.POST("/api/v1/admin/accounts", accountHandler.Create)
  18. router.PUT("/api/v1/admin/accounts/:id", accountHandler.Update)
  19. router.POST("/api/v1/admin/accounts/bulk-update", accountHandler.BulkUpdate)
  20. return router
  21. }
  22. func TestAccountHandlerCheckMixedChannelNoRisk(t *testing.T) {
  23. adminSvc := newStubAdminService()
  24. router := setupAccountMixedChannelRouter(adminSvc)
  25. body, _ := json.Marshal(map[string]any{
  26. "platform": "antigravity",
  27. "group_ids": []int64{27},
  28. })
  29. rec := httptest.NewRecorder()
  30. req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/check-mixed-channel", bytes.NewReader(body))
  31. req.Header.Set("Content-Type", "application/json")
  32. router.ServeHTTP(rec, req)
  33. require.Equal(t, http.StatusOK, rec.Code)
  34. var resp map[string]any
  35. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
  36. require.Equal(t, float64(0), resp["code"])
  37. data, ok := resp["data"].(map[string]any)
  38. require.True(t, ok)
  39. require.Equal(t, false, data["has_risk"])
  40. require.Equal(t, int64(0), adminSvc.lastMixedCheck.accountID)
  41. require.Equal(t, "antigravity", adminSvc.lastMixedCheck.platform)
  42. require.Equal(t, []int64{27}, adminSvc.lastMixedCheck.groupIDs)
  43. }
  44. func TestAccountHandlerCheckMixedChannelWithRisk(t *testing.T) {
  45. adminSvc := newStubAdminService()
  46. adminSvc.checkMixedErr = &service.MixedChannelError{
  47. GroupID: 27,
  48. GroupName: "claude-max",
  49. CurrentPlatform: "Antigravity",
  50. OtherPlatform: "Anthropic",
  51. }
  52. router := setupAccountMixedChannelRouter(adminSvc)
  53. body, _ := json.Marshal(map[string]any{
  54. "platform": "antigravity",
  55. "group_ids": []int64{27},
  56. "account_id": 99,
  57. })
  58. rec := httptest.NewRecorder()
  59. req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/check-mixed-channel", bytes.NewReader(body))
  60. req.Header.Set("Content-Type", "application/json")
  61. router.ServeHTTP(rec, req)
  62. require.Equal(t, http.StatusOK, rec.Code)
  63. var resp map[string]any
  64. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
  65. require.Equal(t, float64(0), resp["code"])
  66. data, ok := resp["data"].(map[string]any)
  67. require.True(t, ok)
  68. require.Equal(t, true, data["has_risk"])
  69. require.Equal(t, "mixed_channel_warning", data["error"])
  70. details, ok := data["details"].(map[string]any)
  71. require.True(t, ok)
  72. require.Equal(t, float64(27), details["group_id"])
  73. require.Equal(t, "claude-max", details["group_name"])
  74. require.Equal(t, "Antigravity", details["current_platform"])
  75. require.Equal(t, "Anthropic", details["other_platform"])
  76. require.Equal(t, int64(99), adminSvc.lastMixedCheck.accountID)
  77. }
  78. func TestAccountHandlerCreateMixedChannelConflictSimplifiedResponse(t *testing.T) {
  79. adminSvc := newStubAdminService()
  80. adminSvc.createAccountErr = &service.MixedChannelError{
  81. GroupID: 27,
  82. GroupName: "claude-max",
  83. CurrentPlatform: "Antigravity",
  84. OtherPlatform: "Anthropic",
  85. }
  86. router := setupAccountMixedChannelRouter(adminSvc)
  87. body, _ := json.Marshal(map[string]any{
  88. "name": "ag-oauth-1",
  89. "platform": "antigravity",
  90. "type": "oauth",
  91. "credentials": map[string]any{"refresh_token": "rt"},
  92. "group_ids": []int64{27},
  93. })
  94. rec := httptest.NewRecorder()
  95. req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts", bytes.NewReader(body))
  96. req.Header.Set("Content-Type", "application/json")
  97. router.ServeHTTP(rec, req)
  98. require.Equal(t, http.StatusConflict, rec.Code)
  99. var resp map[string]any
  100. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
  101. require.Equal(t, "mixed_channel_warning", resp["error"])
  102. require.Contains(t, resp["message"], "mixed_channel_warning")
  103. _, hasDetails := resp["details"]
  104. _, hasRequireConfirmation := resp["require_confirmation"]
  105. require.False(t, hasDetails)
  106. require.False(t, hasRequireConfirmation)
  107. }
  108. func TestAccountHandlerUpdateMixedChannelConflictSimplifiedResponse(t *testing.T) {
  109. adminSvc := newStubAdminService()
  110. adminSvc.updateAccountErr = &service.MixedChannelError{
  111. GroupID: 27,
  112. GroupName: "claude-max",
  113. CurrentPlatform: "Antigravity",
  114. OtherPlatform: "Anthropic",
  115. }
  116. router := setupAccountMixedChannelRouter(adminSvc)
  117. body, _ := json.Marshal(map[string]any{
  118. "group_ids": []int64{27},
  119. })
  120. rec := httptest.NewRecorder()
  121. req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/accounts/3", bytes.NewReader(body))
  122. req.Header.Set("Content-Type", "application/json")
  123. router.ServeHTTP(rec, req)
  124. require.Equal(t, http.StatusConflict, rec.Code)
  125. var resp map[string]any
  126. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
  127. require.Equal(t, "mixed_channel_warning", resp["error"])
  128. require.Contains(t, resp["message"], "mixed_channel_warning")
  129. _, hasDetails := resp["details"]
  130. _, hasRequireConfirmation := resp["require_confirmation"]
  131. require.False(t, hasDetails)
  132. require.False(t, hasRequireConfirmation)
  133. }
  134. func TestAccountHandlerUpdateMapsUpstreamBillingRateSyncSettings(t *testing.T) {
  135. adminSvc := newStubAdminService()
  136. router := setupAccountMixedChannelRouter(adminSvc)
  137. body, _ := json.Marshal(map[string]any{
  138. "name": "gemini-key",
  139. "upstream_billing_probe_enabled": true,
  140. "upstream_billing_rate_sync_enabled": true,
  141. })
  142. rec := httptest.NewRecorder()
  143. req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/accounts/42", bytes.NewReader(body))
  144. req.Header.Set("Content-Type", "application/json")
  145. router.ServeHTTP(rec, req)
  146. require.Equal(t, http.StatusOK, rec.Code)
  147. require.NotNil(t, adminSvc.lastUpdateAccountInput)
  148. require.NotNil(t, adminSvc.lastUpdateAccountInput.ProbeEnabled)
  149. require.True(t, *adminSvc.lastUpdateAccountInput.ProbeEnabled)
  150. require.NotNil(t, adminSvc.lastUpdateAccountInput.RateSyncEnabled)
  151. require.True(t, *adminSvc.lastUpdateAccountInput.RateSyncEnabled)
  152. }
  153. func TestAccountHandlerBulkUpdateMixedChannelConflict(t *testing.T) {
  154. adminSvc := newStubAdminService()
  155. adminSvc.bulkUpdateAccountErr = &service.MixedChannelError{
  156. GroupID: 27,
  157. GroupName: "claude-max",
  158. CurrentPlatform: "Antigravity",
  159. OtherPlatform: "Anthropic",
  160. }
  161. router := setupAccountMixedChannelRouter(adminSvc)
  162. body, _ := json.Marshal(map[string]any{
  163. "account_ids": []int64{1, 2, 3},
  164. "group_ids": []int64{27},
  165. })
  166. rec := httptest.NewRecorder()
  167. req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
  168. req.Header.Set("Content-Type", "application/json")
  169. router.ServeHTTP(rec, req)
  170. require.Equal(t, http.StatusConflict, rec.Code)
  171. var resp map[string]any
  172. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
  173. require.Equal(t, "mixed_channel_warning", resp["error"])
  174. require.Contains(t, resp["message"], "claude-max")
  175. }
  176. func TestAccountHandlerBulkUpdateMixedChannelConfirmSkips(t *testing.T) {
  177. adminSvc := newStubAdminService()
  178. router := setupAccountMixedChannelRouter(adminSvc)
  179. body, _ := json.Marshal(map[string]any{
  180. "account_ids": []int64{1, 2},
  181. "group_ids": []int64{27},
  182. "confirm_mixed_channel_risk": true,
  183. })
  184. rec := httptest.NewRecorder()
  185. req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
  186. req.Header.Set("Content-Type", "application/json")
  187. router.ServeHTTP(rec, req)
  188. require.Equal(t, http.StatusOK, rec.Code)
  189. var resp map[string]any
  190. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
  191. require.Equal(t, float64(0), resp["code"])
  192. data, ok := resp["data"].(map[string]any)
  193. require.True(t, ok)
  194. require.Equal(t, float64(2), data["success"])
  195. require.Equal(t, float64(0), data["failed"])
  196. }
  197. func TestBulkUpdateAcceptsFilterTargetRequest(t *testing.T) {
  198. adminSvc := newStubAdminService()
  199. router := setupAccountMixedChannelRouter(adminSvc)
  200. body, _ := json.Marshal(map[string]any{
  201. "filters": map[string]any{
  202. "platform": "openai",
  203. "type": "oauth",
  204. "status": "active",
  205. "group": "12",
  206. "privacy_mode": "blocked",
  207. "search": "bulk-target",
  208. },
  209. "schedulable": true,
  210. })
  211. rec := httptest.NewRecorder()
  212. req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
  213. req.Header.Set("Content-Type", "application/json")
  214. router.ServeHTTP(rec, req)
  215. require.Equal(t, http.StatusOK, rec.Code)
  216. var resp map[string]any
  217. require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
  218. require.Equal(t, float64(0), resp["code"])
  219. }
  220. func TestBulkUpdateAcceptsDedicatedUpstreamBillingProbeSetting(t *testing.T) {
  221. adminSvc := newStubAdminService()
  222. router := setupAccountMixedChannelRouter(adminSvc)
  223. body, _ := json.Marshal(map[string]any{
  224. "account_ids": []int64{1, 2},
  225. "upstream_billing_probe_enabled": false,
  226. })
  227. rec := httptest.NewRecorder()
  228. req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
  229. req.Header.Set("Content-Type", "application/json")
  230. router.ServeHTTP(rec, req)
  231. require.Equal(t, http.StatusOK, rec.Code)
  232. require.NotNil(t, adminSvc.lastBulkUpdateAccountInput)
  233. require.NotNil(t, adminSvc.lastBulkUpdateAccountInput.ProbeEnabled)
  234. require.False(t, *adminSvc.lastBulkUpdateAccountInput.ProbeEnabled)
  235. }