feat: Add reverse proxy functionality with enhanced routing capabilities

- Introduced IgnoreRequestPath option in proxy configuration to allow exact match routing.
- Implemented proxy_pass directive in routing extension to handle backend requests.
- Enhanced error handling for backend unavailability and timeouts.
- Added integration tests for reverse proxy, including basic requests, exact match routes, regex routes, header forwarding, and query string preservation.
- Created helper functions for setting up test servers and backends, along with assertion utilities for response validation.
- Updated server initialization to support extension management and middleware chaining.
- Improved logging for debugging purposes during request handling.
This commit is contained in:
Илья Глазунов
2025-12-12 00:38:30 +03:00
parent 8f5b9a5cd1
commit 881028c1e6
17 changed files with 3574 additions and 176 deletions
+110 -73
View File
@@ -2,6 +2,8 @@ package logging
import (
"testing"
"github.com/konduktor/konduktor/internal/config"
)
func TestNew(t *testing.T) {
@@ -15,71 +17,84 @@ func TestNew(t *testing.T) {
t.Fatal("Expected logger, got nil")
}
if logger.level != "INFO" {
t.Errorf("Expected level INFO, got %s", logger.level)
if logger.name != "konduktor" {
t.Errorf("Expected name konduktor, got %s", logger.name)
}
}
func TestNew_DefaultTimestampFormat(t *testing.T) {
logger, _ := New(Config{Level: "DEBUG"})
logger, err := New(Config{Level: "DEBUG"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if logger.timestampFormat != "2006-01-02 15:04:05" {
t.Errorf("Expected default timestamp format, got %s", logger.timestampFormat)
// Logger should be created successfully
if logger == nil {
t.Fatal("Expected logger, got nil")
}
}
func TestNew_CustomTimestampFormat(t *testing.T) {
logger, _ := New(Config{
logger, err := New(Config{
Level: "DEBUG",
TimestampFormat: "15:04:05",
})
if logger.timestampFormat != "15:04:05" {
t.Errorf("Expected custom timestamp format, got %s", logger.timestampFormat)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if logger == nil {
t.Fatal("Expected logger, got nil")
}
}
func TestLogger_ShouldLog(t *testing.T) {
tests := []struct {
loggerLevel string
msgLevel string
shouldLog bool
}{
{"DEBUG", "DEBUG", true},
{"DEBUG", "INFO", true},
{"DEBUG", "WARN", true},
{"DEBUG", "ERROR", true},
{"INFO", "DEBUG", false},
{"INFO", "INFO", true},
{"INFO", "WARN", true},
{"INFO", "ERROR", true},
{"WARN", "DEBUG", false},
{"WARN", "INFO", false},
{"WARN", "WARN", true},
{"WARN", "ERROR", true},
{"ERROR", "DEBUG", false},
{"ERROR", "INFO", false},
{"ERROR", "WARN", false},
{"ERROR", "ERROR", true},
func TestNewFromConfig(t *testing.T) {
cfg := config.LoggingConfig{
Level: "DEBUG",
ConsoleOutput: true,
Format: config.LogFormatConfig{
Type: "standard",
UseColors: true,
ShowModule: true,
TimestampFormat: "2006-01-02 15:04:05",
},
}
for _, tt := range tests {
t.Run(tt.loggerLevel+"_"+tt.msgLevel, func(t *testing.T) {
logger, _ := New(Config{Level: tt.loggerLevel})
logger, err := NewFromConfig(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if got := logger.shouldLog(tt.msgLevel); got != tt.shouldLog {
t.Errorf("shouldLog(%s) = %v, want %v", tt.msgLevel, got, tt.shouldLog)
}
})
if logger == nil {
t.Fatal("Expected logger, got nil")
}
}
func TestLogger_ShouldLog_InvalidLevel(t *testing.T) {
logger, _ := New(Config{Level: "INVALID"})
func TestNewFromConfig_WithConsole(t *testing.T) {
cfg := config.LoggingConfig{
Level: "INFO",
ConsoleOutput: true,
Format: config.LogFormatConfig{
Type: "standard",
UseColors: true,
},
Console: &config.ConsoleLogConfig{
Level: "DEBUG",
Format: config.LogFormatConfig{
Type: "standard",
UseColors: false,
},
},
}
// Should default to INFO level
if !logger.shouldLog("INFO") {
t.Error("Invalid level should default to INFO")
logger, err := NewFromConfig(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if logger == nil {
t.Fatal("Expected logger, got nil")
}
}
@@ -111,34 +126,65 @@ func TestLogger_Error(t *testing.T) {
logger.Error("test message", "key", "value")
}
func TestFormatValue(t *testing.T) {
func TestLogger_Named(t *testing.T) {
logger, _ := New(Config{Level: "INFO"})
named := logger.Named("test.module")
if named == nil {
t.Fatal("Expected named logger, got nil")
}
if named.name != "test.module" {
t.Errorf("Expected name 'test.module', got %s", named.name)
}
// Should not panic
named.Info("test from named logger")
}
func TestLogger_With(t *testing.T) {
logger, _ := New(Config{Level: "INFO"})
withFields := logger.With("service", "test")
if withFields == nil {
t.Fatal("Expected logger with fields, got nil")
}
// Should not panic
withFields.Info("test with fields")
}
func TestLogger_Sync(t *testing.T) {
logger, _ := New(Config{Level: "INFO"})
// Should not panic
err := logger.Sync()
// Sync may return an error for stdout on some systems, ignore it
_ = err
}
func TestParseLevel(t *testing.T) {
tests := []struct {
input interface{}
input string
expected string
}{
{"test", "test"},
{42, "*"}, // int converts to rune
{nil, ""},
{"DEBUG", "debug"},
{"INFO", "info"},
{"WARN", "warn"},
{"WARNING", "warn"},
{"ERROR", "error"},
{"CRITICAL", "fatal"},
{"FATAL", "fatal"},
{"invalid", "info"}, // defaults to INFO
}
for _, tt := range tests {
got := formatValue(tt.input)
// Just check it doesn't panic
_ = got
}
}
func TestLogger_FormatTime(t *testing.T) {
logger, _ := New(Config{
Level: "INFO",
TimestampFormat: "2006-01-02",
})
result := logger.formatTime()
// Should be in expected format (YYYY-MM-DD)
if len(result) != 10 {
t.Errorf("Expected date format YYYY-MM-DD, got %s", result)
t.Run(tt.input, func(t *testing.T) {
level := parseLevel(tt.input)
if level.String() != tt.expected {
t.Errorf("parseLevel(%s) = %s, want %s", tt.input, level.String(), tt.expected)
}
})
}
}
@@ -161,12 +207,3 @@ func BenchmarkLogger_Debug_Filtered(b *testing.B) {
logger.Debug("test message", "key", "value")
}
}
func BenchmarkLogger_ShouldLog(b *testing.B) {
logger, _ := New(Config{Level: "INFO"})
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.shouldLog("DEBUG")
}
}