Skip to content
quarry Artifact [7179ce23f9]
Login

Artifact [7179ce23f9]

Artifact 7179ce23f9488b702d635c06a916f7c17931139acd8fa10607536e3c48f9ecf7:


package api

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

// alertMirrors counts failures and answers the streak, like the store.
type alertMirrors struct {
	memMirrors
	streak int
}

func (a *alertMirrors) RecordMirrorRun(_ string, _ time.Duration, _ bool, err error) (int, error) {
	if err != nil {
		a.streak++
	} else {
		a.streak = 0
	}
	return a.streak, nil
}

// An admin is told at the third failure, at the tenth, and when a
// streak that was told about ends — not on every failure, and not on
// a first blip. 29 silent failures were how the wapp outage went
// unnoticed; a ticket sat 14 days after recovery because nothing said
// so (a06c9ad3, 82eb641a).
func TestAFailingMirrorTellsAnAdminAtChosenStreaksAndOnRecovery(t *testing.T) {
	fn := &fakeNotify{}
	store := &alertMirrors{}
	fail := errors.New("dial tcp: no such host")
	d := Deps{Notify: fn, Mirrors: store, Repos: NewRepos(map[string]*Repo{"w": {
		Mirror: func(string) (bool, error) { return false, fail },
	}})}
	m := Mirror{Repo: "w", URL: "https://example.invalid/w"}
	for i := 1; i <= 12; i++ {
		m.Failures = i - 1 // what the scheduler would read before this attempt
		d.PullMirror(m)
	}
	evs := waitNotify(fn, 2)
	if len(evs) != 2 {
		t.Fatalf("FAIL: %d alerts over 12 failures, want 2 (at 3 and 10): %+v", len(evs), evs)
	}
	// fireNotify is asynchronous, so the two may arrive in either order.
	got := map[string]bool{evs[0].TxnID: true, evs[1].TxnID: true}
	if !got["mirror:w:failing:3"] || !got["mirror:w:failing:10"] {
		t.Fatalf("FAIL: alerts %s, %s", evs[0].TxnID, evs[1].TxnID)
	}
	t.Logf("ok: told at 3 and 10 of 12 failures")

	// Then it comes back.
	d.Repos = NewRepos(map[string]*Repo{"w": {Mirror: func(string) (bool, error) { return true, nil }}})
	m.Failures, m.LastAt = 12, "2026-09-22T00:00:00Z"
	d.PullMirror(m)
	evs = waitNotify(fn, 3)
	recovered := false
	for _, e := range evs {
		recovered = recovered || e.TxnID == "mirror:w:recovered:2026-09-22T00:00:00Z"
	}
	if len(evs) != 3 || !recovered {
		t.Fatalf("FAIL: recovery not told: %+v", evs)
	}
	t.Log("ok: recovery told once")

	// A single blip that recovers before the third failure says nothing.
	fn2 := &fakeNotify{}
	d2 := Deps{Notify: fn2, Mirrors: &alertMirrors{}, Repos: NewRepos(map[string]*Repo{"w": {Mirror: func(string) (bool, error) { return false, fail }}})}
	d2.PullMirror(Mirror{Repo: "w"})
	d2.Repos = NewRepos(map[string]*Repo{"w": {Mirror: func(string) (bool, error) { return true, nil }}})
	d2.PullMirror(Mirror{Repo: "w", Failures: 1})
	if got := waitNotify(fn2, 0); len(got) != 0 {
		t.Fatalf("FAIL: a one-failure blip alerted: %+v", got)
	}
	t.Log("ok: a blip is silent")
	_ = context.Background()
}