package main import ( "errors" "net/http" "net/http/httptest" "strings" "testing" "time" ) var errSend = errors.New("send failed") func sampleData() RequestData { return RequestData{ Time: time.Date(2026, 6, 14, 10, 0, 0, 0, time.UTC), Method: "POST", Host: "example.com", URL: "/telegram", RemoteAddr: "1.2.3.4", Header: http.Header{"X-Test": []string{"v"}}, Body: "payload<>", } } func TestFormatMarkdown(t *testing.T) { out := formatMarkdown(sampleData()) if !strings.HasPrefix(out, "```\n") || !strings.HasSuffix(out, "```") { // Перевод строки после открывающего фенса обязателен: иначе markdown // в Pachca/Telegram съедает первую строку как указатель языка, и путь // запроса (METHOD host/url) пропадает из уведомления. t.Fatalf("expected ```\\n wrapping, got: %q", out) } if !strings.Contains(out, "/telegram") { t.Errorf("missing request path/url: %q", out) } if !strings.Contains(out, "POST") || !strings.Contains(out, "1.2.3.4") { t.Errorf("missing fields: %q", out) } if !strings.Contains(out, "X-Test: v") { t.Errorf("missing header: %q", out) } } func TestFormatHTML(t *testing.T) { out := formatHTML(sampleData()) if !strings.Contains(out, "
") || !strings.Contains(out, "
") { t.Fatalf("expected
 wrapping, got: %q", out)
	}
	if !strings.Contains(out, "payload<>") {
		t.Errorf("body not escaped: %q", out)
	}
	if strings.Contains(out, "payload<>") {
		t.Errorf("unescaped body present: %q", out)
	}
}

type mockNotifier struct {
	called bool
	err    error
}

func (m *mockNotifier) Send(d RequestData) error {
	m.called = true
	return m.err
}

func TestHandlerRouting(t *testing.T) {
	tg := &mockNotifier{}
	h := makeHandler(map[string]Notifier{"telegram": tg})

	rec := httptest.NewRecorder()
	h(rec, httptest.NewRequest("POST", "/telegram", strings.NewReader("x")))
	if rec.Code != 200 || !tg.called {
		t.Fatalf("telegram: code=%d called=%v", rec.Code, tg.called)
	}

	rec = httptest.NewRecorder()
	h(rec, httptest.NewRequest("POST", "/unknown", strings.NewReader("x")))
	if rec.Code != 404 {
		t.Fatalf("unknown: code=%d", rec.Code)
	}

	rec = httptest.NewRecorder()
	h(rec, httptest.NewRequest("POST", "/", nil))
	if rec.Code != 404 {
		t.Fatalf("empty: code=%d", rec.Code)
	}
}

func TestHandlerSendError(t *testing.T) {
	failing := &mockNotifier{err: errSend}
	h := makeHandler(map[string]Notifier{"telegram": failing})
	rec := httptest.NewRecorder()
	h(rec, httptest.NewRequest("POST", "/telegram", strings.NewReader("x")))
	if rec.Code != 502 {
		t.Fatalf("expected 502, got %d", rec.Code)
	}
}

func TestBuildNotifiersEmpty(t *testing.T) {
	n, err := BuildNotifiers(&Config{})
	if err != nil {
		t.Fatalf("unexpected err: %v", err)
	}
	if len(n) != 0 {
		t.Fatalf("expected empty map, got %d", len(n))
	}
}

func TestBuildNotifiersPachcaMissingURL(t *testing.T) {
	_, err := BuildNotifiers(&Config{Pachca: &ConfigPachca{}})
	if err == nil {
		t.Fatal("expected error for empty webhook_url")
	}
}

func TestBuildNotifiersEmailNoRecipients(t *testing.T) {
	_, err := BuildNotifiers(&Config{Email: &ConfigEmail{
		SMTPAddr: "smtp.example.com:587",
		From:     "logger@example.com",
		To:       nil,
	}})
	if err == nil {
		t.Fatal("expected error for zero recipients")
	}
}

func TestBuildNotifiersEmailBadSMTPAddr(t *testing.T) {
	_, err := BuildNotifiers(&Config{Email: &ConfigEmail{
		SMTPAddr: "no-port",
		From:     "logger@example.com",
		To:       []string{"a@example.com"},
	}})
	if err == nil {
		t.Fatal("expected error for smtp_addr without port")
	}
}

func TestBuildNotifiersEmailOK(t *testing.T) {
	n, err := BuildNotifiers(&Config{Email: &ConfigEmail{
		SMTPAddr: "smtp.example.com:587",
		Username: "u",
		Password: "p",
		From:     "logger@example.com",
		To:       []string{"a@example.com"},
	}})
	if err != nil {
		t.Fatalf("unexpected err: %v", err)
	}
	if _, ok := n["email"]; !ok {
		t.Fatal("email notifier not built")
	}
}