80aeac71f6
gates / gates (push) Successful in 29s
Red-proofs RP40-RP42. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
66 lines
2.5 KiB
Go
66 lines
2.5 KiB
Go
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))
|
|
}
|
|
}
|