package report import ( "io" "log" "testing" ) // R-204 item 4 / R-193 — the ACK's `identity_blob_present` must be recorded on EVERY ACK, including // (especially) on a box with no off-site target. // // THE DEFECT THIS PINS: Reconcile returns early when the box is neither pending nor escrowed, which // is exactly a REBUILT box's state — so the one fact that distinguishes it from a box that never had // off-site backups was discarded on every cycle. The recorder therefore runs BEFORE every gate, and // the test drives Reconcile itself rather than calling the recorder, because the ordering IS the fix. func TestEscrowConfirm_RecordsPresenceEvenWhenOffboxUnconfigured(t *testing.T) { var recorded []bool c := &EscrowAutoConfirmer{ // The unconfigured-box shape: neither pending nor escrowed. Every gate below will skip. Pending: func() bool { return false }, Escrowed: func() bool { return false }, LocalHash: func() (string, bool) { return "", false }, Flip: func() error { t.Fatal("an unconfigured box must never flip"); return nil }, RecordPresence: func(p bool) error { recorded = append(recorded, p); return nil }, Logger: log.New(io.Discard, "", 0), } c.Reconcile(&EscrowStatus{IdentityBlobPresent: true, ResticPwSHA256: "SHA1"}) if len(recorded) != 1 || !recorded[0] { t.Fatalf("presence not recorded on an unconfigured box: %v — a rebuilt box cannot learn the hub holds its recovery package", recorded) } // It must also record the NEGATIVE, so a customer RESET (the hub losing its escrow row) turns the // box's declaration back off. A set-only flag would strand the declaration forever. c.Reconcile(&EscrowStatus{IdentityBlobPresent: false}) if len(recorded) != 2 || recorded[1] { t.Fatalf("a false presence was not recorded: %v", recorded) } } // A nil ACK escrow object records nothing (an old hub, or no escrow row) — absence of a statement is // not a statement of absence, and overwriting a known-true with false here would un-declare a genuinely // stranded box every time an old hub answered. func TestEscrowConfirm_NilAckRecordsNothing(t *testing.T) { called := false c := &EscrowAutoConfirmer{ Pending: func() bool { return false }, Escrowed: func() bool { return false }, RecordPresence: func(bool) error { called = true; return nil }, Logger: log.New(io.Discard, "", 0), } c.Reconcile(nil) if called { t.Fatal("a nil ACK escrow object must not record a presence") } } // A recorder FAILURE must be logged, not swallowed, and must not stop the auto-confirm — the two are // independent concerns and a failed settings write must not also break escrow confirmation. func TestEscrowConfirm_RecordFailureDoesNotBlockAutoConfirm(t *testing.T) { flipped := false c := &EscrowAutoConfirmer{ Pending: func() bool { return true }, Escrowed: func() bool { return false }, LocalHash: func() (string, bool) { return "MATCH", true }, Flip: func() error { flipped = true; return nil }, RecordPresence: func(bool) error { return errRecord }, Logger: log.New(io.Discard, "", 0), } c.Reconcile(&EscrowStatus{IdentityBlobPresent: true, ResticPwSHA256: "MATCH"}) if !flipped { t.Fatal("a presence-record failure blocked the escrow auto-confirm — they are independent") } } type recordErr struct{} func (recordErr) Error() string { return "record failed" } var errRecord = recordErr{}