internal/output/formatter_test.go (view raw)
1package output
2
3import (
4 "encoding/json"
5 "strings"
6 "testing"
7 "time"
8)
9
10func TestNewFormatter(t *testing.T) {
11 tests := []struct {
12 name string
13 format string
14 wantErr bool
15 wantNil bool
16 }{
17 {"text format", "text", false, false},
18 {"json format", "json", false, false},
19 {"markdown format", "markdown", false, false},
20 {"uppercase text", "TEXT", false, false},
21 {"mixed case json", "Json", false, false},
22 {"unsupported format", "xml", true, true},
23 {"empty format", "", true, true},
24 {"unknown format", "csv", true, true},
25 }
26
27 for _, tt := range tests {
28 t.Run(tt.name, func(t *testing.T) {
29 formatter, err := NewFormatter(tt.format)
30
31 if tt.wantErr && err == nil {
32 t.Error("Expected error, got nil")
33 }
34 if !tt.wantErr && err != nil {
35 t.Errorf("Unexpected error: %v", err)
36 }
37 if tt.wantNil && formatter != nil {
38 t.Error("Expected nil formatter")
39 }
40 if !tt.wantNil && formatter == nil {
41 t.Error("Expected non-nil formatter")
42 }
43 })
44 }
45}
46
47func TestFormatDataJSON(t *testing.T) {
48 formatter, err := NewFormatter("json")
49 if err != nil {
50 t.Fatalf("Failed to create formatter: %v", err)
51 }
52
53 data := map[string]any{
54 "title": "Test Summary",
55 "prompt": "Summarise the news",
56 "model": "deepseek-chat",
57 "articles_count": 5,
58 "timestamp": "2024-01-15T10:00:00Z",
59 "summary": "This is a test summary.",
60 }
61
62 output, err := formatter.FormatData(data)
63 if err != nil {
64 t.Errorf("FormatData failed: %v", err)
65 }
66
67 // Verify it's valid JSON
68 var parsed map[string]any
69 if err := json.Unmarshal([]byte(output), &parsed); err != nil {
70 t.Errorf("Output is not valid JSON: %v", err)
71 }
72
73 // Verify expected fields
74 if parsed["title"] != "Test Summary" {
75 t.Errorf("Expected title 'Test Summary', got %v", parsed["title"])
76 }
77 if parsed["summary"] != "This is a test summary." {
78 t.Errorf("Expected summary 'This is a test summary.', got %v", parsed["summary"])
79 }
80}
81
82func TestFormatDataMarkdown(t *testing.T) {
83 formatter, err := NewFormatter("markdown")
84 if err != nil {
85 t.Fatalf("Failed to create formatter: %v", err)
86 }
87
88 data := map[string]any{
89 "title": "Markdown Test",
90 "prompt": "Test prompt",
91 "model": "gpt-4",
92 "articles_count": 3,
93 "summary": "Markdown summary content.",
94 }
95
96 output, err := formatter.FormatData(data)
97 if err != nil {
98 t.Errorf("FormatData failed: %v", err)
99 }
100
101 // Check for markdown structure
102 if !strings.Contains(output, "# Markdown Test") {
103 t.Error("Expected title in markdown format")
104 }
105 if !strings.Contains(output, "## Metadata") {
106 t.Error("Expected metadata section")
107 }
108 if !strings.Contains(output, "## Summary") {
109 t.Error("Expected summary section")
110 }
111 if !strings.Contains(output, "Markdown summary content.") {
112 t.Error("Expected summary content")
113 }
114}
115
116func TestFormatDataText(t *testing.T) {
117 formatter, err := NewFormatter("text")
118 if err != nil {
119 t.Fatalf("Failed to create formatter: %v", err)
120 }
121
122 data := map[string]any{
123 "title": "Text Output Test",
124 "prompt": "Test prompt",
125 "model": "claude-3",
126 "articles_count": 2,
127 "summary": "Plain text summary.",
128 }
129
130 output, err := formatter.FormatData(data)
131 if err != nil {
132 t.Errorf("FormatData failed: %v", err)
133 }
134
135 // Check for text formatting elements
136 if !strings.Contains(output, "====") {
137 t.Error("Expected separator lines")
138 }
139 if !strings.Contains(output, "Text Output Test") {
140 t.Error("Expected title in output")
141 }
142 if !strings.Contains(output, "METADATA") {
143 t.Error("Expected metadata header")
144 }
145 if !strings.Contains(output, "SUMMARY") {
146 t.Error("Expected summary header")
147 }
148}
149
150func TestFormatDataWithArticles(t *testing.T) {
151 formatter, err := NewFormatter("text")
152 if err != nil {
153 t.Fatalf("Failed to create formatter: %v", err)
154 }
155
156 articles := []map[string]any{
157 {
158 "title": "Article One",
159 "source_feed": "News Feed",
160 "author": "John Doe",
161 "link": "https://example.com/1",
162 "published": time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC),
163 },
164 {
165 "title": "Article Two",
166 "source_feed": "Tech Feed",
167 "author": "Jane Smith",
168 "link": "https://example.com/2",
169 },
170 }
171
172 data := map[string]any{
173 "title": "With Articles",
174 "summary": "Summary with articles",
175 "articles_count": 2,
176 "articles": articles,
177 }
178
179 output, err := formatter.FormatData(data)
180 if err != nil {
181 t.Errorf("FormatData failed: %v", err)
182 }
183
184 // Check articles are included
185 if !strings.Contains(output, "Article One") {
186 t.Error("Expected Article One in output")
187 }
188 if !strings.Contains(output, "Article Two") {
189 t.Error("Expected Article Two in output")
190 }
191 if !strings.Contains(output, "News Feed") {
192 t.Error("Expected feed name in output")
193 }
194}
195
196func TestFormatDataMarkdownWithArticles(t *testing.T) {
197 formatter, err := NewFormatter("markdown")
198 if err != nil {
199 t.Fatalf("Failed to create formatter: %v", err)
200 }
201
202 articles := []map[string]any{
203 {
204 "title": "MD Article",
205 "source_feed": "MD Feed",
206 "published": time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC),
207 "link": "https://example.com/md",
208 },
209 }
210
211 data := map[string]any{
212 "title": "Markdown with Articles",
213 "summary": "Test summary",
214 "articles_count": 1,
215 "articles": articles,
216 }
217
218 output, err := formatter.FormatData(data)
219 if err != nil {
220 t.Errorf("FormatData failed: %v", err)
221 }
222
223 // Check markdown article format
224 if !strings.Contains(output, "### Article 1: MD Article") {
225 t.Error("Expected markdown article header")
226 }
227 if !strings.Contains(output, "**Source**: MD Feed") {
228 t.Error("Expected source field in markdown")
229 }
230 if !strings.Contains(output, "[https://example.com/md](https://example.com/md)") {
231 t.Error("Expected link in markdown format")
232 }
233}
234
235func TestFormatDataDefaultValues(t *testing.T) {
236 formatter, err := NewFormatter("text")
237 if err != nil {
238 t.Fatalf("Failed to create formatter: %v", err)
239 }
240
241 // Data with missing fields - should use defaults
242 data := map[string]any{}
243
244 output, err := formatter.FormatData(data)
245 if err != nil {
246 t.Errorf("FormatData failed: %v", err)
247 }
248
249 // Check default values are used
250 if !strings.Contains(output, "LLM Aggregator Summary") {
251 t.Error("Expected default title")
252 }
253 if !strings.Contains(output, "No summary available.") {
254 t.Error("Expected default summary message")
255 }
256}
257
258func TestGetStringHelper(t *testing.T) {
259 tests := []struct {
260 name string
261 data map[string]any
262 key string
263 defaultValue string
264 want string
265 }{
266 {"existing string", map[string]any{"key": "value"}, "key", "default", "value"},
267 {"missing key", map[string]any{}, "key", "default", "default"},
268 {"nil value", map[string]any{"key": nil}, "key", "default", "default"},
269 {"wrong type", map[string]any{"key": 123}, "key", "default", "default"},
270 }
271
272 for _, tt := range tests {
273 t.Run(tt.name, func(t *testing.T) {
274 got := getString(tt.data, tt.key, tt.defaultValue)
275 if got != tt.want {
276 t.Errorf("getString() = %q, want %q", got, tt.want)
277 }
278 })
279 }
280}
281
282func TestGetIntHelper(t *testing.T) {
283 tests := []struct {
284 name string
285 data map[string]any
286 key string
287 defaultValue int
288 want int
289 }{
290 {"existing int", map[string]any{"count": 42}, "count", 0, 42},
291 {"existing float64", map[string]any{"count": float64(42.5)}, "count", 0, 42},
292 {"missing key", map[string]any{}, "count", 10, 10},
293 {"nil value", map[string]any{"count": nil}, "count", 10, 10},
294 {"wrong type", map[string]any{"count": "string"}, "count", 10, 10},
295 }
296
297 for _, tt := range tests {
298 t.Run(tt.name, func(t *testing.T) {
299 got := getInt(tt.data, tt.key, tt.defaultValue)
300 if got != tt.want {
301 t.Errorf("getInt() = %d, want %d", got, tt.want)
302 }
303 })
304 }
305}
306
307func TestCenterText(t *testing.T) {
308 tests := []struct {
309 name string
310 text string
311 width int
312 expect string
313 }{
314 {"short text", "Hi", 10, " Hi "}, // 3 left, 2 right (floored)
315 {"exact width", "Hello", 5, "Hello"},
316 {"longer than width", "Hello World", 5, "Hello World"},
317 {"even padding", "Test", 12, " Test "}, // 4 left, 3 right (floored)
318 }
319
320 for _, tt := range tests {
321 t.Run(tt.name, func(t *testing.T) {
322 got := centerText(tt.text, tt.width)
323 if got != tt.expect {
324 t.Errorf("centerText(%q, %d) = %q, want %q", tt.text, tt.width, got, tt.expect)
325 }
326 })
327 }
328}
329
330func TestJSONIndentation(t *testing.T) {
331 formatter, err := NewFormatter("json")
332 if err != nil {
333 t.Fatalf("Failed to create formatter: %v", err)
334 }
335
336 data := map[string]any{
337 "nested": map[string]any{
338 "key1": "value1",
339 "key2": 42,
340 },
341 "array": []int{1, 2, 3},
342 }
343
344 output, err := formatter.FormatData(data)
345 if err != nil {
346 t.Errorf("FormatData failed: %v", err)
347 }
348
349 // Check for proper indentation (two spaces)
350 lines := strings.Split(output, "\n")
351 if len(lines) < 3 {
352 t.Error("Expected multiple lines in formatted JSON")
353 }
354 if !strings.HasPrefix(lines[1], " ") {
355 t.Error("Expected two-space indentation")
356 }
357}