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
68 lines
2.6 KiB
Go
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)
|
|
}
|
|
}
|