all repos — llm_aggregator @ 29a4710e981512a82abba77ec8d7b43e413453e7

A CLI tool to aggregate RSS feeds and summarise them with LLMs

internal/aggregator/article_test.go (view raw)

  1package aggregator
  2
  3import (
  4	"strings"
  5	"testing"
  6	"time"
  7)
  8
  9func TestArticleToMap(t *testing.T) {
 10	tests := []struct {
 11		name     string
 12		article  *Article
 13		checkFn  func(map[string]any) bool
 14	}{
 15		{
 16			name: "full article with all fields",
 17			article: &Article{
 18				Title:      "Test Article Title",
 19				Link:       "https://example.com/article",
 20				Content:    "This is the article content",
 21				Published:  time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
 22				Author:     "John Doe",
 23				SourceFeed: "Test Feed",
 24				Summary:    "This is a summary",
 25			},
 26			checkFn: func(m map[string]any) bool {
 27				return m["title"] == "Test Article Title" &&
 28					m["link"] == "https://example.com/article" &&
 29					m["author"] == "John Doe" &&
 30					m["source_feed"] == "Test Feed" &&
 31					m["summary"] == "This is a summary" &&
 32					strings.Contains(m["content"].(string), "article content")
 33			},
 34		},
 35		{
 36			name: "article with truncated content",
 37			article: &Article{
 38				Title:   "Long Article",
 39				Link:    "https://example.com/long",
 40				Content: strings.Repeat("x", 600), // More than 500 chars
 41			},
 42			checkFn: func(m map[string]any) bool {
 43				content := m["content"].(string)
 44				// 600 chars > 500, so truncated to 500 + "... [truncated]" (15 chars) = 515
 45				return len(content) == 515 &&
 46					strings.HasSuffix(content, "... [truncated]")
 47			},
 48		},
 49		{
 50			name: "article with zero time",
 51			article: &Article{
 52				Title:     "No Date Article",
 53				Link:      "https://example.com/nodate",
 54				Content:   "Content",
 55				Published: time.Time{}, // Zero time
 56			},
 57			checkFn: func(m map[string]any) bool {
 58				_, hasPublished := m["published"]
 59				return !hasPublished
 60			},
 61		},
 62		{
 63			name: "article without author",
 64			article: &Article{
 65				Title:     "Anonymous Article",
 66				Link:      "https://example.com/anon",
 67				Content:   "Content",
 68				Author:    "",
 69				SourceFeed: "Feed",
 70			},
 71			checkFn: func(m map[string]any) bool {
 72				return m["author"] == ""
 73			},
 74		},
 75	}
 76
 77	for _, tt := range tests {
 78		t.Run(tt.name, func(t *testing.T) {
 79			result := tt.article.ToMap()
 80			if !tt.checkFn(result) {
 81				t.Errorf("ToMap() produced unexpected result: %v", result)
 82			}
 83		})
 84	}
 85}
 86
 87func TestArticleStruct(t *testing.T) {
 88	t.Run("empty article", func(t *testing.T) {
 89		article := &Article{}
 90		m := article.ToMap()
 91
 92		if m["title"] != "" {
 93			t.Errorf("Expected empty title, got %q", m["title"])
 94		}
 95		if m["link"] != "" {
 96			t.Errorf("Expected empty link, got %q", m["link"])
 97		}
 98	})
 99
100	t.Run("article with only title and link", func(t *testing.T) {
101		article := &Article{
102			Title: "Minimal Article",
103			Link:  "https://example.com/minimal",
104		}
105		m := article.ToMap()
106
107		if m["title"] != "Minimal Article" {
108			t.Errorf("Expected title 'Minimal Article', got %q", m["title"])
109		}
110		if m["link"] != "https://example.com/minimal" {
111			t.Errorf("Expected link 'https://example.com/minimal', got %q", m["link"])
112		}
113	})
114
115	t.Run("article with future date", func(t *testing.T) {
116		futureDate := time.Now().Add(24 * time.Hour)
117		article := &Article{
118			Title:     "Future Article",
119			Link:      "https://example.com/future",
120			Content:   "Content",
121			Published: futureDate,
122		}
123		m := article.ToMap()
124
125		if published, ok := m["published"].(string); !ok {
126			t.Error("Expected published field to be set")
127		} else if !strings.Contains(published, futureDate.Format("2006-01-02")) {
128			t.Errorf("Expected published date %s, got %s", futureDate.Format(time.RFC3339), published)
129		}
130	})
131}