mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
Release 0.7.5 (#70)
* Resolve issue with opaque tokens not being parsed correctly * Increase test coverage * Further improvements to test coverage and code quality * Add new providers. * fixup! Add new providers. * Cleanup. * fixup! Cleanup. * fixup! fixup! Cleanup. * fixup! fixup! fixup! Cleanup. * fixup! fixup! fixup! fixup! Cleanup. * Memory management optimisation 24 bytes per Put < 256-4096 bytes per buffer allocation avoided (10-170x difference) * Pooling cleanup.
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestCreateProxy tests the CreateProxy method
|
||||
func TestCreateProxy(t *testing.T) {
|
||||
factory := NewFactory(nil)
|
||||
client, err := factory.CreateProxy()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create proxy client: %v", err)
|
||||
}
|
||||
if client == nil {
|
||||
t.Fatal("Expected non-nil proxy client")
|
||||
}
|
||||
|
||||
// Verify proxy configuration specifics
|
||||
if client.Timeout != 60*time.Second {
|
||||
t.Errorf("Expected proxy timeout to be 60s, got %v", client.Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateConfigEdgeCases tests additional validation scenarios
|
||||
func TestValidateConfigEdgeCases(t *testing.T) {
|
||||
factory := NewFactory(nil)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config Config
|
||||
shouldFail bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "Negative MaxIdleConnsPerHost",
|
||||
config: Config{
|
||||
MaxIdleConnsPerHost: -1,
|
||||
},
|
||||
shouldFail: true,
|
||||
errorMsg: "MaxIdleConnsPerHost cannot be negative",
|
||||
},
|
||||
{
|
||||
name: "Excessive MaxIdleConnsPerHost",
|
||||
config: Config{
|
||||
MaxIdleConnsPerHost: 200,
|
||||
},
|
||||
shouldFail: true,
|
||||
errorMsg: "MaxIdleConnsPerHost too high",
|
||||
},
|
||||
{
|
||||
name: "Negative MaxConnsPerHost",
|
||||
config: Config{
|
||||
MaxConnsPerHost: -1,
|
||||
},
|
||||
shouldFail: true,
|
||||
errorMsg: "MaxConnsPerHost cannot be negative",
|
||||
},
|
||||
{
|
||||
name: "Excessive MaxConnsPerHost",
|
||||
config: Config{
|
||||
MaxConnsPerHost: 300,
|
||||
},
|
||||
shouldFail: true,
|
||||
errorMsg: "MaxConnsPerHost too high",
|
||||
},
|
||||
{
|
||||
name: "Negative WriteBufferSize",
|
||||
config: Config{
|
||||
WriteBufferSize: -1,
|
||||
},
|
||||
shouldFail: true,
|
||||
errorMsg: "buffer sizes cannot be negative",
|
||||
},
|
||||
{
|
||||
name: "Negative ReadBufferSize",
|
||||
config: Config{
|
||||
ReadBufferSize: -1,
|
||||
},
|
||||
shouldFail: true,
|
||||
errorMsg: "buffer sizes cannot be negative",
|
||||
},
|
||||
{
|
||||
name: "Excessive WriteBufferSize",
|
||||
config: Config{
|
||||
WriteBufferSize: 2 * 1024 * 1024,
|
||||
},
|
||||
shouldFail: true,
|
||||
errorMsg: "buffer sizes too large",
|
||||
},
|
||||
{
|
||||
name: "Excessive ReadBufferSize",
|
||||
config: Config{
|
||||
ReadBufferSize: 2 * 1024 * 1024,
|
||||
},
|
||||
shouldFail: true,
|
||||
errorMsg: "buffer sizes too large",
|
||||
},
|
||||
{
|
||||
name: "Valid edge values",
|
||||
config: Config{
|
||||
MaxIdleConns: 1000,
|
||||
MaxIdleConnsPerHost: 100,
|
||||
MaxConnsPerHost: 200,
|
||||
Timeout: 5 * time.Minute,
|
||||
WriteBufferSize: 1024 * 1024,
|
||||
ReadBufferSize: 1024 * 1024,
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := factory.ValidateConfig(&tc.config)
|
||||
if tc.shouldFail {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected validation to fail with message containing: %s", tc.errorMsg)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected validation error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTransportPoolClose tests the Close method of TransportPool
|
||||
func TestTransportPoolClose(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
pool := &TransportPool{
|
||||
transports: make(map[string]*sharedTransport),
|
||||
maxConns: 20,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
clientCount: 0,
|
||||
maxClients: 5,
|
||||
}
|
||||
|
||||
// Create some transports
|
||||
config := PresetConfigs[ClientTypeDefault]
|
||||
transport1 := pool.GetOrCreateTransport(config)
|
||||
if transport1 == nil {
|
||||
t.Fatal("Failed to create transport")
|
||||
}
|
||||
|
||||
// Modify config slightly to create a different transport
|
||||
config.Timeout = 20 * time.Second
|
||||
transport2 := pool.GetOrCreateTransport(config)
|
||||
if transport2 == nil {
|
||||
t.Fatal("Failed to create second transport")
|
||||
}
|
||||
|
||||
// Verify transports were created
|
||||
pool.mu.RLock()
|
||||
initialCount := len(pool.transports)
|
||||
pool.mu.RUnlock()
|
||||
if initialCount == 0 {
|
||||
t.Fatal("Expected transports to be created")
|
||||
}
|
||||
|
||||
// Close the pool
|
||||
err := pool.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to close pool: %v", err)
|
||||
}
|
||||
|
||||
// Verify all transports were removed
|
||||
pool.mu.RLock()
|
||||
finalCount := len(pool.transports)
|
||||
pool.mu.RUnlock()
|
||||
if finalCount != 0 {
|
||||
t.Fatalf("Expected 0 transports after close, got %d", finalCount)
|
||||
}
|
||||
|
||||
// Verify client count was reset
|
||||
if pool.clientCount != 0 {
|
||||
t.Fatalf("Expected client count to be 0 after close, got %d", pool.clientCount)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNoOpLogger tests the no-op logger implementation
|
||||
func TestNoOpLogger(t *testing.T) {
|
||||
logger := &noOpLogger{}
|
||||
|
||||
// These should not panic or cause any issues
|
||||
logger.Debug("test debug")
|
||||
logger.Debugf("test debug %s", "formatted")
|
||||
logger.Info("test info")
|
||||
logger.Infof("test info %s", "formatted")
|
||||
logger.Error("test error")
|
||||
logger.Errorf("test error %s", "formatted")
|
||||
|
||||
// Test using logger with factory
|
||||
factory := NewFactory(logger)
|
||||
client, err := factory.CreateDefault()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client with no-op logger: %v", err)
|
||||
}
|
||||
if client == nil {
|
||||
t.Fatal("Expected non-nil client")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateClientWithCustomTLS tests creating client with custom TLS config
|
||||
func TestCreateClientWithCustomTLS(t *testing.T) {
|
||||
factory := NewFactory(nil)
|
||||
|
||||
customTLS := &tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
config := Config{
|
||||
Timeout: 10 * time.Second,
|
||||
MaxIdleConns: 10,
|
||||
MaxIdleConnsPerHost: 2,
|
||||
MaxConnsPerHost: 5,
|
||||
TLSConfig: customTLS,
|
||||
}
|
||||
|
||||
client, err := factory.CreateClient(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client with custom TLS: %v", err)
|
||||
}
|
||||
if client == nil {
|
||||
t.Fatal("Expected non-nil client")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateClientWithMaxRedirects tests redirect limiting
|
||||
func TestCreateClientWithMaxRedirects(t *testing.T) {
|
||||
redirectCount := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
redirectCount++
|
||||
if redirectCount <= 3 {
|
||||
http.Redirect(w, r, "/redirect", http.StatusFound)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("final"))
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
factory := NewFactory(nil)
|
||||
|
||||
// Test with max redirects = 2 (should fail)
|
||||
config := Config{
|
||||
Timeout: 10 * time.Second,
|
||||
MaxRedirects: 2,
|
||||
MaxIdleConns: 10,
|
||||
MaxIdleConnsPerHost: 2,
|
||||
MaxConnsPerHost: 5,
|
||||
}
|
||||
|
||||
client, err := factory.CreateClient(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
redirectCount = 0
|
||||
_, err = client.Get(server.URL)
|
||||
if err == nil {
|
||||
t.Fatal("Expected redirect limit error")
|
||||
}
|
||||
|
||||
// Test with max redirects = 5 (should succeed)
|
||||
config.MaxRedirects = 5
|
||||
client, err = factory.CreateClient(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
redirectCount = 0
|
||||
resp, err := client.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTransportPoolMaxClientsLimit tests the max clients limitation
|
||||
func TestTransportPoolMaxClientsLimit(t *testing.T) {
|
||||
pool := &TransportPool{
|
||||
transports: make(map[string]*sharedTransport),
|
||||
maxConns: 20,
|
||||
clientCount: 0,
|
||||
maxClients: 2, // Set low limit for testing
|
||||
}
|
||||
|
||||
// Create transports up to the limit
|
||||
configs := []Config{
|
||||
{Timeout: 10 * time.Second},
|
||||
{Timeout: 20 * time.Second},
|
||||
{Timeout: 30 * time.Second}, // This should not create a new transport
|
||||
}
|
||||
|
||||
for i, config := range configs {
|
||||
transport := pool.GetOrCreateTransport(config)
|
||||
if i < 2 {
|
||||
if transport == nil {
|
||||
t.Fatalf("Expected transport %d to be created", i)
|
||||
}
|
||||
// Transport created successfully within limit
|
||||
} else {
|
||||
// When limit is reached, should return existing transport or nil
|
||||
if transport == nil {
|
||||
// This is acceptable - nil when limit reached
|
||||
t.Log("Transport creation blocked due to client limit")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify client count doesn't exceed limit
|
||||
if pool.clientCount > pool.maxClients {
|
||||
t.Fatalf("Client count %d exceeds max %d", pool.clientCount, pool.maxClients)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCleanupIdleTransportsContext tests cleanup goroutine with context
|
||||
func TestCleanupIdleTransportsContext(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
pool := &TransportPool{
|
||||
transports: make(map[string]*sharedTransport),
|
||||
maxConns: 20,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
clientCount: 0,
|
||||
maxClients: 5,
|
||||
}
|
||||
|
||||
// Start cleanup goroutine
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
pool.cleanupIdleTransports(ctx)
|
||||
done <- true
|
||||
}()
|
||||
|
||||
// Give it a moment to start
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Cancel context to stop cleanup
|
||||
cancel()
|
||||
|
||||
// Wait for goroutine to exit
|
||||
select {
|
||||
case <-done:
|
||||
// Success - goroutine exited
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("Cleanup goroutine did not exit after context cancellation")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFactoryWithLogger tests factory creation with custom logger
|
||||
func TestFactoryWithLogger(t *testing.T) {
|
||||
// Create a mock logger that implements the Logger interface
|
||||
logger := &MockLogger{}
|
||||
|
||||
factory := NewFactory(logger)
|
||||
if factory.logger == nil {
|
||||
t.Fatal("Expected logger to be set")
|
||||
}
|
||||
}
|
||||
|
||||
// MockLogger for testing
|
||||
type MockLogger struct {
|
||||
debugCalled bool
|
||||
debugfCalled bool
|
||||
infoCalled bool
|
||||
infofCalled bool
|
||||
errorCalled bool
|
||||
errorfCalled bool
|
||||
}
|
||||
|
||||
func (m *MockLogger) Debug(msg string) { m.debugCalled = true }
|
||||
func (m *MockLogger) Debugf(format string, args ...interface{}) { m.debugfCalled = true }
|
||||
func (m *MockLogger) Info(msg string) { m.infoCalled = true }
|
||||
func (m *MockLogger) Infof(format string, args ...interface{}) { m.infofCalled = true }
|
||||
func (m *MockLogger) Error(msg string) { m.errorCalled = true }
|
||||
func (m *MockLogger) Errorf(format string, args ...interface{}) { m.errorfCalled = true }
|
||||
|
||||
// TestCreateClientLogging tests that logger is called during client creation
|
||||
func TestCreateClientLogging(t *testing.T) {
|
||||
logger := &MockLogger{}
|
||||
factory := NewFactory(logger)
|
||||
|
||||
client, err := factory.CreateDefault()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
if client == nil {
|
||||
t.Fatal("Expected non-nil client")
|
||||
}
|
||||
|
||||
// Verify logger was called
|
||||
if !logger.debugfCalled {
|
||||
t.Error("Expected Debugf to be called during client creation")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user