package notify import ( "database/sql" "io" "log" "path/filepath" "testing" "time" "gitea.dooplex.hu/admin/felhom-hub/internal/store" _ "modernc.org/sqlite" ) // storeWithHost opens a store with customer c1 and one host enrolled `ago` before now. func storeWithHost(t *testing.T, ago time.Duration) *store.Store { t.Helper() path := filepath.Join(t.TempDir(), "d.db") st, err := store.New(path, log.New(io.Discard, "", 0)) if err != nil { t.Fatal(err) } t.Cleanup(func() { st.Close() }) st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c1", APIKey: "k", RetrievalPassword: "p"}) if err := st.UpsertHost(&store.Host{HostID: "c1-abc123", CustomerID: "c1", APIKey: "hk"}); err != nil { t.Fatal(err) } db, err := sql.Open("sqlite", path) if err != nil { t.Fatal(err) } defer db.Close() if _, err := db.Exec(`UPDATE hosts SET created_at = ? WHERE host_id = 'c1-abc123'`, time.Now().UTC().Add(-ago).Format("2006-01-02 15:04:05")); err != nil { t.Fatal(err) } return st } const tierSkipMsg = "Whole-guest backup tier felhom-pbs skipped: its storage does not exist on the host (never provisioned or removed). No app was stopped for it." // R-723 (v0.126.0) — the 2026-09-29 shape: the first whole-guest run fired 7 min after enrolment, before // the off-site descriptor arrived, and the operator was mailed „skipped". The CONSEQUENCE asserted: no // operator mail in a box's first hour. // COMPANION RED-PROOF: drop the first-hour check in processOperator → 1 operator mail. func TestR723_FirstHourTierSkipIsNotMailed(t *testing.T) { st := storeWithHost(t, 7*time.Minute) d := NewDispatcher(st, "test-key", "hub@felhom.eu", "op@felhom.eu", true, log.New(io.Discard, "", 0)) mails := captureSeam(d) d.ProcessEvent("c1", "backup_tier_skipped", "warning", tierSkipMsg, `{"tier":"felhom-pbs"}`, "controller") if op := mailsFor(*mails, "op@felhom.eu"); len(op) != 0 { t.Fatalf("a first-hour tier skip mailed the operator (%d mails)", len(op)) } } // Control: the same skip on a box enrolled a week ago is a real provisioning gap and still mails. func TestR723_TierSkipOnAnOldBoxStillMails(t *testing.T) { st := storeWithHost(t, 7*24*time.Hour) d := NewDispatcher(st, "test-key", "hub@felhom.eu", "op@felhom.eu", true, log.New(io.Discard, "", 0)) mails := captureSeam(d) d.ProcessEvent("c1", "backup_tier_skipped", "warning", tierSkipMsg, `{"tier":"felhom-pbs"}`, "controller") if op := mailsFor(*mails, "op@felhom.eu"); len(op) != 1 { t.Fatalf("an old box's tier skip must still mail the operator, got %d", len(op)) } }