Owner notices: the stall, one webhook path from env to Discord (#171)

Rollout note: the owner_notices table starts empty, so the first pass after deploy sends for conditions already true — correct per one-row-per-episode; say so rather than have it reported as a bug. Prod step: create the webhook, set DISCORD_WEBHOOK_URL on the deployment, redeploy — unset is silent by design, and without that step the feature ships dark. Security invariants preserved: the webhook address is a secret in the class of TOKEN_KEY (never logged, never on a config-printing line), and the owner gate is unchanged.
This commit is contained in:
2026-08-23 00:06:39 +07:00
parent 5a32943528
commit 7b22460f5e
13 changed files with 785 additions and 18 deletions
+38
View File
@@ -2700,3 +2700,41 @@ func failureRow(t *testing.T, s *Store, site, seriesID string) (word string, sin
}
return word, since, true
}
// Owner notices are one row per episode: the row's presence is the whole
// state. MarkNoticeSent stamps it, NoticeSent reads it, and ClearNotice
// forgets it — including a row that was never there, which is not an error.
func TestOwnerNoticeRows(t *testing.T) {
s := newTestStore(t)
if sent, err := s.NoticeSent("stall", "comix"); err != nil || sent {
t.Fatalf("NoticeSent on a fresh table = %v, %v; want false, nil", sent, err)
}
if err := s.MarkNoticeSent("stall", "comix", 4242); err != nil {
t.Fatalf("MarkNoticeSent: %v", err)
}
if sent, err := s.NoticeSent("stall", "comix"); err != nil || !sent {
t.Fatalf("NoticeSent after mark = %v, %v; want true, nil", sent, err)
}
// Re-marking the same episode just moves the stamp: the row is a lock,
// not a log. A different site is its own episode — the key is
// (condition, site).
if err := s.MarkNoticeSent("stall", "comix", 4343); err != nil {
t.Fatalf("re-mark: %v", err)
}
if sent, err := s.NoticeSent("stall", "kagane"); err != nil || sent {
t.Fatalf("NoticeSent on another site = %v, %v; want false, nil", sent, err)
}
if err := s.ClearNotice("stall", "comix"); err != nil {
t.Fatalf("ClearNotice: %v", err)
}
if sent, err := s.NoticeSent("stall", "comix"); err != nil || sent {
t.Fatalf("NoticeSent after clear = %v, %v; want false, nil", sent, err)
}
if err := s.ClearNotice("stall", "comix"); err != nil {
t.Fatalf("ClearNotice on a missing row: %v", err)
}
}