test: add oauth package tests

Add tests for OAuth helper functions:
- GenerateRandomBytes
- GenerateState
- GenerateSessionID
- GenerateCodeVerifier
- GenerateCodeChallenge
- base64URLEncode
- BuildAuthorizationURL
- Constants and types

Coverage: oauth 15.9% → 47.6%
This commit is contained in:
Your Name
2026-05-29 20:50:16 +08:00
parent abcbc4e58d
commit 65309b95e7

View File

@@ -1,43 +1,166 @@
package oauth
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSessionStore_Stop_Idempotent(t *testing.T) {
store := NewSessionStore()
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))
})
store.Stop()
store.Stop()
t.Run("generates different bytes each time", func(t *testing.T) {
bytes1, _ := GenerateRandomBytes(16)
bytes2, _ := GenerateRandomBytes(16)
assert.NotEqual(t, bytes1, bytes2)
})
}
select {
case <-store.stopCh:
// ok
case <-time.After(time.Second):
t.Fatal("stopCh 未关闭")
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 TestSessionStore_Stop_Concurrent(t *testing.T) {
store := NewSessionStore()
func TestBuildAuthorizationURL(t *testing.T) {
url := BuildAuthorizationURL("test_state", "test_challenge", ScopeOAuth)
var wg sync.WaitGroup
for range 50 {
wg.Add(1)
go func() {
defer wg.Done()
store.Stop()
}()
}
wg.Wait()
select {
case <-store.stopCh:
// ok
case <-time.After(time.Second):
t.Fatal("stopCh 未关闭")
}
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)
}