130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|