| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- //go:build unit
- package repository
- import (
- "context"
- "encoding/json"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "github.com/Wei-Shaw/sub2api/internal/pkg/xai"
- "github.com/stretchr/testify/require"
- )
- func TestGrokOAuthClientExchangeAndRefreshUseFormFields(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- require.Equal(t, http.MethodPost, r.Method)
- require.NoError(t, r.ParseForm())
- require.Equal(t, "client-id", r.Form.Get("client_id"))
- switch r.Form.Get("grant_type") {
- case "authorization_code":
- require.Equal(t, "auth-code", r.Form.Get("code"))
- require.Equal(t, "http://127.0.0.1:56121/callback", r.Form.Get("redirect_uri"))
- require.Equal(t, "verifier", r.Form.Get("code_verifier"))
- require.Empty(t, r.Form.Get("code_challenge"))
- require.Empty(t, r.Form.Get("code_challenge_method"))
- _ = json.NewEncoder(w).Encode(map[string]any{
- "access_token": "exchange-access",
- "refresh_token": "exchange-refresh",
- "token_type": "Bearer",
- "expires_in": 3600,
- "scope": "openid api:access",
- })
- case "refresh_token":
- require.Equal(t, "refresh-token", r.Form.Get("refresh_token"))
- _ = json.NewEncoder(w).Encode(map[string]any{
- "access_token": "refresh-access",
- "refresh_token": "refresh-rotated",
- "token_type": "Bearer",
- "expires_in": 7200,
- })
- default:
- http.Error(w, "unexpected grant_type", http.StatusBadRequest)
- }
- }))
- defer server.Close()
- // Tests inject a loopback token endpoint; allowlist requires unsafe override.
- t.Setenv(xai.EnvAllowUnsafeURLOverrides, "true")
- t.Setenv(xai.EnvTokenURL, server.URL)
- client := NewGrokOAuthClient()
- exchanged, err := client.ExchangeCode(context.Background(), "auth-code", "verifier", "http://127.0.0.1:56121/callback", "", "client-id")
- require.NoError(t, err)
- require.Equal(t, "exchange-access", exchanged.AccessToken)
- require.Equal(t, "exchange-refresh", exchanged.RefreshToken)
- require.Equal(t, int64(3600), exchanged.ExpiresIn)
- require.Equal(t, "openid api:access", exchanged.Scope)
- refreshed, err := client.RefreshToken(context.Background(), "refresh-token", "", "client-id")
- require.NoError(t, err)
- require.Equal(t, "refresh-access", refreshed.AccessToken)
- require.Equal(t, "refresh-rotated", refreshed.RefreshToken)
- require.Equal(t, int64(7200), refreshed.ExpiresIn)
- }
- func TestGrokOAuthClientRefreshForbiddenClassifiesOnlyExplicitEntitlement(t *testing.T) {
- tests := []struct {
- name string
- body string
- wantReason string
- }{
- {name: "explicit entitlement", body: `{"error":"access_denied"}`, wantReason: "GROK_OAUTH_ENTITLEMENT_DENIED"},
- {name: "generic forbidden", body: `{"error":"forbidden"}`, wantReason: "GROK_OAUTH_TOKEN_REFRESH_FAILED"},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
- w.WriteHeader(http.StatusForbidden)
- _, _ = w.Write([]byte(tt.body))
- }))
- defer server.Close()
- t.Setenv(xai.EnvAllowUnsafeURLOverrides, "true")
- t.Setenv(xai.EnvTokenURL, server.URL)
- client := NewGrokOAuthClient()
- _, err := client.RefreshToken(context.Background(), "refresh-token", "", "client-id")
- require.Error(t, err)
- require.Contains(t, strings.ToUpper(err.Error()), tt.wantReason)
- })
- }
- }
- func TestGrokOAuthClientStatusErrorRedactsSensitiveResponseBody(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
- w.WriteHeader(http.StatusBadRequest)
- _, _ = w.Write([]byte(`{"error":"invalid_grant","access_token":"access-secret","refresh_token":"refresh-secret","code_verifier":"verifier-secret"}`))
- }))
- defer server.Close()
- t.Setenv(xai.EnvAllowUnsafeURLOverrides, "true")
- t.Setenv(xai.EnvTokenURL, server.URL)
- client := NewGrokOAuthClient()
- _, err := client.RefreshToken(context.Background(), "refresh-secret", "", "client-id")
- require.Error(t, err)
- errText := err.Error()
- require.Contains(t, errText, "status 400")
- require.Contains(t, errText, `\"refresh_token\":\"***\"`)
- require.NotContains(t, errText, "access-secret")
- require.NotContains(t, errText, "refresh-secret")
- require.NotContains(t, errText, "verifier-secret")
- }
- func TestGrokOAuthEntitlementDenialRequiresExplicitEvidence(t *testing.T) {
- t.Parallel()
- require.True(t, grokOAuthHasExplicitEntitlementDenial(`{"error":"access_denied"}`))
- require.True(t, grokOAuthHasExplicitEntitlementDenial(`{"code":"entitlement_denied"}`))
- require.True(t, grokOAuthHasExplicitEntitlementDenial(`{"message":"no active Grok subscription"}`))
- require.False(t, grokOAuthHasExplicitEntitlementDenial(`{"error":"forbidden","message":"request forbidden"}`))
- require.False(t, grokOAuthHasExplicitEntitlementDenial(`<html>403 Forbidden</html>`))
- require.False(t, grokOAuthHasExplicitEntitlementDenial(`{"error":"access_denied","message":"You have run out of credits"}`))
- require.False(t, grokOAuthHasExplicitEntitlementDenial(`{"code":"subscription_required","message":"included free usage exhausted"}`))
- }
- func TestNewGrokOAuthClient_UnvalidatedTokenURLFallsBackToDefault(t *testing.T) {
- // Without unsafe overrides, a random env TokenURL must not be used (fail-closed).
- t.Setenv(xai.EnvAllowUnsafeURLOverrides, "")
- t.Setenv(xai.EnvTokenURL, "https://evil.example/oauth/token")
- client := NewGrokOAuthClient().(*grokOAuthClient)
- require.Equal(t, xai.DefaultTokenURL, client.tokenURL)
- }
|