Antonio Mika
·
2026-10-10
1package prose
2
3import (
4 "log/slog"
5 "testing"
6 "time"
7
8 "github.com/picosh/pico/pkg/db"
9 "github.com/picosh/pico/pkg/db/postgres"
10 "github.com/picosh/pico/pkg/db/postgres/pgtest"
11)
12
13var testDB *PsqlDB
14
15func TestMain(m *testing.M) {
16 pgtest.Main(m)
17}
18
19func cleanupTestData(t *testing.T) {
20 t.Helper()
21 if testDB == nil {
22 testDB = NewDB(postgres.NewDB(pgtest.URL(t), slog.New(slog.DiscardHandler)))
23 }
24 pgtest.Clean(t, testDB.Db)
25 testDB.ClearPopularPostsCache()
26}
27
28func mustInsertPost(t *testing.T, post *db.Post) *db.Post {
29 t.Helper()
30 return pgtest.InsertPost(t, testDB, post)
31}
32
33func TestFindPopularPosts(t *testing.T) {
34 cleanupTestData(t)
35
36 user, _ := testDB.RegisterUser("feedowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feedowner", "comment", "")
37
38 now := time.Now()
39 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "feedpost.md", Slug: "feedpost", Title: "Feed Post", Space: "prose", PublishAt: &now})
40
41 // Before having feature flag, should not appear
42 posts, err := testDB.FindPopularPosts()
43 if err != nil {
44 t.Fatalf("FindPopularPosts failed: %v", err)
45 }
46 if len(posts) != 0 {
47 t.Errorf("expected 0 posts without feature flag, got %d", len(posts))
48 }
49
50 // Add prose feature flag
51 _, err = testDB.InsertFeature(user.ID, "prose", time.Now().Add(time.Hour*24))
52 if err != nil {
53 t.Fatalf("InsertFeature failed: %v", err)
54 }
55 testDB.ClearPopularPostsCache()
56
57 posts, err = testDB.FindPopularPosts()
58 if err != nil {
59 t.Fatalf("FindPopularPosts failed: %v", err)
60 }
61 if len(posts) != 1 {
62 t.Fatalf("expected 1 post in popular posts, got %d", len(posts))
63 }
64 if posts[0].Score != 0 {
65 t.Errorf("expected score 0, got %d", posts[0].Score)
66 }
67
68 // Add visits
69 _ = testDB.InsertVisit(&db.AnalyticsVisits{UserID: user.ID, PostID: post.ID, IpAddress: "192.168.1.1", Status: 200})
70 _ = testDB.InsertVisit(&db.AnalyticsVisits{UserID: user.ID, PostID: post.ID, IpAddress: "192.168.1.2", Status: 200})
71 _ = testDB.InsertVisit(&db.AnalyticsVisits{UserID: user.ID, PostID: post.ID, IpAddress: "192.168.1.1", Status: 200}) // duplicate IP
72
73 // Before clearing cache, should return cached result (score 0)
74 cachedPosts, err := testDB.FindPopularPosts()
75 if err != nil {
76 t.Fatalf("FindPopularPosts failed: %v", err)
77 }
78 if cachedPosts[0].Score != 0 {
79 t.Errorf("expected cached score 0, got %d", cachedPosts[0].Score)
80 }
81
82 // After clearing cache, should re-query and return updated score 2
83 testDB.ClearPopularPostsCache()
84 posts, err = testDB.FindPopularPosts()
85 if err != nil {
86 t.Fatalf("FindPopularPosts failed: %v", err)
87 }
88 if len(posts) != 1 {
89 t.Fatalf("expected 1 post, got %d", len(posts))
90 }
91 if posts[0].Score != 2 {
92 t.Errorf("expected score 2 (unique visitors), got %d", posts[0].Score)
93 }
94
95 // Verify max 3 posts per user
96 testDB.ClearPopularPostsCache()
97 post2Time := now.Add(-1 * time.Hour)
98 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "feedpost2.md", Slug: "feedpost2", Title: "Feed Post 2", Space: "prose", PublishAt: &post2Time})
99 post3Time := now.Add(-2 * time.Hour)
100 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "feedpost3.md", Slug: "feedpost3", Title: "Feed Post 3", Space: "prose", PublishAt: &post3Time})
101 post4Time := now.Add(-3 * time.Hour)
102 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "feedpost4.md", Slug: "feedpost4", Title: "Feed Post 4", Space: "prose", PublishAt: &post4Time})
103
104 posts, err = testDB.FindPopularPosts()
105 if err != nil {
106 t.Fatalf("FindPopularPosts failed: %v", err)
107 }
108 if len(posts) != 3 {
109 t.Errorf("expected max 3 posts for single user, got %d", len(posts))
110 }
111
112 // Post older than 30 days should be excluded
113 testDB.ClearPopularPostsCache()
114 oldTime := now.Add(-31 * 24 * time.Hour)
115 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "oldpost.md", Slug: "oldpost", Title: "Old Post", Space: "prose", PublishAt: &oldTime})
116
117 posts, err = testDB.FindPopularPosts()
118 if err != nil {
119 t.Fatalf("FindPopularPosts failed: %v", err)
120 }
121 if len(posts) != 3 {
122 t.Errorf("expected still 3 posts (old post excluded), got %d", len(posts))
123 }
124}
125
126// ============ Tags Tests ============
127
128func TestFindUserPostsByTag(t *testing.T) {
129 cleanupTestData(t)
130
131 user, _ := testDB.RegisterUser("usertagowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI usertagowner", "comment", "")
132 now := time.Now()
133 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "usertag.md", Slug: "usertag", Title: "User Tag", Space: "prose", PublishAt: &now})
134 _ = testDB.ReplaceTagsByPost([]string{"mytag"}, post.ID)
135
136 pager := &db.Pager{Num: 10, Page: 0}
137 result, err := testDB.FindUserPostsByTag(pager, "mytag", user.ID, "prose")
138 if err != nil {
139 t.Fatalf("FindUserPostsByTag failed: %v", err)
140 }
141 if len(result.Data) != 1 {
142 t.Errorf("expected 1 post with tag, got %d", len(result.Data))
143 }
144}
145
146func TestFindPostsByTag(t *testing.T) {
147 cleanupTestData(t)
148
149 user, _ := testDB.RegisterUser("tagsearchowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tagsearchowner", "comment", "")
150 now := time.Now()
151 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "tagsearch.md", Slug: "tagsearch", Title: "Tag Search", Space: "prose", PublishAt: &now})
152 _ = testDB.ReplaceTagsByPost([]string{"searchtag"}, post.ID)
153
154 pager := &db.Pager{Num: 10, Page: 0}
155 result, err := testDB.FindPostsByTag(pager, "searchtag", "prose")
156 if err != nil {
157 t.Fatalf("FindPostsByTag failed: %v", err)
158 }
159 if len(result.Data) != 1 {
160 t.Errorf("expected 1 post with tag, got %d", len(result.Data))
161 }
162}
163
164func TestFindPopularTags(t *testing.T) {
165 cleanupTestData(t)
166
167 user, _ := testDB.RegisterUser("populartagowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI populartagowner", "comment", "")
168 post1 := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "pop1.md", Slug: "pop1", Title: "Pop 1", Space: "prose"})
169 post2 := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "pop2.md", Slug: "pop2", Title: "Pop 2", Space: "prose"})
170 _ = testDB.ReplaceTagsByPost([]string{"popular"}, post1.ID)
171 _ = testDB.ReplaceTagsByPost([]string{"popular"}, post2.ID)
172
173 tags, err := testDB.FindPopularTags("prose")
174 if err != nil {
175 t.Fatalf("FindPopularTags failed: %v", err)
176 }
177 if len(tags) < 1 {
178 t.Errorf("expected at least 1 popular tag, got %d", len(tags))
179 }
180}
181
182// ============ Aliases Tests ============