package service import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" ) func TestNotificationChannelCRUD(t *testing.T) { t.Skip("requires database") } func TestNotificationChannelTelegram(t *testing.T) { t.Skip("requires database") } func TestNotificationUpdateSubscriptions(t *testing.T) { t.Skip("requires database") } func TestDeliverWebhook_Success(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { t.Errorf("expected POST, got %s", r.Method) } if r.Header.Get("Content-Type") != "application/json" { t.Errorf("expected application/json content type") } var body map[string]interface{} json.NewDecoder(r.Body).Decode(&body) if body["title"] != "Test Event" { t.Errorf("expected title 'Test Event', got %v", body["title"]) } w.WriteHeader(200) })) defer server.Close() svc := NewNotificationService(nil) // nil DB is fine for delivery tests err := svc.DeliverWebhook(context.Background(), server.URL, map[string]interface{}{ "title": "Test Event", }) if err != nil { t.Fatalf("expected nil error, got: %v", err) } } func TestDeliverWebhook_Non200(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) })) defer server.Close() svc := NewNotificationService(nil) err := svc.DeliverWebhook(context.Background(), server.URL, map[string]interface{}{}) if err == nil { t.Fatal("expected error on 500 response, got nil") } } func TestDeliverWebhook_Timeout(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(15 * time.Second) // exceed client timeout })) defer server.Close() svc := NewNotificationService(nil) err := svc.DeliverWebhook(context.Background(), server.URL, map[string]interface{}{}) if err == nil { t.Fatal("expected error on timeout, got nil") } } func TestDeliverTelegram_Success(t *testing.T) { var receivedPath string var receivedBody map[string]interface{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedPath = r.URL.Path json.NewDecoder(r.Body).Decode(&receivedBody) w.WriteHeader(200) json.NewEncoder(w).Encode(map[string]interface{}{"ok": true}) })) defer server.Close() svc := NewNotificationService(nil) svc.telegramBaseURL = server.URL // override for testing err := svc.DeliverTelegram(context.Background(), "123456:ABC-DEF", "987654321", "Test Message") if err != nil { t.Fatalf("expected nil error, got: %v", err) } expectedPath := "/bot123456:ABC-DEF/sendMessage" if receivedPath != expectedPath { t.Errorf("expected path %s, got %s", expectedPath, receivedPath) } if receivedBody["chat_id"] != "987654321" { t.Errorf("expected chat_id 987654321, got %v", receivedBody["chat_id"]) } if receivedBody["parse_mode"] != "HTML" { t.Errorf("expected parse_mode HTML, got %v", receivedBody["parse_mode"]) } } func TestDeliverTelegram_Non200(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(400) json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "description": "Bad Request"}) })) defer server.Close() svc := NewNotificationService(nil) svc.telegramBaseURL = server.URL err := svc.DeliverTelegram(context.Background(), "token", "chat", "text") if err == nil { t.Fatal("expected error on 400 response, got nil") } } func TestCalculateBackoff(t *testing.T) { expected := []time.Duration{ 30 * time.Second, // attempt 0: 30s 60 * time.Second, // attempt 1: 60s 120 * time.Second, // attempt 2: 120s 240 * time.Second, // attempt 3: 240s 480 * time.Second, // attempt 4: 480s (capped) 480 * time.Second, // attempt 5: still capped } for i, want := range expected { got := calculateBackoff(i) if got != want { t.Errorf("calculateBackoff(%d) = %v, want %v", i, got, want) } } } func TestMaskTelegramConfig(t *testing.T) { raw := json.RawMessage(`{"bot_token":"secret123","chat_id":"999"}`) masked := maskConfig("telegram", raw) var m map[string]interface{} json.Unmarshal(masked, &m) if m["bot_token"] != "***" { t.Errorf("expected bot_token masked as ***, got %v", m["bot_token"]) } if m["chat_id"] != "999" { t.Errorf("expected chat_id preserved as 999, got %v", m["chat_id"]) } } func TestMaskWebhookConfig(t *testing.T) { raw := json.RawMessage(`{"url":"https://example.com/hook"}`) masked := maskConfig("webhook", raw) var m map[string]interface{} json.Unmarshal(masked, &m) if m["url"] != "https://example.com/hook" { t.Errorf("expected url preserved, got %v", m["url"]) } } func TestValidateChannelConfig_Webhook(t *testing.T) { svc := NewNotificationService(nil) tests := []struct { name string config string wantErr bool }{ {"valid", `{"url":"https://example.com/hook"}`, false}, {"http scheme", `{"url":"http://example.com/hook"}`, false}, {"missing url", `{"url":""}`, true}, {"no url field", `{}`, true}, {"invalid scheme", `{"url":"ftp://example.com"}`, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := svc.ValidateChannelConfig("webhook", json.RawMessage(tt.config)) if (err != nil) != tt.wantErr { t.Errorf("ValidateChannelConfig() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestValidateChannelConfig_Telegram(t *testing.T) { svc := NewNotificationService(nil) tests := []struct { name string config string wantErr bool }{ {"valid", `{"bot_token":"123:ABC","chat_id":"999"}`, false}, {"missing bot_token", `{"bot_token":"","chat_id":"999"}`, true}, {"missing chat_id", `{"bot_token":"123:ABC","chat_id":""}`, true}, {"both missing", `{}`, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := svc.ValidateChannelConfig("telegram", json.RawMessage(tt.config)) if (err != nil) != tt.wantErr { t.Errorf("ValidateChannelConfig() error = %v, wantErr %v", err, tt.wantErr) } }) } }