openai_oauth_service_state_test.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. package service
  2. import (
  3. "context"
  4. "errors"
  5. "sync/atomic"
  6. "testing"
  7. "time"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/openai"
  9. "github.com/stretchr/testify/require"
  10. )
  11. type openaiOAuthClientStateStub struct {
  12. exchangeCalled int32
  13. lastClientID string
  14. }
  15. func (s *openaiOAuthClientStateStub) ExchangeCode(ctx context.Context, code, codeVerifier, redirectURI, proxyURL, clientID string) (*openai.TokenResponse, error) {
  16. atomic.AddInt32(&s.exchangeCalled, 1)
  17. s.lastClientID = clientID
  18. return &openai.TokenResponse{
  19. AccessToken: "at",
  20. RefreshToken: "rt",
  21. ExpiresIn: 3600,
  22. }, nil
  23. }
  24. func (s *openaiOAuthClientStateStub) RefreshToken(ctx context.Context, refreshToken, proxyURL string) (*openai.TokenResponse, error) {
  25. return nil, errors.New("not implemented")
  26. }
  27. func (s *openaiOAuthClientStateStub) RefreshTokenWithClientID(ctx context.Context, refreshToken, proxyURL string, clientID string) (*openai.TokenResponse, error) {
  28. return s.RefreshToken(ctx, refreshToken, proxyURL)
  29. }
  30. func TestOpenAIOAuthService_ExchangeCode_StateRequired(t *testing.T) {
  31. client := &openaiOAuthClientStateStub{}
  32. svc := NewOpenAIOAuthService(nil, client)
  33. defer svc.Stop()
  34. svc.sessionStore.Set("sid", &openai.OAuthSession{
  35. State: "expected-state",
  36. CodeVerifier: "verifier",
  37. RedirectURI: openai.DefaultRedirectURI,
  38. CreatedAt: time.Now(),
  39. })
  40. _, err := svc.ExchangeCode(context.Background(), &OpenAIExchangeCodeInput{
  41. SessionID: "sid",
  42. Code: "auth-code",
  43. })
  44. require.Error(t, err)
  45. require.Contains(t, err.Error(), "oauth state is required")
  46. require.Equal(t, int32(0), atomic.LoadInt32(&client.exchangeCalled))
  47. }
  48. func TestOpenAIOAuthService_ExchangeCode_StateMismatch(t *testing.T) {
  49. client := &openaiOAuthClientStateStub{}
  50. svc := NewOpenAIOAuthService(nil, client)
  51. defer svc.Stop()
  52. svc.sessionStore.Set("sid", &openai.OAuthSession{
  53. State: "expected-state",
  54. CodeVerifier: "verifier",
  55. RedirectURI: openai.DefaultRedirectURI,
  56. CreatedAt: time.Now(),
  57. })
  58. _, err := svc.ExchangeCode(context.Background(), &OpenAIExchangeCodeInput{
  59. SessionID: "sid",
  60. Code: "auth-code",
  61. State: "wrong-state",
  62. })
  63. require.Error(t, err)
  64. require.Contains(t, err.Error(), "invalid oauth state")
  65. require.Equal(t, int32(0), atomic.LoadInt32(&client.exchangeCalled))
  66. }
  67. func TestOpenAIOAuthService_ExchangeCode_StateMatch(t *testing.T) {
  68. client := &openaiOAuthClientStateStub{}
  69. svc := NewOpenAIOAuthService(nil, client)
  70. defer svc.Stop()
  71. svc.sessionStore.Set("sid", &openai.OAuthSession{
  72. State: "expected-state",
  73. CodeVerifier: "verifier",
  74. RedirectURI: openai.DefaultRedirectURI,
  75. CreatedAt: time.Now(),
  76. })
  77. info, err := svc.ExchangeCode(context.Background(), &OpenAIExchangeCodeInput{
  78. SessionID: "sid",
  79. Code: "auth-code",
  80. State: "expected-state",
  81. })
  82. require.NoError(t, err)
  83. require.NotNil(t, info)
  84. require.Equal(t, "at", info.AccessToken)
  85. require.Equal(t, openai.ClientID, info.ClientID)
  86. require.Equal(t, openai.ClientID, client.lastClientID)
  87. require.Equal(t, int32(1), atomic.LoadInt32(&client.exchangeCalled))
  88. _, ok := svc.sessionStore.Get("sid")
  89. require.False(t, ok)
  90. }