226 lines
7.2 KiB
Go
226 lines
7.2 KiB
Go
// ABOUTME: Unit tests for authentication providers including username/password token flow
|
|
// ABOUTME: Tests token caching, login flow, and error conditions with mock servers
|
|
|
|
package v2
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestStaticTokenProvider(t *testing.T) {
|
|
provider := NewStaticTokenProvider("test-token-123")
|
|
|
|
req, _ := http.NewRequest("GET", "https://example.com", nil)
|
|
ctx := context.Background()
|
|
|
|
err := provider.Attach(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Bearer test-token-123", req.Header.Get("Authorization"))
|
|
}
|
|
|
|
func TestStaticTokenProvider_EmptyToken(t *testing.T) {
|
|
provider := NewStaticTokenProvider("")
|
|
|
|
req, _ := http.NewRequest("GET", "https://example.com", nil)
|
|
ctx := context.Background()
|
|
|
|
err := provider.Attach(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, req.Header.Get("Authorization"))
|
|
}
|
|
|
|
func TestUsernamePasswordProvider_Success(t *testing.T) {
|
|
// Mock login server
|
|
loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "POST", r.Method)
|
|
assert.Equal(t, "/api/v1/login", r.URL.Path)
|
|
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
|
|
// Verify request body
|
|
var creds map[string]string
|
|
err := json.NewDecoder(r.Body).Decode(&creds)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "testuser", creds["username"])
|
|
assert.Equal(t, "testpass", creds["password"])
|
|
|
|
// Return token
|
|
response := map[string]string{"token": "dynamic-token-456"}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer loginServer.Close()
|
|
|
|
provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)
|
|
|
|
req, _ := http.NewRequest("GET", "https://api.example.com", nil)
|
|
ctx := context.Background()
|
|
|
|
err := provider.Attach(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Bearer dynamic-token-456", req.Header.Get("Authorization"))
|
|
}
|
|
|
|
func TestUsernamePasswordProvider_LoginFailure(t *testing.T) {
|
|
// Mock login server that returns error
|
|
loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
_, _ = w.Write([]byte("Invalid credentials"))
|
|
}))
|
|
defer loginServer.Close()
|
|
|
|
provider := NewUsernamePasswordProvider(loginServer.URL, "baduser", "badpass", nil)
|
|
|
|
req, _ := http.NewRequest("GET", "https://api.example.com", nil)
|
|
ctx := context.Background()
|
|
|
|
err := provider.Attach(ctx, req)
|
|
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "login failed with status 401")
|
|
assert.Contains(t, err.Error(), "Invalid credentials")
|
|
}
|
|
|
|
func TestUsernamePasswordProvider_TokenCaching(t *testing.T) {
|
|
callCount := 0
|
|
|
|
// Mock login server that tracks calls
|
|
loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
callCount++
|
|
response := map[string]string{"token": "cached-token-789"}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer loginServer.Close()
|
|
|
|
provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)
|
|
ctx := context.Background()
|
|
|
|
// First request should call login
|
|
req1, _ := http.NewRequest("GET", "https://api.example.com", nil)
|
|
err1 := provider.Attach(ctx, req1)
|
|
require.NoError(t, err1)
|
|
assert.Equal(t, "Bearer cached-token-789", req1.Header.Get("Authorization"))
|
|
assert.Equal(t, 1, callCount)
|
|
|
|
// Second request should use cached token (no additional login call)
|
|
req2, _ := http.NewRequest("GET", "https://api.example.com", nil)
|
|
err2 := provider.Attach(ctx, req2)
|
|
require.NoError(t, err2)
|
|
assert.Equal(t, "Bearer cached-token-789", req2.Header.Get("Authorization"))
|
|
assert.Equal(t, 1, callCount) // Still only 1 call
|
|
}
|
|
|
|
func TestUsernamePasswordProvider_TokenExpiry(t *testing.T) {
|
|
callCount := 0
|
|
|
|
loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
callCount++
|
|
response := map[string]string{"token": "refreshed-token-999"}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer loginServer.Close()
|
|
|
|
provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)
|
|
|
|
// Manually set expired token
|
|
provider.mu.Lock()
|
|
provider.cachedToken = "expired-token"
|
|
provider.tokenExpiry = time.Now().Add(-1 * time.Hour) // Already expired
|
|
provider.mu.Unlock()
|
|
|
|
ctx := context.Background()
|
|
req, _ := http.NewRequest("GET", "https://api.example.com", nil)
|
|
|
|
err := provider.Attach(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Bearer refreshed-token-999", req.Header.Get("Authorization"))
|
|
assert.Equal(t, 1, callCount) // New token retrieved
|
|
}
|
|
|
|
func TestUsernamePasswordProvider_InvalidateToken(t *testing.T) {
|
|
callCount := 0
|
|
|
|
loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
callCount++
|
|
response := map[string]string{"token": "new-token-after-invalidation"}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer loginServer.Close()
|
|
|
|
provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)
|
|
ctx := context.Background()
|
|
|
|
// First request to get token
|
|
req1, _ := http.NewRequest("GET", "https://api.example.com", nil)
|
|
err1 := provider.Attach(ctx, req1)
|
|
require.NoError(t, err1)
|
|
assert.Equal(t, 1, callCount)
|
|
|
|
// Invalidate token
|
|
provider.InvalidateToken()
|
|
|
|
// Next request should get new token
|
|
req2, _ := http.NewRequest("GET", "https://api.example.com", nil)
|
|
err2 := provider.Attach(ctx, req2)
|
|
require.NoError(t, err2)
|
|
assert.Equal(t, "Bearer new-token-after-invalidation", req2.Header.Get("Authorization"))
|
|
assert.Equal(t, 2, callCount) // New login call made
|
|
}
|
|
|
|
func TestUsernamePasswordProvider_BadJSONResponse(t *testing.T) {
|
|
// Mock server returning invalid JSON
|
|
loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte("invalid json response"))
|
|
}))
|
|
defer loginServer.Close()
|
|
|
|
provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil)
|
|
|
|
req, _ := http.NewRequest("GET", "https://api.example.com", nil)
|
|
ctx := context.Background()
|
|
|
|
err := provider.Attach(ctx, req)
|
|
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "error parsing JSON")
|
|
}
|
|
|
|
func TestNoAuthProvider(t *testing.T) {
|
|
provider := NewNoAuthProvider()
|
|
|
|
req, _ := http.NewRequest("GET", "https://example.com", nil)
|
|
ctx := context.Background()
|
|
|
|
err := provider.Attach(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, req.Header.Get("Authorization"))
|
|
}
|
|
|
|
func TestNewClientWithCredentials(t *testing.T) {
|
|
client := NewClientWithCredentials("https://example.com", "testuser", "testpass")
|
|
|
|
assert.Equal(t, "https://example.com", client.BaseURL)
|
|
|
|
// Check that auth provider is UsernamePasswordProvider
|
|
authProvider, ok := client.AuthProvider.(*UsernamePasswordProvider)
|
|
require.True(t, ok, "AuthProvider should be UsernamePasswordProvider")
|
|
assert.Equal(t, "testuser", authProvider.Username)
|
|
assert.Equal(t, "testpass", authProvider.Password)
|
|
assert.Equal(t, "https://example.com", authProvider.BaseURL)
|
|
}
|