grok_import_probe_handler_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. //go:build unit
  2. package admin
  3. import (
  4. "context"
  5. "errors"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "sync"
  10. "testing"
  11. "time"
  12. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  13. "github.com/Wei-Shaw/sub2api/internal/pkg/xai"
  14. "github.com/Wei-Shaw/sub2api/internal/service"
  15. "github.com/gin-gonic/gin"
  16. "github.com/stretchr/testify/require"
  17. )
  18. type grokImportAdminService struct {
  19. *stubAdminService
  20. mu sync.Mutex
  21. nextID int64
  22. }
  23. func newGrokImportAdminService() *grokImportAdminService {
  24. return &grokImportAdminService{
  25. stubAdminService: newStubAdminService(),
  26. nextID: 500,
  27. }
  28. }
  29. func (s *grokImportAdminService) CreateAccount(_ context.Context, input *service.CreateAccountInput) (*service.Account, error) {
  30. s.mu.Lock()
  31. s.nextID++
  32. id := s.nextID
  33. s.mu.Unlock()
  34. return &service.Account{
  35. ID: id,
  36. Name: input.Name,
  37. Platform: input.Platform,
  38. Type: input.Type,
  39. Credentials: input.Credentials,
  40. Extra: input.Extra,
  41. ProxyID: input.ProxyID,
  42. Concurrency: input.Concurrency,
  43. Status: service.StatusActive,
  44. Schedulable: true,
  45. CreatedAt: time.Now(),
  46. UpdatedAt: time.Now(),
  47. }, nil
  48. }
  49. type grokImportOAuthClientStub struct{}
  50. func (grokImportOAuthClientStub) ExchangeCode(context.Context, string, string, string, string, string) (*xai.TokenResponse, error) {
  51. return &xai.TokenResponse{AccessToken: "access-token", RefreshToken: "refresh-token", ExpiresIn: 3600}, nil
  52. }
  53. func (grokImportOAuthClientStub) RefreshToken(context.Context, string, string, string) (*xai.TokenResponse, error) {
  54. return &xai.TokenResponse{AccessToken: "access-token", RefreshToken: "refresh-token", ExpiresIn: 3600}, nil
  55. }
  56. func (grokImportOAuthClientStub) LoginWithPassword(context.Context, string, string, string) (*service.GrokPasswordLoginResult, error) {
  57. return nil, errors.New("unexpected password login")
  58. }
  59. func (grokImportOAuthClientStub) ConvertSSOToBuild(context.Context, string, string) (*xai.TokenResponse, error) {
  60. return &xai.TokenResponse{AccessToken: "access-token", RefreshToken: "refresh-token", ExpiresIn: 3600}, nil
  61. }
  62. func TestGrokSSOBatchImportKeepsCreatedAccountsWhenOneAutomaticProbeFails(t *testing.T) {
  63. gin.SetMode(gin.TestMode)
  64. adminService := newGrokImportAdminService()
  65. oauthService := service.NewGrokOAuthService(nil, grokImportOAuthClientStub{})
  66. defer oauthService.Stop()
  67. prober := newGrokImportProbeStub(3)
  68. prober.failures[502] = infraerrors.New(502, "GROK_TEST_PROBE_FAILED", "sensitive-upstream-body")
  69. handler := NewGrokOAuthHandler(oauthService, adminService, nil, nil)
  70. handler.importProber = prober
  71. router := gin.New()
  72. router.POST("/api/v1/admin/grok/sso-to-oauth", handler.CreateAccountsFromSSO)
  73. recorder := httptest.NewRecorder()
  74. request := httptest.NewRequest(
  75. http.MethodPost,
  76. "/api/v1/admin/grok/sso-to-oauth",
  77. strings.NewReader(`{"sso_tokens":["sso-one","sso-two","sso-three"]}`),
  78. )
  79. request.Header.Set("Content-Type", "application/json")
  80. router.ServeHTTP(recorder, request)
  81. require.Equal(t, http.StatusOK, recorder.Code)
  82. require.Contains(t, recorder.Body.String(), `"created"`)
  83. require.NotContains(t, recorder.Body.String(), `GROK_TEST_PROBE_FAILED`)
  84. for i := 0; i < 3; i++ {
  85. awaitGrokProbeSignal(t, prober.done)
  86. }
  87. calls, _, _ := prober.snapshot()
  88. require.Equal(t, map[int64]int{501: 1, 502: 1, 503: 1}, calls)
  89. }
  90. func TestAccountCreateWithoutAutomaticGrokProbeServiceStillSucceeds(t *testing.T) {
  91. gin.SetMode(gin.TestMode)
  92. handler := NewAccountHandler(
  93. newGrokImportAdminService(),
  94. nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  95. )
  96. router := gin.New()
  97. router.POST("/api/v1/admin/accounts", handler.Create)
  98. recorder := httptest.NewRecorder()
  99. request := httptest.NewRequest(
  100. http.MethodPost,
  101. "/api/v1/admin/accounts",
  102. strings.NewReader(`{"name":"grok-rt","platform":"grok","type":"oauth","credentials":{"refresh_token":"secret"}}`),
  103. )
  104. request.Header.Set("Content-Type", "application/json")
  105. router.ServeHTTP(recorder, request)
  106. require.Equal(t, http.StatusOK, recorder.Code)
  107. }