Artifact 85bc5b9ad981188fa02447dea597c651fc9d6d3af45361934dc505947c2d82a1:
- File
internal/api/forum_test.go
— part of check-in
[cf172ec6c6]
at
2026-08-21 19:19:18
on branch quarry-forum
— forum hot tab: published HN-style decay ranking over votes
Bounded to threads active in 30 days; formula printed on the page. Hand-computed order pinned in TestSortForumHot. (user: dmestas, size: 1666) [annotate] [blame] [check-ins using] [more...]
package api import ( "testing" "time" "craftdesign.group/quarry/internal/index" ) // The hot order must be reproducible by hand from the published formula // — legibility is the feature. Fixed clock, hand-computed expectations. func TestSortForumHot(t *testing.T) { now := time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC) mk := func(root, created, updated string, posts, score int) forumThreadOut { return forumThreadOut{ForumThread: index.ForumThread{ Root: root, Created: created, Updated: updated, Posts: posts, }, Score: score} } threads := []forumThreadOut{ // Recency order in, as forum.list produces it. mk("fresh-quiet", "2026-08-21T10:00:00", "2026-08-21T10:00:00", 1, 0), // 2h old, no votes: rank 0 mk("hot-now", "2026-08-21T08:00:00", "2026-08-21T11:00:00", 3, 5), // 4h old, 5 votes + 2 replies mk("old-heavy", "2026-08-01T12:00:00", "2026-08-10T12:00:00", 9, 40), // 20 days old, heavy but decayed mk("dormant-a", "2026-05-01T12:00:00", "2026-06-01T12:00:00", 2, 100), // outside the 30-day window mk("dormant-b", "2026-04-01T12:00:00", "2026-05-01T12:00:00", 2, 200), // outside, older activity } sortForumHot(threads, now) var got []string for _, th := range threads { got = append(got, th.Root) } // hot-now: (5+1)/(6)^1.8 ≈ 0.238; old-heavy: (40+4)/(482)^1.8 ≈ 0.0006; // fresh-quiet: 0. Dormant threads follow in their arrival (recency) // order regardless of score — the window bounds the ranked set. want := []string{"hot-now", "old-heavy", "fresh-quiet", "dormant-a", "dormant-b"} for i := range want { if got[i] != want[i] { t.Fatalf("hot order = %v, want %v", got, want) } } }