Skip to content
quarry Artifact [018b9c13de]
Login

Artifact [018b9c13de]

Artifact 018b9c13def43802de45b687abba495752a48c27f2e1a79b85b7484de52d8233:


package index

import (
	"path/filepath"
	"testing"
)

// Counts takes includePrivate and its ticket number depends on it — but
// the memo behind it was ONE slot, so whoever asked first decided what
// everyone got until the next fold. An admin loading any page warmed it
// with private tickets counted, and the next anonymous reader was told
// how many private tickets the tracker holds.
//
// A count is small, and it is still the answer to a question the reader
// was not allowed to ask.
func TestCountsMemoDoesNotLeakPrivateTickets(t *testing.T) {
	ix, err := Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer ix.Close()
	ins := func(id string, private int) {
		if _, err := ix.db.Exec(`INSERT INTO ticket
			(id, created, updated, title, status, private)
			VALUES (?, '2026-01-01', '2026-01-01', 'x', 'Open', ?)`, id, private); err != nil {
			t.Fatal(err)
		}
	}
	ins("public-1", 0)
	ins("secret-1", 1)
	ins("secret-2", 1)

	// The admin looks first and warms the memo.
	all, err := ix.Counts(true)
	if err != nil {
		t.Fatal(err)
	}
	if all.Tickets != 3 {
		t.Fatalf("admin sees %d open tickets, want all 3", all.Tickets)
	}
	// Now somebody who may not see private tickets asks.
	pub, err := ix.Counts(false)
	if err != nil {
		t.Fatal(err)
	}
	if pub.Tickets != 1 {
		t.Errorf("a reader without private access is told there are %d open tickets, "+
			"want 1: the memo is serving the admin's count", pub.Tickets)
	}

	// And the other way round: a public reader first must not leave the
	// admin under-counting their own tracker.
	ix.invalidateCounts()
	if _, err := ix.Counts(false); err != nil {
		t.Fatal(err)
	}
	again, err := ix.Counts(true)
	if err != nil {
		t.Fatal(err)
	}
	if again.Tickets != 3 {
		t.Errorf("admin sees %d after a public reader warmed the memo, want 3", again.Tickets)
	}
}