d3b284863e
gates / gates (push) Successful in 26s
Allowlisted, not operator-only, seeded for new households and added once (add-only) to every existing enabled_events row. mail.event entries in hu and en name the app from details.stack_name. Per-app cooldown on both the operator and the household leg. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
93 lines
3.5 KiB
Go
93 lines
3.5 KiB
Go
package claim
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// TestMarkClaimed_SeedsNotificationPrefs (v0.71.0 F12, Scenario D): the unclaimed→claimed
|
|
// transition seeds a customer_notifications row from the registered email with the default
|
|
// critical-only event set; a pre-existing (customer-edited) row is NEVER modified.
|
|
// Companion red-proof: seeding via SaveNotificationPrefs (upsert) makes the second half fail.
|
|
func TestMarkClaimed_SeedsNotificationPrefs(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)
|
|
}
|
|
prefs, err := st.GetNotificationPrefs("c1")
|
|
if err != nil || prefs == nil {
|
|
t.Fatalf("prefs must be seeded at claim, got %+v err=%v", prefs, err)
|
|
}
|
|
if prefs.Email != "owner@example.hu" {
|
|
t.Fatalf("seeded email = %q, want the registered address", prefs.Email)
|
|
}
|
|
if len(prefs.EnabledEvents) != len(defaultSeedEvents) {
|
|
t.Fatalf("seeded events = %v, want the default set %v", prefs.EnabledEvents, defaultSeedEvents)
|
|
}
|
|
for i, ev := range defaultSeedEvents {
|
|
if prefs.EnabledEvents[i] != ev {
|
|
t.Fatalf("seeded events = %v, want %v", prefs.EnabledEvents, defaultSeedEvents)
|
|
}
|
|
}
|
|
if prefs.CooldownHours != 6 {
|
|
t.Fatalf("seeded cooldown = %d, want 6", prefs.CooldownHours)
|
|
}
|
|
}
|
|
|
|
// TestMarkClaimed_NeverOverwritesExistingPrefs: a customer-edited row survives a claim
|
|
// byte-identical (re-claim after RESET is the natural trigger).
|
|
func TestMarkClaimed_NeverOverwritesExistingPrefs(t *testing.T) {
|
|
e, st, _ := newTestEngine(t)
|
|
if err := st.SaveNotificationPrefs("c1", "edited@example.hu", []string{"node_down"}, 12); err != nil {
|
|
t.Fatalf("pre-existing prefs: %v", err)
|
|
}
|
|
if _, err := e.EnsureIssued(cust()); err != nil {
|
|
t.Fatalf("EnsureIssued: %v", err)
|
|
}
|
|
if err := e.MarkClaimed(cust()); err != nil {
|
|
t.Fatalf("MarkClaimed: %v", err)
|
|
}
|
|
prefs, _ := st.GetNotificationPrefs("c1")
|
|
if prefs == nil || prefs.Email != "edited@example.hu" || len(prefs.EnabledEvents) != 1 || prefs.CooldownHours != 12 {
|
|
t.Fatalf("claim seed MODIFIED an existing row (upsert bug): %+v", prefs)
|
|
}
|
|
}
|
|
|
|
// TestMarkClaimed_EmptyEmail_NoRow_ClaimSucceeds: no registered email → no seed, and the claim
|
|
// itself still succeeds (notification plumbing must never gate claiming).
|
|
func TestMarkClaimed_EmptyEmail_NoRow_ClaimSucceeds(t *testing.T) {
|
|
e, st, _ := newTestEngine(t)
|
|
noMail := &store.CustomerConfig{CustomerID: "c1", Email: "", Domain: "example.hu"}
|
|
if _, err := e.EnsureIssued(cust()); err != nil { // issue needs an email to deliver the code
|
|
t.Fatalf("EnsureIssued: %v", err)
|
|
}
|
|
if err := e.MarkClaimed(noMail); err != nil {
|
|
t.Fatalf("MarkClaimed with empty email must succeed: %v", err)
|
|
}
|
|
if prefs, _ := st.GetNotificationPrefs("c1"); prefs != nil {
|
|
t.Fatalf("empty registered email must seed nothing, got %+v", prefs)
|
|
}
|
|
// The transition happened — a second call is a no-op (unchanged idempotency).
|
|
if err := e.MarkClaimed(noMail); err != nil {
|
|
t.Fatalf("idempotent re-claim: %v", err)
|
|
}
|
|
}
|
|
|
|
// v0.120.0 — a NEW household hears about an undone or held update from day one.
|
|
// RED-PROOF (REPORT.md): drop app_update_undone from defaultSeedEvents → this fails naming it.
|
|
func TestDefaultSeedIncludesTheUpdateOutcomes(t *testing.T) {
|
|
have := map[string]bool{}
|
|
for _, e := range defaultSeedEvents {
|
|
have[e] = true
|
|
}
|
|
for _, et := range []string{"app_update_undone", "app_update_held"} {
|
|
if !have[et] {
|
|
t.Fatalf("defaultSeedEvents must include %s", et)
|
|
}
|
|
}
|
|
}
|