| package logger |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "github.com/korap/korap-mcp/config" |
| "github.com/rs/zerolog" |
| "github.com/stretchr/testify/assert" |
| ) |
| |
| func TestSetupLogger(t *testing.T) { |
| t.Run("should setup logger with default configuration", func(t *testing.T) { |
| cfg := &config.LoggingConfig{ |
| Level: "info", |
| Format: "json", |
| } |
| |
| logger, err := SetupLogger(cfg) |
| assert.NoError(t, err) |
| assert.NotNil(t, logger) |
| }) |
| |
| t.Run("should setup logger with text format", func(t *testing.T) { |
| cfg := &config.LoggingConfig{ |
| Level: "debug", |
| Format: "text", |
| } |
| |
| logger, err := SetupLogger(cfg) |
| assert.NoError(t, err) |
| assert.NotNil(t, logger) |
| }) |
| |
| t.Run("should setup logger with file output", func(t *testing.T) { |
| // Create temporary file |
| tmpDir := t.TempDir() |
| logFile := filepath.Join(tmpDir, "test.log") |
| |
| cfg := &config.LoggingConfig{ |
| Level: "warn", |
| Format: "json", |
| File: logFile, |
| } |
| |
| logger, err := SetupLogger(cfg) |
| assert.NoError(t, err) |
| assert.NotNil(t, logger) |
| |
| // Test logging to file |
| logger.Info().Msg("test message") |
| |
| // Verify file was created |
| assert.FileExists(t, logFile) |
| }) |
| |
| t.Run("should return error for invalid log level", func(t *testing.T) { |
| cfg := &config.LoggingConfig{ |
| Level: "invalid", |
| Format: "json", |
| } |
| |
| _, err := SetupLogger(cfg) |
| assert.Error(t, err) |
| assert.Contains(t, strings.ToLower(err.Error()), "unknown level") |
| }) |
| |
| t.Run("should return error for invalid file path", func(t *testing.T) { |
| cfg := &config.LoggingConfig{ |
| Level: "info", |
| Format: "json", |
| File: "/invalid/path/test.log", |
| } |
| |
| _, err := SetupLogger(cfg) |
| assert.Error(t, err) |
| }) |
| } |
| |
| func TestGetLogger(t *testing.T) { |
| t.Run("should return configured logger", func(t *testing.T) { |
| cfg := &config.LoggingConfig{ |
| Level: "debug", |
| Format: "json", |
| } |
| |
| logger := GetLogger(cfg) |
| assert.NotNil(t, logger) |
| }) |
| |
| t.Run("should return fallback logger on configuration error", func(t *testing.T) { |
| cfg := &config.LoggingConfig{ |
| Level: "invalid", |
| Format: "json", |
| } |
| |
| logger := GetLogger(cfg) |
| assert.NotNil(t, logger) |
| }) |
| } |
| |
| func TestLoggerOutput(t *testing.T) { |
| t.Run("should produce structured JSON output", func(t *testing.T) { |
| var buf bytes.Buffer |
| |
| // Create a logger that writes to our buffer |
| logger := zerolog.New(&buf).With().Timestamp().Logger() |
| |
| // Log a message |
| logger.Info().Str("test", "value").Msg("test message") |
| |
| // Parse the JSON output |
| var logEntry map[string]interface{} |
| err := json.Unmarshal(buf.Bytes(), &logEntry) |
| assert.NoError(t, err) |
| |
| // Verify log structure |
| assert.Equal(t, "info", logEntry["level"]) |
| assert.Equal(t, "test message", logEntry["message"]) |
| assert.Equal(t, "value", logEntry["test"]) |
| assert.Contains(t, logEntry, "time") |
| }) |
| |
| t.Run("should respect log levels", func(t *testing.T) { |
| var buf bytes.Buffer |
| |
| // Set global level to warn |
| zerolog.SetGlobalLevel(zerolog.WarnLevel) |
| logger := zerolog.New(&buf).With().Timestamp().Logger() |
| |
| // Try to log at different levels |
| logger.Debug().Msg("debug message") |
| logger.Info().Msg("info message") |
| logger.Warn().Msg("warn message") |
| |
| // Only warn message should be present |
| output := buf.String() |
| assert.NotContains(t, output, "debug message") |
| assert.NotContains(t, output, "info message") |
| assert.Contains(t, output, "warn message") |
| |
| // Reset global level |
| zerolog.SetGlobalLevel(zerolog.InfoLevel) |
| }) |
| } |
| |
| func TestLoggerIntegration(t *testing.T) { |
| t.Run("should work with all supported log levels", func(t *testing.T) { |
| levels := []string{"trace", "debug", "info", "warn", "error", "fatal", "panic"} |
| |
| for _, level := range levels { |
| cfg := &config.LoggingConfig{ |
| Level: level, |
| Format: "json", |
| } |
| |
| logger, err := SetupLogger(cfg) |
| if level == "panic" { |
| // panic level might not be supported in all contexts |
| continue |
| } |
| assert.NoError(t, err, "Failed to setup logger with level: %s", level) |
| assert.NotNil(t, logger, "Logger should not be nil for level: %s", level) |
| } |
| }) |
| |
| t.Run("should work with different output formats", func(t *testing.T) { |
| formats := []string{"json", "text"} |
| |
| for _, format := range formats { |
| cfg := &config.LoggingConfig{ |
| Level: "info", |
| Format: format, |
| } |
| |
| logger, err := SetupLogger(cfg) |
| assert.NoError(t, err, "Failed to setup logger with format: %s", format) |
| assert.NotNil(t, logger, "Logger should not be nil for format: %s", format) |
| } |
| }) |
| } |
| |
| func TestLoggerErrorHandling(t *testing.T) { |
| t.Run("should handle errors gracefully in GetLogger", func(t *testing.T) { |
| // Test with various invalid configurations |
| invalidConfigs := []*config.LoggingConfig{ |
| {Level: "invalid-level", Format: "json"}, |
| {Level: "info", Format: "json", File: "/dev/null/invalid"}, |
| } |
| |
| for _, cfg := range invalidConfigs { |
| logger := GetLogger(cfg) |
| // Should not panic and should return a valid logger |
| assert.NotNil(t, logger) |
| |
| // Should be able to log |
| logger.Info().Msg("test message") |
| } |
| }) |
| } |