diff --git a/hub/internal/claim/engine_test.go b/hub/internal/claim/engine_test.go index 0e6d89d..60f2db4 100644 --- a/hub/internal/claim/engine_test.go +++ b/hub/internal/claim/engine_test.go @@ -202,3 +202,30 @@ func TestIssue_EmailFailureKeepsGateArmed(t *testing.T) { t.Fatalf("emailed_at must stay unset on failure: %+v", row) } } + +// Reset non-DoS (Scenario C WRONG case): repeated reset REQUESTS never touch the box's password +// or claimed state — the engine only rotates the CODE. The controller's password keeps working +// throughout (the box side owns the password; the hub only mails codes). Red-proof partner: make +// RequestReset also clear claimed_at → this fails. +func TestRequestReset_NeverAltersClaimedState(t *testing.T) { + e, st, _ := newTestEngine(t) + if _, err := e.EnsureIssued(cust()); err != nil { + t.Fatalf("EnsureIssued: %v", err) + } + if err := e.MarkClaimed(cust()); err != nil { + t.Fatalf("MarkClaimed: %v", err) + } + claimedBefore, _ := st.GetClaim("c1") + for i := 0; i < 3; i++ { + if err := e.RequestReset(cust()); err != nil { + t.Fatalf("RequestReset %d: %v", i, err) + } + } + claimedAfter, _ := st.GetClaim("c1") + if !claimedAfter.Claimed() { + t.Fatal("reset requests un-claimed the box — must never happen") + } + if !claimedAfter.ClaimedAt.Equal(*claimedBefore.ClaimedAt) { + t.Fatal("reset requests moved claimed_at") + } +}