Files
felhom-controller/controller/internal/web/recovery_older_mail_test.go
T
admin a40729a698
gates / gates (push) Successful in 59s
R-304 option C: operator mail when a household's code opens or may open an older package (once a day); R-298 side fix (no eject/format on a backup-target drive); R-717 comments
Unreleased; ships with tomorrow's release (needs the hub of the same day).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-08 10:07:03 +02:00

67 lines
2.6 KiB
Go

package web
import (
"net/http"
"strings"
"testing"
"time"
)
// R-304 option C (decision 183): the operator is mailed when a household's code opens (422) or may open (424) an older
// sealed package — once per day per box, never on a wrong code, and never with the code in it.
// RED-PROOF: remove the two notifyRecoveryOlderPackage calls from recoveryUnlockHandler → no event → the first
// assertion FAILS; drop the day check → the second unlock sends a second event → FAILS.
type olderEvent struct{ typ, sev, msg, class string }
func olderFixture(t *testing.T, now *time.Time) (*recoveryFixture, *[]olderEvent) {
t.Helper()
f := newRecoveryFixture(t)
var evs []olderEvent
f.s.recoveryOlderPushFn = func(typ, sev, msg string, d map[string]string) {
evs = append(evs, olderEvent{typ, sev, msg, d["class"]})
}
f.s.SetRecoveryClock(func() time.Time { return *now })
return f, &evs
}
func TestR304_OlderPackageMail_OncePerDay(t *testing.T) {
now := time.Date(2026, 10, 8, 10, 0, 0, 0, time.UTC)
f, evs := olderFixture(t, &now)
f.rec.failWith = refusal(422, "the recovery code is correct, but it belongs to an EARLIER sealed package")
postUnlockWith(t, f.s, testRecoveryCode)
if len(*evs) != 1 || (*evs)[0].typ != "recovery_older_package" || (*evs)[0].sev != "warning" || (*evs)[0].class != "code-opens-retained" {
t.Fatalf("events = %+v, want one warning recovery_older_package (code-opens-retained)", *evs)
}
if strings.Contains((*evs)[0].msg, testRecoveryCode) {
t.Fatal("the operator mail must never carry the recovery code")
}
now = now.Add(3 * time.Hour)
postUnlockWith(t, f.s, testRecoveryCode)
if len(*evs) != 1 {
t.Fatalf("a second try the same day sent %d events, want still 1", len(*evs))
}
// A controller restart the same day: a new Server over the same data dir must not mail again.
f.s.recoveryOlderLastDay = ""
postUnlockWith(t, f.s, testRecoveryCode)
if len(*evs) != 1 {
t.Fatalf("after a restart the same day: %d events, want still 1 (the day is on disk)", len(*evs))
}
now = now.Add(24 * time.Hour)
f.rec.failWith = refusal(http.StatusFailedDependency, "not all checked")
postUnlockWith(t, f.s, testRecoveryCode)
if len(*evs) != 2 || (*evs)[1].class != "older-unchecked" {
t.Fatalf("next day, a 424: events = %+v, want a second one (older-unchecked)", *evs)
}
}
func TestR304_OlderPackageMail_NotOnAWrongCode(t *testing.T) {
now := time.Date(2026, 10, 8, 10, 0, 0, 0, time.UTC)
f, evs := olderFixture(t, &now)
f.rec.failWith = refusal(400, "the recovery code did not open the sealed bundle")
postUnlockWith(t, f.s, testRecoveryCode)
if len(*evs) != 0 {
t.Fatalf("a plain wrong code must not mail the operator: %+v", *evs)
}
}