package oltp
import "testing"
func TestForumVoteRoundTrip(t *testing.T) {
db := testDB(t)
// Vote, and the score reflects it.
if err := db.SetForumVote("r", "alice", "post1", "thread1", 1); err != nil {
t.Fatal(err)
}
if err := db.SetForumVote("r", "bob", "post1", "thread1", -1); err != nil {
t.Fatal(err)
}
if err := db.SetForumVote("r", "carol", "post1", "thread1", 1); err != nil {
t.Fatal(err)
}
scores, err := db.ForumPostScores("r", []string{"post1", "absent"})
if err != nil {
t.Fatal(err)
}
if scores["post1"] != 1 {
t.Fatalf("post1 score = %d, want 1", scores["post1"])
}
if _, ok := scores["absent"]; ok {
t.Fatal("unvoted post must be absent from the map, not zero-valued in it")
}
// Changing a vote replaces it — the (repo, account, post) key is the
// idempotency: two upvotes from one account are one upvote.
if err := db.SetForumVote("r", "alice", "post1", "thread1", -1); err != nil {
t.Fatal(err)
}
scores, _ = db.ForumPostScores("r", []string{"post1"})
if scores["post1"] != -1 {
t.Fatalf("after alice flips: score = %d, want -1", scores["post1"])
}
// Retracting (dir 0) deletes the row.
if err := db.SetForumVote("r", "bob", "post1", "thread1", 0); err != nil {
t.Fatal(err)
}
scores, _ = db.ForumPostScores("r", []string{"post1"})
if scores["post1"] != 0 {
t.Fatalf("after bob retracts: score = %d, want 0", scores["post1"])
}
// Anything other than -1, 0, 1 is refused.
if err := db.SetForumVote("r", "mallory", "post1", "thread1", 5); err == nil {
t.Fatal("dir 5 accepted")
}
}
func TestForumThreadScoresAggregate(t *testing.T) {
db := testDB(t)
must := func(err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
must(db.SetForumVote("r", "alice", "p1", "t1", 1))
must(db.SetForumVote("r", "bob", "p2", "t1", 1))
must(db.SetForumVote("r", "carol", "p3", "t2", -1))
// Another repo's votes must not bleed in.
must(db.SetForumVote("other", "dave", "p1", "t1", -1))
scores, err := db.ForumThreadScores("r", []string{"t1", "t2", "t3"})
if err != nil {
t.Fatal(err)
}
if scores["t1"] != 2 || scores["t2"] != -1 {
t.Fatalf("thread scores = %v, want t1:2 t2:-1", scores)
}
if _, ok := scores["t3"]; ok {
t.Fatal("unvoted thread present in map")
}
}
func TestForumAccountVotes(t *testing.T) {
db := testDB(t)
if err := db.SetForumVote("r", "alice", "p1", "t1", 1); err != nil {
t.Fatal(err)
}
if err := db.SetForumVote("r", "alice", "p2", "t1", -1); err != nil {
t.Fatal(err)
}
mine, err := db.ForumAccountVotes("r", "alice", []string{"p1", "p2", "p3"})
if err != nil {
t.Fatal(err)
}
if mine["p1"] != 1 || mine["p2"] != -1 {
t.Fatalf("account votes = %v", mine)
}
if _, ok := mine["p3"]; ok {
t.Fatal("unvoted post present")
}
// Empty account (anonymous) asks for nothing and gets nothing.
if none, _ := db.ForumAccountVotes("r", "", []string{"p1"}); len(none) != 0 {
t.Fatalf("anonymous votes = %v, want empty", none)
}
}
func TestPurgeRepoSweepsForumVotes(t *testing.T) {
db := testDB(t)
if err := db.SetForumVote("gone", "alice", "p1", "t1", 1); err != nil {
t.Fatal(err)
}
if err := db.PurgeRepo("gone"); err != nil {
t.Fatal(err)
}
scores, err := db.ForumPostScores("gone", []string{"p1"})
if err != nil {
t.Fatal(err)
}
if len(scores) != 0 {
t.Fatalf("votes survived purge: %v", scores)
}
}
func TestThreadCategoryRoundTrip(t *testing.T) {
db := testDB(t)
if err := db.SetThreadCategory("r", "t1", Announcements); err != nil {
t.Fatal(err)
}
if err := db.SetThreadCategory("r", "t2", "qa"); err != nil {
t.Fatal(err)
}
cats, err := db.ThreadCategories("r")
if err != nil {
t.Fatal(err)
}
if cats["t1"] != Announcements || cats["t2"] != "qa" || len(cats) != 2 {
t.Fatalf("categories = %v", cats)
}
// Empty category uncategorizes; other repos never bleed in.
if err := db.SetThreadCategory("r", "t2", ""); err != nil {
t.Fatal(err)
}
if cats, _ = db.ThreadCategories("r"); len(cats) != 1 {
t.Fatalf("after uncategorize: %v", cats)
}
if other, _ := db.ThreadCategories("other"); len(other) != 0 {
t.Fatalf("cross-repo bleed: %v", other)
}
}
func TestPurgeRepoSweepsCategories(t *testing.T) {
db := testDB(t)
if err := db.SetThreadCategory("gone", "t1", Announcements); err != nil {
t.Fatal(err)
}
if err := db.PurgeRepo("gone"); err != nil {
t.Fatal(err)
}
if cats, _ := db.ThreadCategories("gone"); len(cats) != 0 {
t.Fatalf("categories survived purge: %v", cats)
}
}
func TestPollBallotRoundTrip(t *testing.T) {
db := testDB(t)
must := func(err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
choices := func(posts ...string) map[string]int {
t.Helper()
bs, err := db.PollBallots("r", posts)
if err != nil {
t.Fatal(err)
}
out := map[string]int{}
for _, b := range bs {
out[b.Account+"@"+b.Post] = b.Choice
}
return out
}
must(db.SetPollVote("r", "alice", "p1", 0))
must(db.SetPollVote("r", "bob", "p1", 1))
must(db.SetPollVote("r", "carol", "p2", 1))
if got := choices("p1"); len(got) != 2 || got["alice@p1"] != 0 || got["bob@p1"] != 1 {
t.Fatalf("FAIL: ballots on p1 = %v", got)
}
// One ballot per account and post: changing a mind replaces, never adds.
must(db.SetPollVote("r", "alice", "p1", 1))
if got := choices("p1"); len(got) != 2 || got["alice@p1"] != 1 {
t.Fatalf("FAIL: after change: %v", got)
}
// Several posts at once: the versions of one edited post.
if got := choices("p1", "p2"); len(got) != 3 || got["carol@p2"] != 1 {
t.Fatalf("FAIL: ballots on p1 and p2 = %v", got)
}
if got := choices(); len(got) != 0 {
t.Fatalf("FAIL: no posts named, yet ballots came back: %v", got)
}
}
func TestPurgeRepoSweepsPollVotes(t *testing.T) {
db := testDB(t)
if err := db.SetPollVote("gone", "alice", "p1", 0); err != nil {
t.Fatal(err)
}
if err := db.PurgeRepo("gone"); err != nil {
t.Fatal(err)
}
if bs, _ := db.PollBallots("gone", []string{"p1"}); len(bs) != 0 {
t.Fatalf("ballots survived purge: %v", bs)
}
}