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)
}
}
}