feat: add notifier Sender interface and digest formatter
This commit is contained in:
129
internal/notifier/notifier_test.go
Normal file
129
internal/notifier/notifier_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"naviwatcher/internal/scanner"
|
||||
)
|
||||
|
||||
type stubSender struct {
|
||||
sent []string
|
||||
failErr error
|
||||
}
|
||||
|
||||
func (s *stubSender) Send(ctx context.Context, message string) error {
|
||||
if s.failErr != nil {
|
||||
return s.failErr
|
||||
}
|
||||
s.sent = append(s.sent, message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFormatDigest_Empty(t *testing.T) {
|
||||
got := FormatDigest(nil, "http://ui")
|
||||
if got != "NaviWatcher: no new missing releases found." {
|
||||
t.Fatalf("unexpected empty digest: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDigest_GroupsByArtistAndCounts(t *testing.T) {
|
||||
missing := []scanner.MissingRelease{
|
||||
{ArtistID: "art-b", Title: "Zebra", RGID: "r3"},
|
||||
{ArtistID: "art-a", Title: "Alpha", RGID: "r1"},
|
||||
{ArtistID: "art-a", Title: "Beta", RGID: "r2"},
|
||||
}
|
||||
got := FormatDigest(missing, "http://localhost:8080/")
|
||||
if !strings.Contains(got, "art-a (2):") {
|
||||
t.Errorf("expected art-a with count 2, got:\n%s", got)
|
||||
}
|
||||
if !strings.Contains(got, "art-b (1):") {
|
||||
t.Errorf("expected art-b with count 1, got:\n%s", got)
|
||||
}
|
||||
// art-a should be alphabetically before art-b.
|
||||
if strings.Index(got, "art-a") > strings.Index(got, "art-b") {
|
||||
t.Errorf("artists not sorted: got:\n%s", got)
|
||||
}
|
||||
// Releases within artist sorted: Alpha before Beta.
|
||||
aIdx := strings.Index(got, "Alpha")
|
||||
bIdx := strings.Index(got, "Beta")
|
||||
if aIdx > bIdx {
|
||||
t.Errorf("titles not sorted: got:\n%s", got)
|
||||
}
|
||||
if !strings.Contains(got, "View details: http://localhost:8080") {
|
||||
t.Errorf("expected UI link, got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDigest_EmptyUIBaseURLOmitsLink(t *testing.T) {
|
||||
missing := []scanner.MissingRelease{{ArtistID: "a", Title: "x", RGID: "r1"}}
|
||||
got := FormatDigest(missing, "")
|
||||
if strings.Contains(got, "View details:") {
|
||||
t.Errorf("did not expect UI link when base URL empty: got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramSender_Success(t *testing.T) {
|
||||
var gotReq sendMessageRequest
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/botTOKEN/sendMessage" {
|
||||
t.Errorf("unexpected path %q", r.URL.Path)
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&gotReq); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"ok":true}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
s := &telegramSender{
|
||||
httpClient: srv.Client(),
|
||||
token: "TOKEN",
|
||||
chatID: "CHAT",
|
||||
baseURL: srv.URL,
|
||||
}
|
||||
if err := s.Send(context.Background(), "hello"); err != nil {
|
||||
t.Fatalf("Send returned error: %v", err)
|
||||
}
|
||||
if gotReq.ChatID != "CHAT" || gotReq.Text != "hello" || !gotReq.DisableWebPagePreview {
|
||||
t.Errorf("unexpected payload: %+v", gotReq)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramSender_Non2xxError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte(`{"description":"Unauthorized"}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
s := &telegramSender{
|
||||
httpClient: srv.Client(),
|
||||
token: "TOKEN",
|
||||
chatID: "CHAT",
|
||||
baseURL: srv.URL,
|
||||
}
|
||||
err := s.Send(context.Background(), "hi")
|
||||
if err == nil {
|
||||
t.Fatal("expected error on non-2xx")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "401") {
|
||||
t.Errorf("expected status in error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSenderInterfaceFailurePropagated(t *testing.T) {
|
||||
// Verifies the Sender interface can be used by callers and failures surface.
|
||||
want := errors.New("boom")
|
||||
s := &stubSender{failErr: want}
|
||||
err := s.Send(context.Background(), "msg")
|
||||
if !errors.Is(err, want) {
|
||||
t.Fatalf("expected wrapped error %v, got %v", want, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user