mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
ae59a5e88a
* Add ability to disable replay protection. - This is useful for runs with multiple traefik replicas to avoid false positives and tokens re-creation. * Enhance the CI/CD pipelines * Increase test coverage. * Update vendored dependencies. * Update behaviour on forceHTTPS as per issue #82
394 lines
8.4 KiB
Go
394 lines
8.4 KiB
Go
// Package testing provides unified mock implementations for tests
|
|
package testing
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// UnifiedMockLogger provides a standard mock logger for all tests
|
|
type UnifiedMockLogger struct {
|
|
LoggedMessages []string
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewUnifiedMockLogger() *UnifiedMockLogger {
|
|
return &UnifiedMockLogger{
|
|
LoggedMessages: make([]string, 0),
|
|
}
|
|
}
|
|
|
|
func (l *UnifiedMockLogger) Debug(msg string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.LoggedMessages = append(l.LoggedMessages, fmt.Sprintf("DEBUG: %s", msg))
|
|
}
|
|
|
|
func (l *UnifiedMockLogger) Debugf(format string, args ...interface{}) {
|
|
l.Debug(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (l *UnifiedMockLogger) Info(msg string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.LoggedMessages = append(l.LoggedMessages, fmt.Sprintf("INFO: %s", msg))
|
|
}
|
|
|
|
func (l *UnifiedMockLogger) Infof(format string, args ...interface{}) {
|
|
l.Info(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (l *UnifiedMockLogger) Error(msg string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.LoggedMessages = append(l.LoggedMessages, fmt.Sprintf("ERROR: %s", msg))
|
|
}
|
|
|
|
func (l *UnifiedMockLogger) Errorf(format string, args ...interface{}) {
|
|
l.Error(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (l *UnifiedMockLogger) GetMessages() []string {
|
|
l.mu.RLock()
|
|
defer l.mu.RUnlock()
|
|
result := make([]string, len(l.LoggedMessages))
|
|
copy(result, l.LoggedMessages)
|
|
return result
|
|
}
|
|
|
|
func (l *UnifiedMockLogger) Clear() {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.LoggedMessages = l.LoggedMessages[:0]
|
|
}
|
|
|
|
// UnifiedMockSession provides a standard mock session for all tests
|
|
type UnifiedMockSession struct {
|
|
authenticated bool
|
|
idToken string
|
|
accessToken string
|
|
refreshToken string
|
|
email string
|
|
csrf string
|
|
nonce string
|
|
codeVerifier string
|
|
incomingPath string
|
|
redirectCount int
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewUnifiedMockSession() *UnifiedMockSession {
|
|
return &UnifiedMockSession{}
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetAuthenticated() bool {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.authenticated
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetAuthenticated(auth bool) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.authenticated = auth
|
|
return nil
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetIDToken() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.idToken
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetIDToken(token string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.idToken = token
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetAccessToken() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.accessToken
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetAccessToken(token string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.accessToken = token
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetRefreshToken() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.refreshToken
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetRefreshToken(token string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.refreshToken = token
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetEmail() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.email
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetEmail(email string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.email = email
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetCSRF() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.csrf
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetCSRF(csrf string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.csrf = csrf
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetNonce() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.nonce
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetNonce(nonce string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.nonce = nonce
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetCodeVerifier() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.codeVerifier
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetCodeVerifier(verifier string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.codeVerifier = verifier
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetIncomingPath() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.incomingPath
|
|
}
|
|
|
|
func (s *UnifiedMockSession) SetIncomingPath(path string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.incomingPath = path
|
|
}
|
|
|
|
func (s *UnifiedMockSession) GetRedirectCount() int {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.redirectCount
|
|
}
|
|
|
|
func (s *UnifiedMockSession) IncrementRedirectCount() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.redirectCount++
|
|
}
|
|
|
|
func (s *UnifiedMockSession) ResetRedirectCount() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.redirectCount = 0
|
|
}
|
|
|
|
func (s *UnifiedMockSession) Save(req *http.Request, rw http.ResponseWriter) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *UnifiedMockSession) Clear(req *http.Request, rw http.ResponseWriter) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.authenticated = false
|
|
s.idToken = ""
|
|
s.accessToken = ""
|
|
s.refreshToken = ""
|
|
s.email = ""
|
|
s.csrf = ""
|
|
s.nonce = ""
|
|
s.codeVerifier = ""
|
|
s.incomingPath = ""
|
|
s.redirectCount = 0
|
|
return nil
|
|
}
|
|
|
|
func (s *UnifiedMockSession) MarkDirty() {}
|
|
|
|
func (s *UnifiedMockSession) IsDirty() bool {
|
|
return false
|
|
}
|
|
|
|
func (s *UnifiedMockSession) ReturnToPoolSafely() {}
|
|
|
|
// UnifiedMockTokenVerifier provides a standard mock token verifier
|
|
type UnifiedMockTokenVerifier struct {
|
|
ShouldFail bool
|
|
Error error
|
|
}
|
|
|
|
func NewUnifiedMockTokenVerifier() *UnifiedMockTokenVerifier {
|
|
return &UnifiedMockTokenVerifier{}
|
|
}
|
|
|
|
func (v *UnifiedMockTokenVerifier) VerifyToken(token string) error {
|
|
if v.ShouldFail {
|
|
if v.Error != nil {
|
|
return v.Error
|
|
}
|
|
return fmt.Errorf("mock verification failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UnifiedMockTokenCache provides a standard mock token cache
|
|
type UnifiedMockTokenCache struct {
|
|
data map[string]map[string]interface{}
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewUnifiedMockTokenCache() *UnifiedMockTokenCache {
|
|
return &UnifiedMockTokenCache{
|
|
data: make(map[string]map[string]interface{}),
|
|
}
|
|
}
|
|
|
|
func (c *UnifiedMockTokenCache) Get(key string) (map[string]interface{}, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
value, exists := c.data[key]
|
|
return value, exists
|
|
}
|
|
|
|
func (c *UnifiedMockTokenCache) Set(key string, claims map[string]interface{}, ttl time.Duration) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.data[key] = claims
|
|
}
|
|
|
|
func (c *UnifiedMockTokenCache) Delete(key string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
delete(c.data, key)
|
|
}
|
|
|
|
func (c *UnifiedMockTokenCache) SetMaxSize(size int) {}
|
|
|
|
func (c *UnifiedMockTokenCache) Size() int {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return len(c.data)
|
|
}
|
|
|
|
func (c *UnifiedMockTokenCache) Clear() {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.data = make(map[string]map[string]interface{})
|
|
}
|
|
|
|
func (c *UnifiedMockTokenCache) Cleanup() {}
|
|
|
|
func (c *UnifiedMockTokenCache) Close() {}
|
|
|
|
func (c *UnifiedMockTokenCache) GetStats() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"size": c.Size(),
|
|
}
|
|
}
|
|
|
|
// UnifiedMockHTTPClient provides a mock HTTP client for tests
|
|
type UnifiedMockHTTPClient struct {
|
|
Responses map[string]*http.Response
|
|
Errors map[string]error
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewUnifiedMockHTTPClient() *UnifiedMockHTTPClient {
|
|
return &UnifiedMockHTTPClient{
|
|
Responses: make(map[string]*http.Response),
|
|
Errors: make(map[string]error),
|
|
}
|
|
}
|
|
|
|
func (c *UnifiedMockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
url := req.URL.String()
|
|
if err, exists := c.Errors[url]; exists {
|
|
return nil, err
|
|
}
|
|
if resp, exists := c.Responses[url]; exists {
|
|
return resp, nil
|
|
}
|
|
|
|
// Default response
|
|
return &http.Response{
|
|
StatusCode: 200,
|
|
Body: http.NoBody,
|
|
Header: make(http.Header),
|
|
}, nil
|
|
}
|
|
|
|
func (c *UnifiedMockHTTPClient) SetResponse(url string, response *http.Response) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.Responses[url] = response
|
|
}
|
|
|
|
func (c *UnifiedMockHTTPClient) SetError(url string, err error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.Errors[url] = err
|
|
}
|
|
|
|
// TestSuite provides a unified test setup and teardown
|
|
type TestSuite struct {
|
|
Logger *UnifiedMockLogger
|
|
Session *UnifiedMockSession
|
|
TokenVerifier *UnifiedMockTokenVerifier
|
|
TokenCache *UnifiedMockTokenCache
|
|
HTTPClient *UnifiedMockHTTPClient
|
|
}
|
|
|
|
func NewTestSuite() *TestSuite {
|
|
return &TestSuite{
|
|
Logger: NewUnifiedMockLogger(),
|
|
Session: NewUnifiedMockSession(),
|
|
TokenVerifier: NewUnifiedMockTokenVerifier(),
|
|
TokenCache: NewUnifiedMockTokenCache(),
|
|
HTTPClient: NewUnifiedMockHTTPClient(),
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) Setup() {
|
|
// Common test setup
|
|
ts.Logger.Clear()
|
|
_ = ts.Session.Clear(nil, nil) // Safe to ignore: test helper function
|
|
ts.TokenCache.Clear()
|
|
ts.TokenVerifier.ShouldFail = false
|
|
ts.TokenVerifier.Error = nil
|
|
}
|
|
|
|
func (ts *TestSuite) Teardown() {
|
|
// Common test teardown
|
|
ts.TokenCache.Close()
|
|
}
|