Metrics, tests
parent
fa2c09316c
commit
113b7c8a08
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue