Metrics, tests

pull/717/head
binwiederhier 2023-05-06 14:23:48 -04:00
parent fa2c09316c
commit 113b7c8a08
4 changed files with 126 additions and 27 deletions

View File

@ -98,7 +98,7 @@ var (
docsRegex = regexp.MustCompile(`^/docs(|/.*)$`) docsRegex = regexp.MustCompile(`^/docs(|/.*)$`)
fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`) fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`)
urlRegex = regexp.MustCompile(`^https?://`) urlRegex = regexp.MustCompile(`^https?://`)
phoneNumberRegex = regexp.MustCompile(`^\+\d{1,100}`) phoneNumberRegex = regexp.MustCompile(`^\+\d{1,100}$`)
//go:embed site //go:embed site
webFs embed.FS webFs embed.FS

View File

@ -15,6 +15,10 @@ var (
metricEmailsPublishedFailure prometheus.Counter metricEmailsPublishedFailure prometheus.Counter
metricEmailsReceivedSuccess prometheus.Counter metricEmailsReceivedSuccess prometheus.Counter
metricEmailsReceivedFailure prometheus.Counter metricEmailsReceivedFailure prometheus.Counter
metricSMSSentSuccess prometheus.Counter
metricSMSSentFailure prometheus.Counter
metricCallsMadeSuccess prometheus.Counter
metricCallsMadeFailure prometheus.Counter
metricUnifiedPushPublishedSuccess prometheus.Counter metricUnifiedPushPublishedSuccess prometheus.Counter
metricMatrixPublishedSuccess prometheus.Counter metricMatrixPublishedSuccess prometheus.Counter
metricMatrixPublishedFailure prometheus.Counter metricMatrixPublishedFailure prometheus.Counter
@ -57,6 +61,18 @@ func initMetrics() {
metricEmailsReceivedFailure = prometheus.NewCounter(prometheus.CounterOpts{ metricEmailsReceivedFailure = prometheus.NewCounter(prometheus.CounterOpts{
Name: "ntfy_emails_received_failure", Name: "ntfy_emails_received_failure",
}) })
metricSMSSentSuccess = prometheus.NewCounter(prometheus.CounterOpts{
Name: "ntfy_sms_sent_success",
})
metricSMSSentFailure = prometheus.NewCounter(prometheus.CounterOpts{
Name: "ntfy_sms_sent_failure",
})
metricCallsMadeSuccess = prometheus.NewCounter(prometheus.CounterOpts{
Name: "ntfy_calls_made_success",
})
metricCallsMadeFailure = prometheus.NewCounter(prometheus.CounterOpts{
Name: "ntfy_calls_made_failure",
})
metricUnifiedPushPublishedSuccess = prometheus.NewCounter(prometheus.CounterOpts{ metricUnifiedPushPublishedSuccess = prometheus.NewCounter(prometheus.CounterOpts{
Name: "ntfy_unifiedpush_published_success", Name: "ntfy_unifiedpush_published_success",
}) })
@ -95,6 +111,10 @@ func initMetrics() {
metricEmailsPublishedFailure, metricEmailsPublishedFailure,
metricEmailsReceivedSuccess, metricEmailsReceivedSuccess,
metricEmailsReceivedFailure, metricEmailsReceivedFailure,
metricSMSSentSuccess,
metricSMSSentFailure,
metricCallsMadeSuccess,
metricCallsMadeFailure,
metricUnifiedPushPublishedSuccess, metricUnifiedPushPublishedSuccess,
metricMatrixPublishedSuccess, metricMatrixPublishedSuccess,
metricMatrixPublishedFailure, metricMatrixPublishedFailure,

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"github.com/prometheus/client_golang/prometheus"
"heckel.io/ntfy/log" "heckel.io/ntfy/log"
"heckel.io/ntfy/util" "heckel.io/ntfy/util"
"io" "io"
@ -36,7 +37,7 @@ func (s *Server) sendSMS(v *visitor, r *http.Request, m *message, to string) {
data.Set("From", s.config.TwilioFromNumber) data.Set("From", s.config.TwilioFromNumber)
data.Set("To", to) data.Set("To", to)
data.Set("Body", body) data.Set("Body", body)
s.performTwilioRequest(v, r, m, twilioMessageEndpoint, to, body, data) s.performTwilioRequest(v, r, m, metricSMSSentSuccess, metricSMSSentFailure, twilioMessageEndpoint, to, body, data)
} }
func (s *Server) callPhone(v *visitor, r *http.Request, m *message, to string) { func (s *Server) callPhone(v *visitor, r *http.Request, m *message, to string) {
@ -45,10 +46,10 @@ func (s *Server) callPhone(v *visitor, r *http.Request, m *message, to string) {
data.Set("From", s.config.TwilioFromNumber) data.Set("From", s.config.TwilioFromNumber)
data.Set("To", to) data.Set("To", to)
data.Set("Twiml", body) data.Set("Twiml", body)
s.performTwilioRequest(v, r, m, twilioCallEndpoint, to, body, data) s.performTwilioRequest(v, r, m, metricCallsMadeSuccess, metricCallsMadeFailure, twilioCallEndpoint, to, body, data)
} }
func (s *Server) performTwilioRequest(v *visitor, r *http.Request, m *message, endpoint, to, body string, data url.Values) { func (s *Server) performTwilioRequest(v *visitor, r *http.Request, m *message, msuccess, mfailure prometheus.Counter, endpoint, to, body string, data url.Values) {
logContext := log.Context{ logContext := log.Context{
"twilio_from": s.config.TwilioFromNumber, "twilio_from": s.config.TwilioFromNumber,
"twilio_to": to, "twilio_to": to,
@ -66,6 +67,7 @@ func (s *Server) performTwilioRequest(v *visitor, r *http.Request, m *message, e
Field("twilio_response", response). Field("twilio_response", response).
Err(err). Err(err).
Warn("Error sending Twilio request") Warn("Error sending Twilio request")
minc(mfailure)
return return
} }
if ev.IsTrace() { if ev.IsTrace() {
@ -73,6 +75,7 @@ func (s *Server) performTwilioRequest(v *visitor, r *http.Request, m *message, e
} else if ev.IsDebug() { } else if ev.IsDebug() {
ev.Debug("Received successful Twilio response") ev.Debug("Received successful Twilio response")
} }
minc(msuccess)
} }
func (s *Server) performTwilioRequestInternal(endpoint string, data url.Values) (string, error) { func (s *Server) performTwilioRequestInternal(endpoint string, data url.Values) (string, error) {

View File

@ -2,37 +2,113 @@ package server
import ( import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"io"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing" "testing"
) )
func TestServer_Twilio_SMS(t *testing.T) { func TestServer_Twilio_SMS(t *testing.T) {
var called atomic.Bool
twilioServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
require.Nil(t, err)
require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Messages.json", r.URL.Path)
require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
require.Equal(t, "Body=test%0A%0A--%0AThis+message+was+sent+by+9.9.9.9+via+ntfy.sh%2Fmytopic&From=%2B1234567890&To=%2B11122233344", string(body))
called.Store(true)
}))
defer twilioServer.Close()
c := newTestConfig(t) c := newTestConfig(t)
c.TwilioBaseURL = "http://" c.BaseURL = "https://ntfy.sh"
c.TwilioAccount = "AC123" c.TwilioBaseURL = twilioServer.URL
c.TwilioAuthToken = "secret-token" c.TwilioAccount = "AC1234567890"
c.TwilioFromNumber = "+123456789" c.TwilioAuthToken = "AAEAA1234567890"
c.TwilioFromNumber = "+1234567890"
s := newTestServer(t, c) s := newTestServer(t, c)
response := request(t, s, "POST", "/mytopic", "test", map[string]string{ response := request(t, s, "POST", "/mytopic", "test", map[string]string{
"SMS": "+11122233344", "SMS": "+11122233344",
}) })
require.Equal(t, 1, toMessage(t, response.Body.String()).Priority) require.Equal(t, "test", toMessage(t, response.Body.String()).Message)
waitFor(t, func() bool {
response = request(t, s, "GET", "/mytopic/send?priority=low", "test", nil) return called.Load()
require.Equal(t, 2, toMessage(t, response.Body.String()).Priority) })
}
response = request(t, s, "GET", "/mytopic/send?priority=default", "test", nil)
require.Equal(t, 3, toMessage(t, response.Body.String()).Priority) func TestServer_Twilio_Call(t *testing.T) {
var called atomic.Bool
response = request(t, s, "GET", "/mytopic/send?priority=high", "test", nil) twilioServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, 4, toMessage(t, response.Body.String()).Priority) body, err := io.ReadAll(r.Body)
require.Nil(t, err)
response = request(t, s, "GET", "/mytopic/send?priority=max", "test", nil) require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path)
require.Equal(t, 5, toMessage(t, response.Body.String()).Priority) require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EYou+have+a+message+from+notify+on+topic+mytopic.+Message%3A%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3Ethis+message+has%26%23xA%3Ba+new+line+and+%26lt%3Bbrackets%26gt%3B%21%26%23xA%3Band+%26%2334%3Bquotes+and+other+%26%2339%3Bquotes%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EEnd+message.%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EThis+message+was+sent+by+9.9.9.9+via+127.0.0.1%3A12345%2Fmytopic%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%3C%2FResponse%3E", string(body))
response = request(t, s, "GET", "/mytopic/trigger?priority=urgent", "test", nil) called.Store(true)
require.Equal(t, 5, toMessage(t, response.Body.String()).Priority) }))
defer twilioServer.Close()
response = request(t, s, "GET", "/mytopic/trigger?priority=INVALID", "test", nil)
require.Equal(t, 40007, toHTTPError(t, response.Body.String()).Code) c := newTestConfig(t)
c.TwilioBaseURL = twilioServer.URL
c.TwilioAccount = "AC1234567890"
c.TwilioAuthToken = "AAEAA1234567890"
c.TwilioFromNumber = "+1234567890"
s := newTestServer(t, c)
body := `this message has
a new line and <brackets>!
and "quotes and other 'quotes`
response := request(t, s, "POST", "/mytopic", body, map[string]string{
"x-call": "+11122233344",
})
require.Equal(t, "this message has\na new line and <brackets>!\nand \"quotes and other 'quotes", toMessage(t, response.Body.String()).Message)
waitFor(t, func() bool {
return called.Load()
})
}
func TestServer_Twilio_Call_InvalidNumber(t *testing.T) {
c := newTestConfig(t)
c.TwilioBaseURL = "https://127.0.0.1"
c.TwilioAccount = "AC1234567890"
c.TwilioAuthToken = "AAEAA1234567890"
c.TwilioFromNumber = "+1234567890"
s := newTestServer(t, c)
response := request(t, s, "POST", "/mytopic", "test", map[string]string{
"x-call": "+invalid",
})
require.Equal(t, 40031, toHTTPError(t, response.Body.String()).Code)
}
func TestServer_Twilio_SMS_InvalidNumber(t *testing.T) {
c := newTestConfig(t)
c.TwilioBaseURL = "https://127.0.0.1"
c.TwilioAccount = "AC1234567890"
c.TwilioAuthToken = "AAEAA1234567890"
c.TwilioFromNumber = "+1234567890"
s := newTestServer(t, c)
response := request(t, s, "POST", "/mytopic", "test", map[string]string{
"x-sms": "+invalid",
})
require.Equal(t, 40031, toHTTPError(t, response.Body.String()).Code)
}
func TestServer_Twilio_SMS_Unconfigured(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "POST", "/mytopic", "test", map[string]string{
"x-sms": "+1234",
})
require.Equal(t, 40030, toHTTPError(t, response.Body.String()).Code)
}
func TestServer_Twilio_Call_Unconfigured(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "POST", "/mytopic", "test", map[string]string{
"x-call": "+1234",
})
require.Equal(t, 40030, toHTTPError(t, response.Body.String()).Code)
} }