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)
}
}