Skip to content
quarry Artifact [ff5cc63450]
Login

Artifact [ff5cc63450]

Artifact ff5cc63450de9a5565f5fbfbb338d6501b91aa7c876a15d73850545f60cde87a:


package oltp

import (
	"errors"
	"testing"
	"time"
)

// The wait after the nth failure in a row is a minute, doubling, at most
// an hour: the one schedule the runners obey and the page reports.
func TestMirrorBackoffIsOneSchedule(t *testing.T) {
	for fails, want := range map[int]time.Duration{0: time.Minute, 1: time.Minute, 2: 2 * time.Minute, 3: 4 * time.Minute,
		4: 8 * time.Minute, 6: 32 * time.Minute, 7: time.Hour, 50: time.Hour} {
		if got := MirrorBackoff(fails); got != want {
			t.Errorf("FAIL: backoff after %d failures is %v, want %v", fails, got, want)
		}
	}
	at := "2026-10-01T16:00:00Z"
	next, ok := MirrorNextTry(at, 3)
	if !ok || !next.Equal(time.Date(2026, 10, 1, 16, 4, 0, 0, time.UTC)) {
		t.Errorf("FAIL: next try %v %v", next, ok)
	}
	if _, ok := MirrorNextTry("", 3); ok {
		t.Error("FAIL: a mirror that did not fail has a next try")
	}
	if _, ok := MirrorNextTry("not a time", 3); ok {
		t.Error("FAIL: an unreadable stamp has a next try")
	}
	t.Log("ok")
}

// A refusal names the input it is about, still answers to
// ErrMirrorRefused, and reads as it always did.
func TestMirrorRefusalsNameTheirField(t *testing.T) {
	db := heldDB(t)
	cases := []struct {
		name, field string
		err         error
	}{
		{"fossil remote", "remote", CheckFossilRemote("https://u:[email protected]/x")},
		{"fossil user", "user", checkFossilUser("")},
		{"git remote", "remote", CheckGitRemote("https://github.com/o/r.git")},
		{"git branch", "branch", checkGitBranch("-x")},
	}
	_, pwErr := db.SetFossilMirror("proj", fmRemote, "pusher", "", "alice", "")
	cases = append(cases, struct {
		name, field string
		err         error
	}{"fossil password", "password", pwErr})
	for _, c := range cases {
		if c.err == nil || MirrorRefusalField(c.err) != c.field || c.err.Error()[:len("refused: ")] != "refused: " {
			t.Errorf("FAIL: %s: field %q, error %v", c.name, MirrorRefusalField(c.err), c.err)
		}
	}
	if MirrorRefusalField(nil) != "" || MirrorRefusalField(ErrMirrorRefused) != "" {
		t.Error("FAIL: a refusal about no field names one")
	}
	t.Log("ok")
}

// A failed push keeps the last good one, and each further failure
// counts in a row, for either mirror; a success clears the count.
func TestAFailureKeepsTheLastGoodPushAndCounts(t *testing.T) {
	db := heldDB(t)
	f, err := db.SetFossilMirror("proj", fmRemote, "pusher", "pw-x", "alice", "")
	if err != nil {
		t.Fatal(err)
	}
	if err := db.RecordFossilMirrorPush("proj", f.Gen, "tip1"); err != nil {
		t.Fatal(err)
	}
	for i := 0; i < 2; i++ {
		if err := db.RecordFossilMirrorFailure("proj", f.Gen, "the server could not be reached"); err != nil {
			t.Fatal(err)
		}
	}
	m, _, _ := db.FossilMirrorOf("proj")
	if m.Pushed != "tip1" || m.PushedAt == "" || m.Err == "" || m.Fails != 2 {
		t.Errorf("FAIL: fossil after two failures: %+v", m)
	}

	g, err := db.SetGitMirror("proj", "[email protected]:o/r.git", "main", "alice", "")
	if err != nil {
		t.Fatal(err)
	}
	if err := db.RecordGitMirrorPush("proj", g.PublicKey, g.Remote, g.Branch, "tip2"); err != nil {
		t.Fatal(err)
	}
	if err := db.RecordGitMirrorFailure("proj", g.PublicKey, g.Remote, g.Branch, "the host could not be reached"); err != nil {
		t.Fatal(err)
	}
	gm, _, _ := db.GitMirrorOf("proj")
	if gm.Pushed != "tip2" || gm.PushedAt == "" || gm.Err == "" || gm.Fails != 1 {
		t.Errorf("FAIL: git after a failure: %+v", gm)
	}
	if err := db.RecordFossilMirrorPush("proj", f.Gen, "tip3"); err != nil {
		t.Fatal(err)
	}
	m, _, _ = db.FossilMirrorOf("proj")
	if m.Fails != 0 || m.Err != "" || m.Pushed != "tip3" {
		t.Errorf("FAIL: a success did not clear the failures: %+v", m)
	}
	t.Log("ok")
}

// A password with no remote and no user replaces the password of a mirror
// that is on and keeps the stored remote and user, even when another
// admin moved them since the page loaded; it bumps the generation like
// any save, and is refused where any save is.
func TestPasswordOnlySaveKeepsTheStoredDestination(t *testing.T) {
	db := heldDB(t)
	if _, err := db.SetFossilMirror("proj", "", "", "pw-new", "alice", ""); !errors.Is(err, ErrMirrorRefused) || MirrorRefusalField(err) != "remote" {
		t.Fatalf("FAIL: a password alone on a mirror that is off: %v", err)
	}
	first, err := db.SetFossilMirror("proj", fmRemote, "pusher", "pw-old", "alice", "")
	if err != nil {
		t.Fatal(err)
	}
	// Someone else moves the mirror.
	const moved = "https://moved.example.org/proj"
	moved1, err := db.SetFossilMirror("proj", moved, "pusher", "pw-moved", "bob", "")
	if err != nil {
		t.Fatal(err)
	}
	m, err := db.SetFossilMirror("proj", "", "", "pw-replaced", "alice", "")
	if err != nil {
		t.Fatalf("FAIL: password only: %v", err)
	}
	if m.Remote != moved || m.User != "pusher" || m.Gen <= moved1.Gen || m.Gen == first.Gen {
		t.Errorf("FAIL: the destination was moved back or the generation kept: %+v", m)
	}
	if got, err := db.FossilMirrorPassword("proj", m.Gen); err != nil || got != "pw-replaced" {
		t.Errorf("FAIL: the password was not replaced: %v", err)
	}
	if _, err := db.FossilMirrorPassword("proj", moved1.Gen); !errors.Is(err, ErrFossilMirrorChanged) {
		t.Error("FAIL: a push of the old generation can still read the password")
	}
	// Blank everything is not a password-only save.
	if _, err := db.SetFossilMirror("proj", "", "", "", "alice", ""); !errors.Is(err, ErrMirrorRefused) {
		t.Errorf("FAIL: an empty save: %v", err)
	}
	if err := db.SetTakedown(Takedown{Repo: "proj", Strength: Disabled, Category: "copyright", Actor: "dmestas"}); err != nil {
		t.Fatal(err)
	}
	if _, err := db.SetFossilMirror("proj", "", "", "pw-x", "alice", ""); !errors.Is(err, ErrHeld) {
		t.Errorf("FAIL: password only under a takedown: %v", err)
	}
	t.Log("ok")
}