main
1package flux
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7 "time"
8)
9
10func TestStoreMergeDeduplicates(t *testing.T) {
11 s := &Store{
12 Entries: []Entry{
13 {ID: "a", Title: "First", Date: time.Now()},
14 {ID: "b", Title: "Second", Date: time.Now()},
15 },
16 }
17
18 added := s.Merge([]Entry{
19 {ID: "b", Title: "Second (dup)", Date: time.Now()},
20 {ID: "c", Title: "Third", Date: time.Now()},
21 })
22
23 if added != 1 {
24 t.Errorf("Merge() added = %d, want 1", added)
25 }
26 if len(s.Entries) != 3 {
27 t.Errorf("len(Entries) = %d, want 3", len(s.Entries))
28 }
29}
30
31func TestStoreSort(t *testing.T) {
32 now := time.Now()
33 s := &Store{
34 Entries: []Entry{
35 {ID: "old", Date: now.Add(-48 * time.Hour)},
36 {ID: "new", Date: now},
37 {ID: "mid", Date: now.Add(-24 * time.Hour)},
38 },
39 }
40
41 s.Sort()
42
43 if s.Entries[0].ID != "new" {
44 t.Errorf("first entry = %s, want new", s.Entries[0].ID)
45 }
46 if s.Entries[2].ID != "old" {
47 t.Errorf("last entry = %s, want old", s.Entries[2].ID)
48 }
49}
50
51func TestStoreLatest(t *testing.T) {
52 s := &Store{
53 Entries: []Entry{
54 {ID: "1"}, {ID: "2"}, {ID: "3"}, {ID: "4"}, {ID: "5"},
55 },
56 }
57
58 latest := s.Latest(3)
59 if len(latest) != 3 {
60 t.Errorf("Latest(3) = %d entries, want 3", len(latest))
61 }
62
63 all := s.Latest(100)
64 if len(all) != 5 {
65 t.Errorf("Latest(100) = %d entries, want 5", len(all))
66 }
67}
68
69func TestStoreByTag(t *testing.T) {
70 s := &Store{
71 Entries: []Entry{
72 {ID: "1", Tags: []string{"go", "nixos"}},
73 {ID: "2", Tags: []string{"nixos"}},
74 {ID: "3", Tags: []string{"emacs"}},
75 },
76 }
77
78 nixos := s.ByTag("nixos")
79 if len(nixos) != 2 {
80 t.Errorf("ByTag(nixos) = %d, want 2", len(nixos))
81 }
82
83 tags := s.AllTags()
84 if tags["nixos"] != 2 {
85 t.Errorf("AllTags()[nixos] = %d, want 2", tags["nixos"])
86 }
87}
88
89func TestStoreSaveLoad(t *testing.T) {
90 dir := t.TempDir()
91 path := filepath.Join(dir, "test.json")
92
93 original := &Store{
94 Entries: []Entry{
95 {ID: "test-1", Kind: KindNote, Title: "Test", Date: time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)},
96 },
97 }
98
99 if err := original.Save(path); err != nil {
100 t.Fatalf("Save: %v", err)
101 }
102
103 loaded, err := LoadStore(path)
104 if err != nil {
105 t.Fatalf("LoadStore: %v", err)
106 }
107
108 if len(loaded.Entries) != 1 {
109 t.Fatalf("loaded %d entries, want 1", len(loaded.Entries))
110 }
111 if loaded.Entries[0].ID != "test-1" {
112 t.Errorf("loaded ID = %s, want test-1", loaded.Entries[0].ID)
113 }
114}
115
116func TestLoadStoreNotExist(t *testing.T) {
117 s, err := LoadStore("/nonexistent/path.json")
118 if err != nil {
119 t.Fatalf("LoadStore nonexistent: %v", err)
120 }
121 if len(s.Entries) != 0 {
122 t.Errorf("expected empty store, got %d entries", len(s.Entries))
123 }
124}
125
126func TestStoreByYear(t *testing.T) {
127 s := &Store{
128 Entries: []Entry{
129 {ID: "1", Date: time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)},
130 {ID: "2", Date: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)},
131 {ID: "3", Date: time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC)},
132 },
133 }
134
135 byYear := s.ByYear()
136 if len(byYear[2026]) != 2 {
137 t.Errorf("2026 entries = %d, want 2", len(byYear[2026]))
138 }
139 if len(byYear[2025]) != 1 {
140 t.Errorf("2025 entries = %d, want 1", len(byYear[2025]))
141 }
142}
143
144func TestRenderOutputs(t *testing.T) {
145 dir := t.TempDir()
146 store := &Store{
147 Entries: []Entry{
148 {ID: "note-1", Kind: KindNote, Title: "Test Note", Body: "Hello world", Tags: []string{"test"}, Date: time.Date(2026, 4, 1, 12, 0, 0, 0, time.UTC), Source: "manual"},
149 {ID: "github-pr-test/repo#1", Kind: KindGitHubPR, Title: "Fix thing", URL: "https://github.com/test/repo/pull/1", Date: time.Date(2026, 3, 15, 10, 0, 0, 0, time.UTC), Source: "github", Metadata: map[string]string{"repo": "test/repo", "number": "1", "state": "merged"}},
150 },
151 }
152 store.Sort()
153
154 cfg := Config{
155 Title: "Test Flux",
156 Description: "Test",
157 Author: "Test",
158 BaseURL: "https://example.com/flux",
159 SiteURL: "https://example.com",
160 OutputDir: dir,
161 }
162
163 if err := RenderJSONFeed(cfg, store.Entries); err != nil {
164 t.Fatalf("RenderJSONFeed: %v", err)
165 }
166 if _, err := os.Stat(filepath.Join(dir, "feed.json")); err != nil {
167 t.Errorf("feed.json not created: %v", err)
168 }
169
170 if err := RenderAtomFeed(cfg, store.Entries); err != nil {
171 t.Fatalf("RenderAtomFeed: %v", err)
172 }
173 if _, err := os.Stat(filepath.Join(dir, "feed.xml")); err != nil {
174 t.Errorf("feed.xml not created: %v", err)
175 }
176
177 if err := RenderHTML(cfg, store); err != nil {
178 t.Fatalf("RenderHTML: %v", err)
179 }
180 if _, err := os.Stat(filepath.Join(dir, "index.html")); err != nil {
181 t.Errorf("index.html not created: %v", err)
182 }
183 if _, err := os.Stat(filepath.Join(dir, "2026.html")); err != nil {
184 t.Errorf("2026.html not created: %v", err)
185 }
186 if _, err := os.Stat(filepath.Join(dir, "tags", "test.html")); err != nil {
187 t.Errorf("tags/test.html not created: %v", err)
188 }
189}