mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-16 00:15:37 +00:00
feat(deploynotify): DM owner once per new deployed git SHA
New internal/deploynotify package fires a single Telegram DM to BOT_OWNER_ID on the first cold start that observes a new gitSHA. Dedup via a DynamoDB KV record so repeat cold starts of the same version stay silent. Send-then-write order means a failed Telegram call doesn't permanently silence retries. gitSHA is baked into the binary via `-ldflags "-X main.gitSHA=..."` from Makefile; empty SHA (non-make builds) silently disables the feature. No new env vars or IAM permissions.
This commit is contained in:
1 parent
3182179e65
commit
22c817b0db
4 files changed
+342
-2
No files matched your search
@@ -0,0 +1,174 @@
|
||||
package deploynotify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tiennm99/miti99bot/internal/storage"
|
||||
)
|
||||
|
||||
func TestShouldNotify_FirstRun(t *testing.T) {
|
||||
kv := storage.NewMemoryKVStore()
|
||||
got, err := shouldNotify(context.Background(), kv, "abc123")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !got {
|
||||
t.Errorf("first run with empty KV → want true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotify_SameSHA(t *testing.T) {
|
||||
kv := storage.NewMemoryKVStore()
|
||||
if err := markNotified(context.Background(), kv, "abc123"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := shouldNotify(context.Background(), kv, "abc123")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if got {
|
||||
t.Errorf("same SHA → want false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotify_DifferentSHA(t *testing.T) {
|
||||
kv := storage.NewMemoryKVStore()
|
||||
if err := markNotified(context.Background(), kv, "old111"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := shouldNotify(context.Background(), kv, "new222")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !got {
|
||||
t.Errorf("changed SHA → want true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
// recorder is a Sender that captures the last (chatID, text) it received and
|
||||
// flags whether it was ever invoked. Replaces a full RecordingBot since this
|
||||
// package only needs send-or-not signal.
|
||||
type recorder struct {
|
||||
called bool
|
||||
chatID int64
|
||||
text string
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *recorder) send(_ context.Context, chatID int64, text string) error {
|
||||
r.called = true
|
||||
r.chatID = chatID
|
||||
r.text = text
|
||||
return r.err
|
||||
}
|
||||
|
||||
func TestRun_SkipsWhenSHAEmpty(t *testing.T) {
|
||||
kv := storage.NewMemoryKVStore()
|
||||
rec := &recorder{}
|
||||
Run(context.Background(), Config{
|
||||
KV: kv,
|
||||
OwnerID: 42,
|
||||
GitSHA: "",
|
||||
Sender: rec.send,
|
||||
})
|
||||
if rec.called {
|
||||
t.Errorf("empty SHA must not send; got call with text=%q", rec.text)
|
||||
}
|
||||
// And no KV write either.
|
||||
if _, err := kv.Get(context.Background(), kvKey); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("empty SHA must not write KV; got err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun_SkipsWhenNoOwner(t *testing.T) {
|
||||
kv := storage.NewMemoryKVStore()
|
||||
rec := &recorder{}
|
||||
Run(context.Background(), Config{
|
||||
KV: kv,
|
||||
OwnerID: 0,
|
||||
GitSHA: "abc123",
|
||||
Sender: rec.send,
|
||||
})
|
||||
if rec.called {
|
||||
t.Errorf("zero owner must not send")
|
||||
}
|
||||
if _, err := kv.Get(context.Background(), kvKey); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("zero owner must not write KV; got err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun_SendsAndPersistsOnFirstRun(t *testing.T) {
|
||||
kv := storage.NewMemoryKVStore()
|
||||
rec := &recorder{}
|
||||
Run(context.Background(), Config{
|
||||
KV: kv,
|
||||
OwnerID: 42,
|
||||
GitSHA: "abc123",
|
||||
Sender: rec.send,
|
||||
})
|
||||
if !rec.called {
|
||||
t.Fatalf("first run with fresh KV must send")
|
||||
}
|
||||
if rec.chatID != 42 {
|
||||
t.Errorf("chatID = %d, want 42", rec.chatID)
|
||||
}
|
||||
if !strings.Contains(rec.text, "abc123") {
|
||||
t.Errorf("message %q missing SHA", rec.text)
|
||||
}
|
||||
// KV must now hold the SHA so the next Run is silent.
|
||||
var got notifyRecord
|
||||
if err := kv.GetJSON(context.Background(), kvKey, &got); err != nil {
|
||||
t.Fatalf("post-send KV read: %v", err)
|
||||
}
|
||||
if got.SHA != "abc123" {
|
||||
t.Errorf("persisted SHA = %q, want abc123", got.SHA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun_SilentOnSecondRunSameSHA(t *testing.T) {
|
||||
kv := storage.NewMemoryKVStore()
|
||||
if err := markNotified(context.Background(), kv, "abc123"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := &recorder{}
|
||||
Run(context.Background(), Config{
|
||||
KV: kv,
|
||||
OwnerID: 42,
|
||||
GitSHA: "abc123",
|
||||
Sender: rec.send,
|
||||
})
|
||||
if rec.called {
|
||||
t.Errorf("repeat run with same SHA must not send")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun_DoesNotPersistOnSendFailure(t *testing.T) {
|
||||
kv := storage.NewMemoryKVStore()
|
||||
rec := &recorder{err: errors.New("telegram is down")}
|
||||
Run(context.Background(), Config{
|
||||
KV: kv,
|
||||
OwnerID: 42,
|
||||
GitSHA: "abc123",
|
||||
Sender: rec.send,
|
||||
})
|
||||
if !rec.called {
|
||||
t.Fatalf("sender should have been called")
|
||||
}
|
||||
// Send failed → SHA must NOT be persisted, so the next cold start retries.
|
||||
if _, err := kv.Get(context.Background(), kvKey); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("failed send must not write KV; got err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderMessage_ContainsSHA(t *testing.T) {
|
||||
got := renderMessage("deadbeef")
|
||||
if !strings.Contains(got, "deadbeef") {
|
||||
t.Errorf("message %q missing SHA", got)
|
||||
}
|
||||
if !strings.Contains(got, "miti99bot") {
|
||||
t.Errorf("message %q missing bot name", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user