package api
import (
"errors"
"os"
"strings"
"testing"
"craftdesign.group/quarry/internal/ingest"
"craftdesign.group/quarry/internal/ops"
)
// A settle folds the index first, on purpose: "a settle is a
// deliberate, rare act and can afford one ingest round to be sure what
// it is settling". synced() then swallowed ErrStalled and answered nil,
// so a stalled ingest read as a successful fold and the accept went on
// to merge onto whatever tip the stale index still held.
//
// Ticket b7c678a5: the merge's first parent was four hours old, trunk
// grew a second leaf, and thirteen check-ins' files left the tree. The
// landing reported behind:0 the whole time, because behind is counted
// from the same index that had not moved.
func TestASettleWillNotProceedOnAStalledIndex(t *testing.T) {
stalled := &Repo{Sync: func() (int, error) { return 0, ingest.ErrStalled }}
err := stalled.settleSynced()
if err == nil {
t.Error("FAIL: a settle proceeded while the ingest was stalled")
} else if !errors.Is(err, ops.ErrUnavailable) {
t.Errorf("FAIL: refused with %v, want ErrUnavailable so a caller can retry", err)
} else {
t.Logf("ok: refused — %v", err)
}
// A READ is unchanged: a page may lag, it may only never lie about
// it, and refusing every page during a backoff would be worse than
// the lag it guards.
if err := stalled.synced(); err != nil {
t.Errorf("FAIL: a read path now refuses during a backoff: %v", err)
} else {
t.Log("ok: reads still tolerate a stall")
}
// A healthy fold settles as before, and a real failure still
// surfaces rather than being turned into a stall message.
healthy := &Repo{Sync: func() (int, error) { return 3, nil }}
if err := healthy.settleSynced(); err != nil {
t.Errorf("FAIL: a healthy fold refused: %v", err)
} else {
t.Log("ok: a healthy fold settles")
}
boom := errors.New("disk on fire")
broken := &Repo{Sync: func() (int, error) { return 0, boom }}
if err := broken.settleSynced(); !errors.Is(err, boom) {
t.Errorf("FAIL: a real ingest error became %v", err)
} else {
t.Log("ok: a real error is still itself")
}
// A forge with no ingest at all has nothing to be stale.
if err := (&Repo{}).settleSynced(); err != nil {
t.Errorf("FAIL: a forge with no Sync refused: %v", err)
} else {
t.Log("ok: no ingest, nothing to wait for")
}
}
// And the settle path must actually CALL the strict one. Testing the
// helper alone would pass forever while somebody put synced() back at
// the call site, which is the exact line that caused b7c678a5.
func TestTheSettlePathCallsTheStrictFold(t *testing.T) {
src, err := os.ReadFile("register.go")
if err != nil {
t.Fatal(err)
}
i := strings.Index(string(src), `if verb != "comment" {`)
if i < 0 {
t.Fatal("FAIL: the settle's fold is not where this guard looks any more")
}
block := string(src)[i : i+400]
if !strings.Contains(block, "repo.settleSynced()") {
t.Errorf("FAIL: the settle folds with the lenient synced(); a stalled index reads as folded:\n%s", block[:200])
} else {
t.Log("ok: the settle folds strictly")
}
}