Skip to content
quarry Artifact [e51dcac957]
Login

Artifact [e51dcac957]

Artifact e51dcac957a36292938bcb09644faf2eded49790cfe179aecae67af25c6281fe:


package index

import (
	"testing"
	"time"

	"craftdesign.group/quarry/internal/artifact"
)

// EventKinds is a full scan of the event table — covering index or not,
// that is every artifact in the store — and the dashboard asks EVERY
// repository for it on the landing page. Measured cold across this
// fleet it was 610ms of one page's time, for numbers that only move
// when a fold does.
func TestEventKindsIsMemoisedAndRefreshes(t *testing.T) {
	ix := openIndex(t)
	if got, err := ix.EventKinds(); err != nil || len(got) != 0 {
		t.Fatalf("empty index: %v err=%v", got, err)
	}
	if err := ix.FoldNow([]Parsed{
		{Hash: "c1", Art: &artifact.Artifact{Type: artifact.Manifest,
			Date: "2026-01-01T00:00:00", User: "dan", Comment: "one"}},
	}); err != nil {
		t.Fatal(err)
	}
	got, err := ix.EventKinds()
	if err != nil {
		t.Fatal(err)
	}
	if got["ci"] != 1 {
		t.Fatalf("after fold: %v, want one check-in — the memo went stale", got)
	}
	// It IS memoised: a write behind the fold's back is not seen...
	if _, err := ix.db.Exec(`DELETE FROM event`); err != nil {
		t.Fatal(err)
	}
	if got, _ := ix.EventKinds(); got["ci"] != 1 {
		t.Error("not memoised; every dashboard render pays the scan")
	}
	// ...and a fold refreshes it, which is the only thing that moves
	// these numbers in practice. The refresh runs in the background, so
	// the census catches up rather than being current on return.
	if err := ix.FoldNow([]Parsed{
		{Hash: "c2", Art: &artifact.Artifact{Type: artifact.Manifest,
			Date: "2026-01-02T00:00:00", User: "dan", Comment: "two"}},
	}); err != nil {
		t.Fatal(err)
	}
	// Compared against the table itself rather than a number I guessed:
	// the property is that the memo matches reality after a fold, not
	// that reality holds any particular count.
	var live int
	if err := ix.db.QueryRow(`SELECT count(*) FROM event WHERE kind = 'ci'`).Scan(&live); err != nil {
		t.Fatal(err)
	}
	deadline := time.Now().Add(2 * time.Second)
	got, _ = ix.EventKinds()
	for got["ci"] != live && time.Now().Before(deadline) {
		time.Sleep(5 * time.Millisecond)
		got, _ = ix.EventKinds()
	}
	if got["ci"] != live {
		t.Errorf("after refold the memo says %d and the table holds %d", got["ci"], live)
	}
}

// The memo outlives the call, so handing a caller the map itself makes
// every reader a potential writer of everyone else's counts.
func TestEventKindsHandsBackACopy(t *testing.T) {
	ix := openIndex(t)
	if err := ix.FoldNow([]Parsed{
		{Hash: "c1", Art: &artifact.Artifact{Type: artifact.Manifest,
			Date: "2026-01-01T00:00:00", User: "dan", Comment: "one"}},
	}); err != nil {
		t.Fatal(err)
	}
	first, _ := ix.EventKinds()
	first["ci"] = 9999
	delete(first, "ci")
	second, _ := ix.EventKinds()
	if second["ci"] != 1 {
		t.Fatalf("a caller's edit reached the memo: %v", second)
	}
}