package oauth import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGenerateRandomBytes(t *testing.T) { t.Run("generates requested length", func(t *testing.T) { bytes, err := GenerateRandomBytes(32) require.NoError(t, err) assert.Equal(t, 32, len(bytes)) }) t.Run("generates different bytes each time", func(t *testing.T) { bytes1, _ := GenerateRandomBytes(16) bytes2, _ := GenerateRandomBytes(16) assert.NotEqual(t, bytes1, bytes2) }) } func TestGenerateState(t *testing.T) { t.Run("generates non-empty state", func(t *testing.T) { state, err := GenerateState() require.NoError(t, err) assert.NotEmpty(t, state) }) t.Run("generates unique states", func(t *testing.T) { state1, _ := GenerateState() state2, _ := GenerateState() assert.NotEqual(t, state1, state2) }) t.Run("generates URL-safe base64", func(t *testing.T) { state, _ := GenerateState() // Should not contain padding assert.NotContains(t, state, "=") }) } func TestGenerateSessionID(t *testing.T) { t.Run("generates hex string", func(t *testing.T) { sessionID, err := GenerateSessionID() require.NoError(t, err) assert.NotEmpty(t, sessionID) // Should be 32 hex chars (16 bytes * 2) assert.Equal(t, 32, len(sessionID)) }) t.Run("generates unique IDs", func(t *testing.T) { id1, _ := GenerateSessionID() id2, _ := GenerateSessionID() assert.NotEqual(t, id1, id2) }) } func TestGenerateCodeVerifier(t *testing.T) { t.Run("generates verifier", func(t *testing.T) { verifier, err := GenerateCodeVerifier() require.NoError(t, err) assert.NotEmpty(t, verifier) }) t.Run("generates unique verifiers", func(t *testing.T) { v1, _ := GenerateCodeVerifier() v2, _ := GenerateCodeVerifier() assert.NotEqual(t, v1, v2) }) } func TestGenerateCodeChallenge(t *testing.T) { tests := []struct { name string verifier string }{ {"simple verifier", "test_verifier_123"}, {"empty string", ""}, {"long verifier", "a_very_long_verifier_string_for_testing_purposes_only"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { challenge := GenerateCodeChallenge(tt.verifier) assert.NotEmpty(t, challenge) assert.NotContains(t, challenge, "=") // No padding }) } t.Run("deterministic for same input", func(t *testing.T) { verifier := "test_verifier" c1 := GenerateCodeChallenge(verifier) c2 := GenerateCodeChallenge(verifier) assert.Equal(t, c1, c2) }) } func TestBase64URLEncode(t *testing.T) { tests := []struct { input []byte expected string }{ {[]byte("hello"), "aGVsbG8"}, {[]byte("test+123"), "dGVzdCsxMjM"}, {[]byte(""), ""}, } for _, tt := range tests { t.Run(string(tt.input), func(t *testing.T) { result := base64URLEncode(tt.input) assert.Equal(t, tt.expected, result) assert.NotContains(t, result, "=") }) } } func TestBuildAuthorizationURL(t *testing.T) { url := BuildAuthorizationURL("test_state", "test_challenge", ScopeOAuth) assert.Contains(t, url, AuthorizeURL) assert.Contains(t, url, "client_id="+ClientID) assert.Contains(t, url, "state=test_state") assert.Contains(t, url, "code_challenge=test_challenge") assert.Contains(t, url, "code_challenge_method=S256") assert.Contains(t, url, "response_type=code") } func TestConstants(t *testing.T) { assert.NotEmpty(t, ClientID) assert.NotEmpty(t, AuthorizeURL) assert.NotEmpty(t, TokenURL) assert.NotEmpty(t, RedirectURI) assert.NotEmpty(t, ScopeOAuth) assert.NotEmpty(t, ScopeAPI) assert.NotEmpty(t, ScopeInference) } func TestTokenResponse(t *testing.T) { resp := TokenResponse{ AccessToken: "token123", TokenType: "Bearer", ExpiresIn: 3600, RefreshToken: "refresh456", Scope: "user:profile", } assert.Equal(t, "token123", resp.AccessToken) assert.Equal(t, "Bearer", resp.TokenType) assert.Equal(t, int64(3600), resp.ExpiresIn) } func TestOrgInfo(t *testing.T) { org := OrgInfo{UUID: "org-123"} assert.Equal(t, "org-123", org.UUID) } func TestAccountInfo(t *testing.T) { account := AccountInfo{ UUID: "acc-456", EmailAddress: "test@example.com", } assert.Equal(t, "acc-456", account.UUID) assert.Equal(t, "test@example.com", account.EmailAddress) }