Skip to content
quarry Artifact [913c697181]
Login

Artifact [913c697181]

Artifact 913c6971817a2d72de8ee6bb221755b012288b1c65517460387ad1331e41ab85:


package oltp

import (
	"errors"
	"path/filepath"
	"testing"
	"time"
)

func openTest(t *testing.T) *DB {
	t.Helper()
	d, err := Open(filepath.Join(t.TempDir(), "oltp.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { d.Close() })
	return d
}

// Posture changes: a URL change forgets failures about the old peer,
// an interval change keeps them; detaching keeps the URL as provenance
// and re-attaching clears it; writes are refused until allowed.
func TestMirrorPosture(t *testing.T) {
	d := openTest(t)
	if err := d.SetMirror("w", "https://old.example/home", time.Hour); err != nil {
		t.Fatal(err)
	}
	at := time.Date(2026, 8, 21, 16, 0, 0, 0, time.UTC)
	_ = d.RecordMirrorRun("w", at, errors.New("cannot connect"))
	_ = d.RecordMirrorRun("w", at, errors.New("cannot connect"))

	if err := d.SetMirror("w", "https://old.example/home", 2*time.Hour); err != nil {
		t.Fatal(err)
	}
	m, _, _ := d.MirrorFor("w")
	if m.Failures != 2 || m.LastErr == "" {
		t.Fatalf("interval change must keep the failure history: %+v", m)
	}
	if m.AcceptWrites || !m.Tracking() {
		t.Fatalf("a fresh mirror tracks and refuses writes: %+v", m)
	}

	if err := d.SetMirror("w", "https://new.example/home", 2*time.Hour); err != nil {
		t.Fatal(err)
	}
	m, _, _ = d.MirrorFor("w")
	if m.Failures != 0 || m.LastErr != "" {
		t.Fatalf("a URL change must clear failures about the old peer: %+v", m)
	}

	if err := d.SetMirrorWrites("w", true); err != nil {
		t.Fatal(err)
	}
	if m, _, _ = d.MirrorFor("w"); !m.AcceptWrites {
		t.Fatal("accept_writes not persisted")
	}
	if err := d.SetMirrorWrites("nope", true); err == nil {
		t.Fatal("writes flag on a non-mirror must fail")
	}

	if err := d.DetachMirror("w"); err != nil {
		t.Fatal(err)
	}
	m, ok, _ := d.MirrorFor("w")
	if !ok || m.Tracking() || m.DetachedFrom != "https://new.example/home" {
		t.Fatalf("detach keeps the row with provenance and stops tracking: %+v", m)
	}
	if err := d.SetMirror("w", "https://new.example/home", time.Hour); err != nil {
		t.Fatal(err)
	}
	if m, _, _ = d.MirrorFor("w"); !m.Tracking() || m.DetachedFrom != "" || m.AcceptWrites {
		t.Fatalf("re-attaching clears detached_from and returns writes to refused: %+v", m)
	}
	_ = d.SetMirrorWrites("w", true)
	_ = d.SetMirror("w", "https://new.example/home", 3*time.Hour)
	if m, _, _ = d.MirrorFor("w"); !m.AcceptWrites {
		t.Fatal("an interval change on the same upstream keeps the writes decision")
	}
	all, _ := d.Mirrors()
	if len(all) != 1 || all[0].Repo != "w" {
		t.Fatalf("Mirrors: %+v", all)
	}
}