cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package utils
2
3import (
4 "bytes"
5 "os"
6 "strings"
7 "testing"
8
9 "github.com/charmbracelet/log"
10)
11
12func TestNewLogger(t *testing.T) {
13 t.Run("creates logger with info level", func(t *testing.T) {
14 logger := NewLogger("info", "text")
15 if logger == nil {
16 t.Fatal("Logger should not be nil")
17 }
18
19 if logger.GetLevel() != log.InfoLevel {
20 t.Errorf("Expected InfoLevel, got %v", logger.GetLevel())
21 }
22 })
23
24 t.Run("creates logger with debug level", func(t *testing.T) {
25 logger := NewLogger("debug", "text")
26 if logger.GetLevel() != log.DebugLevel {
27 t.Errorf("Expected DebugLevel, got %v", logger.GetLevel())
28 }
29 })
30
31 t.Run("creates logger with warn level", func(t *testing.T) {
32 logger := NewLogger("warn", "text")
33 if logger.GetLevel() != log.WarnLevel {
34 t.Errorf("Expected WarnLevel, got %v", logger.GetLevel())
35 }
36 })
37
38 t.Run("creates logger with warning level alias", func(t *testing.T) {
39 logger := NewLogger("warning", "text")
40 if logger.GetLevel() != log.WarnLevel {
41 t.Errorf("Expected WarnLevel, got %v", logger.GetLevel())
42 }
43 })
44
45 t.Run("creates logger with error level", func(t *testing.T) {
46 logger := NewLogger("error", "text")
47 if logger.GetLevel() != log.ErrorLevel {
48 t.Errorf("Expected ErrorLevel, got %v", logger.GetLevel())
49 }
50 })
51
52 t.Run("defaults to info level for invalid level", func(t *testing.T) {
53 logger := NewLogger("invalid", "text")
54 if logger.GetLevel() != log.InfoLevel {
55 t.Errorf("Expected InfoLevel for invalid input, got %v", logger.GetLevel())
56 }
57 })
58
59 t.Run("handles case insensitive levels", func(t *testing.T) {
60 logger := NewLogger("DEBUG", "text")
61 if logger.GetLevel() != log.DebugLevel {
62 t.Errorf("Expected DebugLevel for uppercase input, got %v", logger.GetLevel())
63 }
64 })
65
66 t.Run("creates logger with json format", func(t *testing.T) {
67 var buf bytes.Buffer
68 logger := NewLogger("info", "json")
69 logger.SetOutput(&buf)
70
71 logger.Info("test message")
72 output := buf.String()
73
74 if !strings.Contains(output, "{") || !strings.Contains(output, "}") {
75 t.Error("Expected JSON formatted output")
76 }
77 })
78
79 t.Run("creates logger with text format", func(t *testing.T) {
80 var buf bytes.Buffer
81 logger := NewLogger("info", "text")
82 logger.SetOutput(&buf)
83
84 logger.Info("test message")
85 output := buf.String()
86
87 if strings.Contains(output, "{") && strings.Contains(output, "}") {
88 t.Error("Expected text formatted output, not JSON")
89 }
90 })
91
92 t.Run("text format includes timestamp", func(t *testing.T) {
93 var buf bytes.Buffer
94 logger := NewLogger("info", "text")
95 logger.SetOutput(&buf)
96
97 logger.Info("test message")
98 output := buf.String()
99
100 if !strings.Contains(output, ":") {
101 t.Error("Expected timestamp in text format output")
102 }
103 })
104}
105
106func TestGetLogger(t *testing.T) {
107 t.Run("returns global logger when set", func(t *testing.T) {
108 originalLogger := Logger
109 defer func() { Logger = originalLogger }()
110
111 testLogger := NewLogger("debug", "json")
112 Logger = testLogger
113
114 retrieved := GetLogger()
115 if retrieved != testLogger {
116 t.Error("GetLogger should return the global logger")
117 }
118 })
119
120 t.Run("creates default logger when global is nil", func(t *testing.T) {
121 originalLogger := Logger
122 defer func() { Logger = originalLogger }()
123
124 Logger = nil
125
126 retrieved := GetLogger()
127 if retrieved == nil {
128 t.Fatal("GetLogger should create a default logger")
129 }
130
131 if retrieved.GetLevel() != log.InfoLevel {
132 t.Error("Default logger should have InfoLevel")
133 }
134
135 if Logger != retrieved {
136 t.Error("Global logger should be set after GetLogger call")
137 }
138 })
139
140 t.Run("subsequent calls return same logger", func(t *testing.T) {
141 originalLogger := Logger
142 defer func() { Logger = originalLogger }()
143
144 Logger = nil
145
146 logger1 := GetLogger()
147 logger2 := GetLogger()
148
149 if logger1 != logger2 {
150 t.Error("Subsequent GetLogger calls should return the same instance")
151 }
152 })
153}
154
155func TestLoggerIntegration(t *testing.T) {
156 t.Run("logger writes to stderr by default", func(t *testing.T) {
157 oldStderr := os.Stderr
158 r, w, _ := os.Pipe()
159 os.Stderr = w
160
161 logger := NewLogger("info", "text")
162 logger.Info("test message")
163
164 w.Close()
165 os.Stderr = oldStderr
166
167 var buf bytes.Buffer
168 buf.ReadFrom(r)
169 output := buf.String()
170
171 if !strings.Contains(output, "test message") {
172 t.Error("Logger should write to stderr by default")
173 }
174 })
175
176 t.Run("logger respects level filtering", func(t *testing.T) {
177 var buf bytes.Buffer
178 logger := NewLogger("error", "text")
179 logger.SetOutput(&buf)
180
181 logger.Debug("debug message")
182 logger.Info("info message")
183 logger.Warn("warn message")
184 logger.Error("error message")
185
186 output := buf.String()
187
188 if strings.Contains(output, "debug message") {
189 t.Error("Debug message should be filtered out at error level")
190 }
191 if strings.Contains(output, "info message") {
192 t.Error("Info message should be filtered out at error level")
193 }
194 if strings.Contains(output, "warn message") {
195 t.Error("Warn message should be filtered out at error level")
196 }
197 if !strings.Contains(output, "error message") {
198 t.Error("Error message should be included at error level")
199 }
200 })
201
202 t.Run("global logger persists between function calls", func(t *testing.T) {
203 originalLogger := Logger
204 defer func() { Logger = originalLogger }()
205
206 Logger = NewLogger("debug", "json")
207
208 retrieved := GetLogger()
209
210 if retrieved.GetLevel() != log.DebugLevel {
211 t.Error("Global logger settings should persist")
212 }
213 })
214}
215
216func TestTitlecase(t *testing.T) {
217 tests := []struct {
218 name string
219 input string
220 expected string
221 }{
222 {
223 name: "single word lowercase",
224 input: "hello",
225 expected: "Hello",
226 },
227 {
228 name: "single word uppercase",
229 input: "HELLO",
230 expected: "HELLO",
231 },
232 {
233 name: "multiple words",
234 input: "hello world",
235 expected: "Hello World",
236 },
237 {
238 name: "mixed case",
239 input: "hELLo WoRLD",
240 expected: "HELLo WoRLD",
241 },
242 {
243 name: "with punctuation",
244 input: "hello, world!",
245 expected: "Hello, World!",
246 },
247 {
248 name: "empty string",
249 input: "",
250 expected: "",
251 },
252 {
253 name: "with numbers",
254 input: "hello 123 world",
255 expected: "Hello 123 World",
256 },
257 {
258 name: "with special characters",
259 input: "hello-world_test",
260 expected: "Hello-World_test",
261 },
262 {
263 name: "already title case",
264 input: "Hello World",
265 expected: "Hello World",
266 },
267 {
268 name: "single character",
269 input: "a",
270 expected: "A",
271 },
272 {
273 name: "apostrophes",
274 input: "it's a beautiful day",
275 expected: "It's A Beautiful Day",
276 },
277 }
278
279 for _, tt := range tests {
280 t.Run(tt.name, func(t *testing.T) {
281 result := Titlecase(tt.input)
282 if result != tt.expected {
283 t.Errorf("Titlecase(%q) = %q, expected %q", tt.input, result, tt.expected)
284 }
285 })
286 }
287}