Improve logging testing
diff --git a/cmd/korap-mcp/main.go b/cmd/korap-mcp/main.go
index 0cf7fb3..2b67475 100644
--- a/cmd/korap-mcp/main.go
+++ b/cmd/korap-mcp/main.go
@@ -13,6 +13,7 @@
func (c *CLI) Run() error {
// Handle version flag
if c.Version {
+ // For version output, we use stdout directly since logging isn't set up yet
fmt.Printf("%s version %s\n", c.GetServerName(), c.GetServerVersion())
os.Exit(0)
}
@@ -75,6 +76,7 @@
// Setup CLI and parser
cli, parser, err := SetupCLI()
if err != nil {
+ // Before logging is set up, we use stderr directly for critical errors
fmt.Fprintf(os.Stderr, "Failed to setup CLI: %v\n", err)
os.Exit(1)
}
diff --git a/logger/logger_test.go b/logger/logger_test.go
new file mode 100644
index 0000000..06bc660
--- /dev/null
+++ b/logger/logger_test.go
@@ -0,0 +1,203 @@
+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")
+ }
+ })
+}