package hlog import ( "bytes" "io" "os" "path/filepath" "strings" "testing" "github.com/rs/zerolog" ) func TestNewLogger(t *testing.T) { tests := []struct { name string cfg *Config writer io.Writer wantErr bool errMsg string }{ { name: "console output only", cfg: &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "console", LogDir: "", LogFileName: "", LogAppend: true, }, writer: bytes.NewBuffer(nil), wantErr: false, }, { name: "nil config", cfg: nil, writer: bytes.NewBuffer(nil), wantErr: true, errMsg: "No config provided", }, { name: "nil writer for both output", cfg: &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "both", }, writer: nil, wantErr: true, errMsg: "No Writer provided", }, { name: "file output without LogDir", cfg: &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "file", LogDir: "", LogFileName: "test.log", LogAppend: true, }, writer: nil, wantErr: true, errMsg: "LOG_DIR must be set", }, { name: "file output without LogFileName", cfg: &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "file", LogDir: "/tmp", LogFileName: "", LogAppend: true, }, writer: nil, wantErr: true, errMsg: "LOG_FILE_NAME must be set", }, { name: "both output without LogDir", cfg: &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "both", LogDir: "", LogFileName: "test.log", LogAppend: true, }, writer: bytes.NewBuffer(nil), wantErr: true, errMsg: "LOG_DIR must be set", }, { name: "both output without LogFileName", cfg: &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "both", LogDir: "/tmp", LogFileName: "", LogAppend: true, }, writer: bytes.NewBuffer(nil), wantErr: true, errMsg: "LOG_FILE_NAME must be set", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logger, err := NewLogger(tt.cfg, tt.writer) if tt.wantErr { if err == nil { t.Errorf("NewLogger() expected error but got nil") return } if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) { t.Errorf("NewLogger() error = %v, should contain %v", err, tt.errMsg) } return } if err != nil { t.Errorf("NewLogger() unexpected error = %v", err) return } if logger == nil { t.Errorf("NewLogger() returned nil logger") return } if logger.Logger == nil { t.Errorf("NewLogger() returned logger with nil zerolog.Logger") } }) } } func TestNewLogger_FileOutput(t *testing.T) { // Create temporary directory for test logs tempDir := t.TempDir() tests := []struct { name string cfg *Config writer io.Writer wantErr bool checkFile bool logMessage string }{ { name: "file output with append", cfg: &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "file", LogDir: tempDir, LogFileName: "append_test.log", LogAppend: true, }, writer: nil, wantErr: false, checkFile: true, logMessage: "test append message", }, { name: "file output with overwrite", cfg: &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "file", LogDir: tempDir, LogFileName: "overwrite_test.log", LogAppend: false, }, writer: nil, wantErr: false, checkFile: true, logMessage: "test overwrite message", }, { name: "both output modes", cfg: &Config{ LogLevel: zerolog.DebugLevel, LogOutput: "both", LogDir: tempDir, LogFileName: "both_test.log", LogAppend: true, }, writer: bytes.NewBuffer(nil), wantErr: false, checkFile: true, logMessage: "test both message", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logger, err := NewLogger(tt.cfg, tt.writer) if tt.wantErr { if err == nil { t.Errorf("NewLogger() expected error but got nil") } return } if err != nil { t.Errorf("NewLogger() unexpected error = %v", err) return } if logger == nil { t.Errorf("NewLogger() returned nil logger") return } // Log a test message logger.Info().Msg(tt.logMessage) // Close the log file to flush err = logger.CloseLogFile() if err != nil { t.Errorf("CloseLogFile() error = %v", err) } // Check if file exists and contains message if tt.checkFile { logPath := filepath.Join(tt.cfg.LogDir, tt.cfg.LogFileName) content, err := os.ReadFile(logPath) if err != nil { t.Errorf("Failed to read log file: %v", err) return } if !strings.Contains(string(content), tt.logMessage) { t.Errorf("Log file doesn't contain expected message. Got: %s", string(content)) } } // Check console output for "both" mode if tt.cfg.LogOutput == "both" && tt.writer != nil { if buf, ok := tt.writer.(*bytes.Buffer); ok { consoleOutput := buf.String() if !strings.Contains(consoleOutput, tt.logMessage) { t.Errorf("Console output doesn't contain expected message. Got: %s", consoleOutput) } } } }) } } func TestNewLogger_AppendVsOverwrite(t *testing.T) { tempDir := t.TempDir() logFileName := "append_vs_overwrite.log" // First logger - write initial content cfg1 := &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "file", LogDir: tempDir, LogFileName: logFileName, LogAppend: true, } logger1, err := NewLogger(cfg1, nil) if err != nil { t.Fatalf("NewLogger() error = %v", err) } logger1.Info().Msg("first message") logger1.CloseLogFile() // Second logger - append mode cfg2 := &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "file", LogDir: tempDir, LogFileName: logFileName, LogAppend: true, } logger2, err := NewLogger(cfg2, nil) if err != nil { t.Fatalf("NewLogger() error = %v", err) } logger2.Info().Msg("second message") logger2.CloseLogFile() // Check both messages exist logPath := filepath.Join(tempDir, logFileName) content, err := os.ReadFile(logPath) if err != nil { t.Fatalf("Failed to read log file: %v", err) } contentStr := string(content) if !strings.Contains(contentStr, "first message") { t.Errorf("Log file missing 'first message' after append") } if !strings.Contains(contentStr, "second message") { t.Errorf("Log file missing 'second message' after append") } // Third logger - overwrite mode cfg3 := &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "file", LogDir: tempDir, LogFileName: logFileName, LogAppend: false, } logger3, err := NewLogger(cfg3, nil) if err != nil { t.Fatalf("NewLogger() error = %v", err) } logger3.Info().Msg("third message") logger3.CloseLogFile() // Check only third message exists content, err = os.ReadFile(logPath) if err != nil { t.Fatalf("Failed to read log file: %v", err) } contentStr = string(content) if strings.Contains(contentStr, "first message") { t.Errorf("Log file still contains 'first message' after overwrite") } if strings.Contains(contentStr, "second message") { t.Errorf("Log file still contains 'second message' after overwrite") } if !strings.Contains(contentStr, "third message") { t.Errorf("Log file missing 'third message' after overwrite") } } func TestLogger_CloseLogFile(t *testing.T) { t.Run("close with file", func(t *testing.T) { tempDir := t.TempDir() cfg := &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "file", LogDir: tempDir, LogFileName: "close_test.log", LogAppend: true, } logger, err := NewLogger(cfg, nil) if err != nil { t.Fatalf("NewLogger() error = %v", err) } err = logger.CloseLogFile() if err != nil { t.Errorf("CloseLogFile() error = %v", err) } }) t.Run("close without file", func(t *testing.T) { cfg := &Config{ LogLevel: zerolog.InfoLevel, LogOutput: "console", } logger, err := NewLogger(cfg, bytes.NewBuffer(nil)) if err != nil { t.Fatalf("NewLogger() error = %v", err) } err = logger.CloseLogFile() if err != nil { t.Errorf("CloseLogFile() should not error when no file is open, got: %v", err) } }) }