Skip to content
quarry Artifact [c902c75bde]
Login

Artifact [c902c75bde]

Artifact c902c75bde0ed4253f5a746c3a1cf3bc6149f1af4e1b0ad71c0cf70c9cb44345:


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