Files
felhom.eu/hub/internal/notify/app_update_events_test.go
T
admin d3b284863e
gates / gates (push) Successful in 26s
hub v0.120.0: app_update_undone/held reach the household, per app, in its language
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
2026-09-23 13:47:13 +02:00

68 lines
2.6 KiB
Go

package notify
import (
"io"
"log"
"strings"
"testing"
)
// v0.120.0 — the household hears about an update ONCE per app per event (R-629: a storm is a
// defect), and two apps on one night are two mails.
//
// COMPANION RED-PROOF (REPORT.md): drop the perAppCustomerCooldownEvents suffix in processCustomer —
// the second app's mail is swallowed by the first app's cooldown and this fails on the count.
func TestAppUpdateEvents_OneMailPerAppPerEvent(t *testing.T) {
st := opOnlyStore(t)
if err := st.SaveNotificationPrefs("c1", "customer@example.com",
[]string{"app_update_undone", "app_update_held"}, 6); err != nil {
t.Fatal(err)
}
var subjects []string
d := NewDispatcher(st, "test-key", "from@felhom.eu", "operator@felhom.eu", true, log.New(io.Discard, "", 0))
d.sendEmailFn = func(to, subject, _ string, _ map[string]string) error {
if to == "customer@example.com" {
subjects = append(subjects, subject)
}
return nil
}
fire := func(et, app string) {
d.ProcessEvent("c1", et, "warning", "A(z) "+app+" frissitese nem sikerult.",
`{"app":"`+app+`","stack_name":"`+app+`"}`, "controller")
}
fire("app_update_undone", "docmost")
fire("app_update_undone", "romm") // another app, same night → its own mail
fire("app_update_undone", "docmost") // the same app again → no second mail
fire("app_update_held", "docmost") // another EVENT for the same app → its own mail
if len(subjects) != 3 {
t.Fatalf("want 3 household mails (docmost undone, romm undone, docmost held), got %d: %q", len(subjects), subjects)
}
for i, app := range []string{"docmost", "romm", "docmost"} {
if !strings.Contains(subjects[i], app+":") {
t.Errorf("mail %d must name %s in the subject, got %q", i, app, subjects[i])
}
}
}
// The other types keep their customer-side grain: the new register is not a blanket change.
func TestAppUpdateEvents_OtherTypesKeepTheirCustomerGrain(t *testing.T) {
st := opOnlyStore(t)
if err := st.SaveNotificationPrefs("c1", "customer@example.com", []string{"app_start_failed"}, 6); err != nil {
t.Fatal(err)
}
n := 0
d := NewDispatcher(st, "test-key", "from@felhom.eu", "operator@felhom.eu", true, log.New(io.Discard, "", 0))
d.sendEmailFn = func(to, _, _ string, _ map[string]string) error {
if to == "customer@example.com" {
n++
}
return nil
}
d.ProcessEvent("c1", "app_start_failed", "error", "x", `{"stack_name":"a"}`, "controller")
d.ProcessEvent("c1", "app_start_failed", "error", "x", `{"stack_name":"b"}`, "controller")
if n != 1 {
t.Fatalf("app_start_failed's household cooldown stays per TYPE (unchanged by v0.120.0), got %d mails", n)
}
}