Skip to content
quarry Artifact [ea704f4484]
Login

Artifact [ea704f4484]

Artifact ea704f4484138c84187c7d7bd66fa135b96a87f3abe47f598049eeb15b797b57:


package api

import (
	"path/filepath"
	"strconv"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
)

// A poll's ballots belong to the post, not the version they were cast on
// (#375): editing text outside the poll keeps every ballot, a ballot may
// still be cast through an older version's hash, and an edit that changes
// the options starts a fresh poll.
func TestAPollKeepsItsBallotsThroughAnEdit(t *testing.T) {
	ix, z, _ := attachStore(t)
	db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	d := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix, Zit: z, Sync: zitSync(z, ix)}}),
		Removals: memRemovals{}, Forum: db}
	r := ops.NewRegistry()
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	poll := "```poll\nquestion: lunch?\n- pizza\n- ramen\n- tacos\n```\n"
	out, err := r.Invoke(as("jva", "3"), "forum.post", forumPostIn{Repo: "test", Title: "lunch", Body: "Vote below.\n\n" + poll})
	if err != nil {
		t.Fatal(err)
	}
	first := out.(forumPostOut).Hash
	vote := func(who, post string, choice int) {
		t.Helper()
		if _, err := r.Invoke(as(who, "3"), "forum.poll", pollVoteIn{Repo: "test", Post: post, Choice: choice}); err != nil {
			t.Fatalf("FAIL: %s voting through %.10s: %v", who, post, err)
		}
	}
	tally := func(who string) pollRendered {
		t.Helper()
		g, err := r.Invoke(as(who, "2"), "forum.get", forumGetIn{Repo: "test", Root: first})
		if err != nil {
			t.Fatal(err)
		}
		return g.(forumGetOut).Posts[0].Poll
	}
	vote("jva", first, 1)
	vote("jan", first, 2)

	if _, err := r.Invoke(as("jva", "3"), "forum.edit", forumEditIn{Repo: "test", Post: first, Body: "Vote below, by Friday.\n\n" + poll}); err != nil {
		t.Fatal(err)
	}
	p := tally("jva")
	if p.Total != 2 || p.Counts[1] != 1 || p.Counts[2] != 1 || p.YourChoice != 1 {
		t.Fatalf("FAIL: after an edit outside the poll: %+v", p)
	}
	t.Log("ok: an edit outside the poll keeps its ballots")

	// A reader whose page still names the first version votes, and
	// changes their mind, on the one poll.
	vote("jan", first, 0)
	if p := tally("jan"); p.Total != 2 || p.Counts[0] != 1 || p.Counts[2] != 0 || p.YourChoice != 0 {
		t.Fatalf("FAIL: a ballot cast through the older version: %+v", p)
	}
	t.Log("ok: an older version's hash still reaches the poll, one ballot per account")

	if _, err := r.Invoke(as("jva", "3"), "forum.edit", forumEditIn{Repo: "test", Post: first,
		Body: "New options.\n\n```poll\nquestion: lunch?\n- soup\n- salad\n```\n"}); err != nil {
		t.Fatal(err)
	}
	if p := tally("jva"); p.Total != 0 || p.YourChoice != -1 || len(p.Counts) != 2 {
		t.Fatalf("FAIL: changed options kept ballots cast on other answers: %+v", p)
	}
	t.Log("ok: changing the options starts a fresh poll")

	// History shows each version's poll with its ballots: the two
	// versions asking pizza/ramen/tacos carry that poll's 2, the one
	// asking soup/salad its own 0 (#376's leftover).
	h, err := r.Invoke(as("jan", "2"), "forum.history", forumHistoryIn{Repo: "test", Root: first, Post: first})
	if err != nil {
		t.Fatal(err)
	}
	vs := h.(forumHistoryOut).Versions
	if len(vs) != 3 {
		t.Fatalf("FAIL: history has %d versions, want 3", len(vs))
	}
	for _, v := range vs {
		want := 2
		if len(v.Poll.Options) == 2 {
			want = 0
		}
		if !v.Poll.Present || v.Poll.Total != want {
			t.Errorf("FAIL: version %.10s poll %+v, want present with %d ballots", v.Hash, v.Poll, want)
		}
		if !strings.Contains(string(v.HTML), strconv.Itoa(want)+" ballots") {
			t.Errorf("FAIL: version %.10s draws no %d ballots", v.Hash, want)
		}
	}
}