package report import ( "bytes" "context" "log" "strings" "testing" ) const ( hubHash = "dbfc02f987e1ac0c91911d5761267089b1144628745a3343e4d96194e43c08e4" otherHash = "1111111111111111111111111111111111111111111111111111111111111111" ) type confirmerHarness struct { c *EscrowAutoConfirmer pending bool local string localOK bool flips int wipes int logbuf *bytes.Buffer } func newConfirmer(t *testing.T) *confirmerHarness { t.Helper() h := &confirmerHarness{pending: true, local: hubHash, localOK: true, logbuf: &bytes.Buffer{}} h.c = &EscrowAutoConfirmer{ Pending: func() bool { return h.pending }, LocalHash: func() (string, bool) { return h.local, h.localOK }, Flip: func() error { h.flips++; h.pending = false; return nil }, Wipe: func(context.Context) error { h.wipes++; return nil }, Logger: log.New(h.logbuf, "", 0), } return h } func matchStatus(hash string) *EscrowStatus { return &EscrowStatus{IdentityBlobPresent: true, ResticPwSHA256: hash, CreatedAt: "2026-07-09T20:00:00Z"} } // Scenario A — hash match → flip to escrowed + wipe the staged secret; and a repeat ACK is a no-op // (state is no longer pending). func TestEscrowConfirm_AutoConfirmsOnMatch(t *testing.T) { h := newConfirmer(t) h.c.Reconcile(matchStatus(hubHash)) if h.flips != 1 || h.wipes != 1 { t.Fatalf("match must flip once + wipe once, got flips=%d wipes=%d", h.flips, h.wipes) } if !strings.Contains(h.logbuf.String(), "auto-confirmed") { t.Fatal("the flip must log the hub-verified auto-confirm") } h.c.Reconcile(matchStatus(hubHash)) // now escrowed → no-op if h.flips != 1 { t.Fatal("an already-escrowed target must never be re-flipped") } } // Scenario B — blob present but the hash does NOT cover the current password → stay pending + a LOUD // warn naming the ceremony; deduped per distinct hash (not per 15-min cycle). func TestEscrowConfirm_StaleBlobStaysPending(t *testing.T) { h := newConfirmer(t) h.local = otherHash // local password differs from what the blob covers h.c.Reconcile(matchStatus(hubHash)) if h.flips != 0 || h.wipes != 0 { t.Fatalf("a mismatched hash must NEVER flip (false custody claim): flips=%d", h.flips) } if !strings.Contains(h.logbuf.String(), "does not cover the CURRENT repo password") || !strings.Contains(h.logbuf.String(), "escrow ceremony") { t.Fatalf("mismatch must warn loudly naming the fix, got: %s", h.logbuf.String()) } // dedupe: the same hash again → no second warn before := strings.Count(h.logbuf.String(), "does not cover") h.c.Reconcile(matchStatus(hubHash)) if strings.Count(h.logbuf.String(), "does not cover") != before { t.Fatal("repeated ACKs with the same mismatched hash must warn ONCE (dedupe)") } // a NEW distinct hash → warns again h.c.Reconcile(matchStatus("2222222222222222222222222222222222222222222222222222222222222222")) if strings.Count(h.logbuf.String(), "does not cover") != before+1 { t.Fatal("a new distinct mismatched hash must warn again") } } // Scenario C — no status / hash-less blob / no local password file → stay pending SILENTLY. func TestEscrowConfirm_SilentPendingCases(t *testing.T) { h := newConfirmer(t) h.c.Reconcile(nil) // no escrow row on the hub h.c.Reconcile(&EscrowStatus{IdentityBlobPresent: true}) // hash NULL (legacy blob) h.c.Reconcile(&EscrowStatus{IdentityBlobPresent: false, ResticPwSHA256: hubHash}) // hash without identity blob — fail-closed h.localOK = false h.c.Reconcile(matchStatus(hubHash)) // no local password file if h.flips != 0 || h.logbuf.Len() != 0 { t.Fatalf("all no-verify cases must stay pending SILENTLY: flips=%d log=%q", h.flips, h.logbuf.String()) } } // Scenario E — not pending (already escrowed / offbox unconfigured) → no-op; NEVER un-confirms. func TestEscrowConfirm_NeverActsOutsidePending(t *testing.T) { h := newConfirmer(t) h.pending = false // already escrowed (the demo's live state) h.c.Reconcile(matchStatus(hubHash)) h.c.Reconcile(matchStatus(otherHash)) // even a MISMATCH on an escrowed target must not warn/touch if h.flips != 0 || h.wipes != 0 || h.logbuf.Len() != 0 { t.Fatalf("non-pending must be a total no-op: flips=%d wipes=%d log=%q", h.flips, h.wipes, h.logbuf.String()) } } // A wipe failure after the flip is loud but does not undo the confirm (the flip is primary). func TestEscrowConfirm_WipeFailureKeepsConfirm(t *testing.T) { h := newConfirmer(t) h.c.Wipe = func(context.Context) error { return context.DeadlineExceeded } h.c.Reconcile(matchStatus(hubHash)) if h.flips != 1 { t.Fatal("the flip must land even when the wipe fails") } if !strings.Contains(h.logbuf.String(), "NOT wiped") { t.Fatal("a failed wipe must log the loud NOT-wiped signal") } }