package oltp
import (
"strings"
"testing"
"time"
)
// RFC 0020 rung 6: a mirror's uv switch, its upstream credential
// (sealed, readable only by MirrorUVCredential), and the import round's
// result on the mirror row and on the pull it followed.
func TestMirrorUVSwitchCredentialAndResult(t *testing.T) {
d := openTest(t)
if err := d.SetMirror("w", "https://example.invalid/w", time.Hour); err != nil {
t.Fatal(err)
}
m, _, _ := d.MirrorFor("w")
if m.UV || m.UVUser != "" || m.UVCredential {
t.Fatalf("FAIL: a new mirror imports or holds a credential: %+v", m)
}
t.Log("ok: the switch is off and no credential is held by default")
if err := d.SetMirrorUV("w", true); err != nil {
t.Fatal(err)
}
if err := d.SetMirrorUVCredential("w", "reader", "s3cret"); err != nil {
t.Fatal(err)
}
m, _, _ = d.MirrorFor("w")
user, pw, ok, err := d.MirrorUVCredential("w")
if err != nil || !ok || user != "reader" || pw != "s3cret" || !m.UV || m.UVUser != "reader" || !m.UVCredential {
t.Fatalf("FAIL: %+v %q %q %v %v", m, user, pw, ok, err)
}
var stored string
if err := d.db.QueryRow(`SELECT uv_secret_enc FROM mirror WHERE repo = 'w'`).Scan(&stored); err != nil || strings.Contains(stored, "s3cret") || stored == "" {
t.Fatalf("FAIL: the password is stored as %q (%v)", stored, err)
}
t.Log("ok: the credential is sealed at rest and comes back whole to the round")
// Re-saving the same URL keeps the credential; a new upstream drops
// it, so a password for one server is never sent to another.
if err := d.SetMirror("w", "https://example.invalid/w", 2*time.Hour); err != nil {
t.Fatal(err)
}
if _, _, ok, _ := d.MirrorUVCredential("w"); !ok {
t.Fatalf("FAIL: changing the interval dropped the credential")
}
if err := d.SetMirror("w", "https://elsewhere.invalid/w", 2*time.Hour); err != nil {
t.Fatal(err)
}
if _, _, ok, _ := d.MirrorUVCredential("w"); ok {
t.Fatalf("FAIL: a new upstream kept the old one's credential")
}
m, _, _ = d.MirrorFor("w")
if !m.UV || m.UVUser != "" || m.UVCredential {
t.Fatalf("FAIL: after a new upstream: %+v", m)
}
t.Log("ok: a new upstream drops the credential and keeps the switch")
if err := d.SetMirrorUVCredential("w", "reader", "x"); err != nil {
t.Fatal(err)
}
if err := d.SetMirrorUVCredential("w", "", ""); err != nil {
t.Fatal(err)
}
if _, _, ok, _ := d.MirrorUVCredential("w"); ok {
t.Fatalf("FAIL: an empty login did not clear the credential")
}
t.Log("ok: an empty login clears the credential")
at := time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)
if _, err := d.RecordMirrorRun("w", at, time.Second, true, nil); err != nil {
t.Fatal(err)
}
if _, err := d.RecordMirrorRun("w", at.Add(time.Minute), time.Second, false, nil); err != nil {
t.Fatal(err)
}
if err := d.RecordMirrorUV("w", at.Add(time.Minute), 3, "big.bin: not carried (larger than this bucket's limit)"); err != nil {
t.Fatal(err)
}
pulls, _ := d.MirrorPulls("w", 0)
m, _, _ = d.MirrorFor("w")
if len(pulls) != 2 || !pulls[0].UVRan || pulls[0].UVChanged != 3 || !strings.Contains(pulls[0].UVErr, "not carried") ||
pulls[1].UVRan || m.UVAt == "" || m.UVChanged != 3 || m.UVErr != pulls[0].UVErr {
t.Fatalf("FAIL: pulls %+v mirror %+v", pulls, m)
}
t.Log("ok: the round is recorded on the pull it followed and on the mirror row")
if err := d.SetMirrorUV("nope", true); err == nil {
t.Fatalf("FAIL: the switch was set on a store with no mirror")
}
if err := d.SetMirrorUVCredential("nope", "a", "b"); err == nil {
t.Fatalf("FAIL: a credential was set on a store with no mirror")
}
t.Log("ok: a store with no mirror is refused")
}