Lots of tests

This commit is contained in:
Philipp Heckel 2022-01-12 11:05:04 -05:00
parent 68a324c206
commit f6b9ebb693
2 changed files with 221 additions and 7 deletions

View file

@ -127,7 +127,7 @@ var (
errHTTPTooManyRequestsLimitRequests = &errHTTP{42901, http.StatusTooManyRequests, "limit reached: too many requests, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitRequests = &errHTTP{42901, http.StatusTooManyRequests, "limit reached: too many requests, please be nice", "https://ntfy.sh/docs/publish/#limitations"}
errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"}
errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"}
errHTTPTooManyRequestsLimitGlobalTopics = &errHTTP{42904, http.StatusTooManyRequests, "limit reached: the total number of topics on the server has been reached, please contact the admin", "https://ntfy.sh/docs/publish/#limitations"} errHTTPTooManyRequestsLimitTotalTopics = &errHTTP{42904, http.StatusTooManyRequests, "limit reached: the total number of topics on the server has been reached, please contact the admin", "https://ntfy.sh/docs/publish/#limitations"}
errHTTPBadRequestEmailDisabled = &errHTTP{40001, http.StatusBadRequest, "e-mail notifications are not enabled", "https://ntfy.sh/docs/config/#e-mail-notifications"} errHTTPBadRequestEmailDisabled = &errHTTP{40001, http.StatusBadRequest, "e-mail notifications are not enabled", "https://ntfy.sh/docs/config/#e-mail-notifications"}
errHTTPBadRequestDelayNoCache = &errHTTP{40002, http.StatusBadRequest, "cannot disable cache for delayed message", ""} errHTTPBadRequestDelayNoCache = &errHTTP{40002, http.StatusBadRequest, "cannot disable cache for delayed message", ""}
errHTTPBadRequestDelayNoEmail = &errHTTP{40003, http.StatusBadRequest, "delayed e-mail notifications are not supported", ""} errHTTPBadRequestDelayNoEmail = &errHTTP{40003, http.StatusBadRequest, "delayed e-mail notifications are not supported", ""}
@ -431,7 +431,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, _ *visitor)
if err != nil { if err != nil {
return errHTTPNotFound return errHTTPNotFound
} }
w.Header().Set("Length", fmt.Sprintf("%d", stat.Size())) w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size()))
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {
return err return err
@ -503,7 +503,7 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
firebase = readParam(r, "x-firebase", "firebase") != "no" firebase = readParam(r, "x-firebase", "firebase") != "no"
m.Title = readParam(r, "x-title", "title", "t") m.Title = readParam(r, "x-title", "title", "t")
m.Click = readParam(r, "x-click", "click") m.Click = readParam(r, "x-click", "click")
attach := readParam(r, "x-attachment", "attachment", "attach", "a") attach := readParam(r, "x-attach", "attach", "a")
filename := readParam(r, "x-filename", "filename", "file", "f") filename := readParam(r, "x-filename", "filename", "file", "f")
if attach != "" || filename != "" { if attach != "" || filename != "" {
m.Attachment = &attachment{} m.Attachment = &attachment{}
@ -617,7 +617,7 @@ func (s *Server) handleBodyAsMessage(m *message, body *util.PeakedReadCloser) er
} }
func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, body *util.PeakedReadCloser) error { func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, body *util.PeakedReadCloser) error {
if s.fileCache == nil { if s.fileCache == nil || s.config.BaseURL == "" || s.config.AttachmentCacheDir == "" {
return errHTTPBadRequestAttachmentsDisallowed return errHTTPBadRequestAttachmentsDisallowed
} else if m.Time > time.Now().Add(s.config.AttachmentExpiryDuration).Unix() { } else if m.Time > time.Now().Add(s.config.AttachmentExpiryDuration).Unix() {
return errHTTPBadRequestAttachmentsExpiryBeforeDelivery return errHTTPBadRequestAttachmentsExpiryBeforeDelivery
@ -871,7 +871,7 @@ func (s *Server) topicsFromIDs(ids ...string) ([]*topic, error) {
} }
if _, ok := s.topics[id]; !ok { if _, ok := s.topics[id]; !ok {
if len(s.topics) >= s.config.TotalTopicLimit { if len(s.topics) >= s.config.TotalTopicLimit {
return nil, errHTTPTooManyRequestsLimitGlobalTopics return nil, errHTTPTooManyRequestsLimitTotalTopics
} }
s.topics[id] = newTopic(id) s.topics[id] = newTopic(id)
} }

View file

@ -7,6 +7,7 @@ import (
"firebase.google.com/go/messaging" "firebase.google.com/go/messaging"
"fmt" "fmt"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"heckel.io/ntfy/util"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@ -163,7 +164,9 @@ func TestServer_StaticSites(t *testing.T) {
} }
func TestServer_PublishLargeMessage(t *testing.T) { func TestServer_PublishLargeMessage(t *testing.T) {
s := newTestServer(t, newTestConfig(t)) c := newTestConfig(t)
c.AttachmentCacheDir = "" // Disable attachments
s := newTestServer(t, c)
body := strings.Repeat("this is a large message", 5000) body := strings.Repeat("this is a large message", 5000)
response := request(t, s, "PUT", "/mytopic", body, nil) response := request(t, s, "PUT", "/mytopic", body, nil)
@ -196,6 +199,9 @@ func TestServer_PublishPriority(t *testing.T) {
response = request(t, s, "GET", "/mytopic/trigger?priority=urgent", "test", nil) response = request(t, s, "GET", "/mytopic/trigger?priority=urgent", "test", nil)
require.Equal(t, 5, toMessage(t, response.Body.String()).Priority) require.Equal(t, 5, toMessage(t, response.Body.String()).Priority)
response = request(t, s, "GET", "/mytopic/trigger?priority=INVALID", "test", nil)
require.Equal(t, 40007, toHTTPError(t, response.Body.String()).Code)
} }
func TestServer_PublishNoCache(t *testing.T) { func TestServer_PublishNoCache(t *testing.T) {
@ -259,13 +265,28 @@ func TestServer_PublishAtTooShortDelay(t *testing.T) {
func TestServer_PublishAtTooLongDelay(t *testing.T) { func TestServer_PublishAtTooLongDelay(t *testing.T) {
s := newTestServer(t, newTestConfig(t)) s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{ response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{
"In": "99999999h", "In": "99999999h",
}) })
require.Equal(t, 400, response.Code) require.Equal(t, 400, response.Code)
} }
func TestServer_PublishAtInvalidDelay(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic?delay=INVALID", "a message", nil)
err := toHTTPError(t, response.Body.String())
require.Equal(t, 400, response.Code)
require.Equal(t, 40004, err.Code)
}
func TestServer_PublishAtTooLarge(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic?x-in=99999h", "a message", nil)
err := toHTTPError(t, response.Body.String())
require.Equal(t, 400, response.Code)
require.Equal(t, 40006, err.Code)
}
func TestServer_PublishAtAndPrune(t *testing.T) { func TestServer_PublishAtAndPrune(t *testing.T) {
s := newTestServer(t, newTestConfig(t)) s := newTestServer(t, newTestConfig(t))
@ -347,6 +368,19 @@ func TestServer_PublishAndPollSince(t *testing.T) {
messages := toMessages(t, response.Body.String()) messages := toMessages(t, response.Body.String())
require.Equal(t, 1, len(messages)) require.Equal(t, 1, len(messages))
require.Equal(t, "test 2", messages[0].Message) require.Equal(t, "test 2", messages[0].Message)
response = request(t, s, "GET", "/mytopic/json?poll=1&since=10s", "", nil)
messages = toMessages(t, response.Body.String())
require.Equal(t, 2, len(messages))
require.Equal(t, "test 1", messages[0].Message)
response = request(t, s, "GET", "/mytopic/json?poll=1&since=100ms", "", nil)
messages = toMessages(t, response.Body.String())
require.Equal(t, 1, len(messages))
require.Equal(t, "test 2", messages[0].Message)
response = request(t, s, "GET", "/mytopic/json?poll=1&since=INVALID", "", nil)
require.Equal(t, 40008, toHTTPError(t, response.Body.String()).Code)
} }
func TestServer_PublishViaGET(t *testing.T) { func TestServer_PublishViaGET(t *testing.T) {
@ -387,6 +421,13 @@ func TestServer_PublishFirebase(t *testing.T) {
time.Sleep(500 * time.Millisecond) // Time for sends time.Sleep(500 * time.Millisecond) // Time for sends
} }
func TestServer_PublishInvalidTopic(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
s.mailer = &testMailer{}
response := request(t, s, "PUT", "/docs", "fail", nil)
require.Equal(t, 40010, toHTTPError(t, response.Body.String()).Code)
}
func TestServer_PollWithQueryFilters(t *testing.T) { func TestServer_PollWithQueryFilters(t *testing.T) {
s := newTestServer(t, newTestConfig(t)) s := newTestServer(t, newTestConfig(t))
@ -640,9 +681,175 @@ func TestServer_MaybeTruncateFCMMessage_NotTooLong(t *testing.T) {
require.Equal(t, "", notTruncatedFCMMessage.Data["truncated"]) require.Equal(t, "", notTruncatedFCMMessage.Data["truncated"])
} }
func TestServer_PublishAttachment(t *testing.T) {
content := util.RandomString(5000) // > 4096
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic", content, nil)
msg := toMessage(t, response.Body.String())
require.Equal(t, "attachment.txt", msg.Attachment.Name)
require.Equal(t, "text/plain; charset=utf-8", msg.Attachment.Type)
require.Equal(t, int64(5000), msg.Attachment.Size)
require.GreaterOrEqual(t, msg.Attachment.Expires, time.Now().Add(3*time.Hour).Unix())
require.Contains(t, msg.Attachment.URL, "http://127.0.0.1:12345/file/")
require.Equal(t, "", msg.Attachment.Owner) // Should never be returned
require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, msg.ID))
path := strings.TrimPrefix(msg.Attachment.URL, "http://127.0.0.1:12345")
response = request(t, s, "GET", path, "", nil)
require.Equal(t, 200, response.Code)
require.Equal(t, "5000", response.Header().Get("Content-Length"))
require.Equal(t, content, response.Body.String())
}
func TestServer_PublishAttachmentShortWithFilename(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
content := "this is an ATTACHMENT"
response := request(t, s, "PUT", "/mytopic?f=myfile.txt", content, nil)
msg := toMessage(t, response.Body.String())
require.Equal(t, "myfile.txt", msg.Attachment.Name)
require.Equal(t, "text/plain; charset=utf-8", msg.Attachment.Type)
require.Equal(t, int64(21), msg.Attachment.Size)
require.GreaterOrEqual(t, msg.Attachment.Expires, time.Now().Add(3*time.Hour).Unix())
require.Contains(t, msg.Attachment.URL, "http://127.0.0.1:12345/file/")
require.Equal(t, "", msg.Attachment.Owner) // Should never be returned
require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, msg.ID))
path := strings.TrimPrefix(msg.Attachment.URL, "http://127.0.0.1:12345")
response = request(t, s, "GET", path, "", nil)
require.Equal(t, 200, response.Code)
require.Equal(t, "21", response.Header().Get("Content-Length"))
require.Equal(t, content, response.Body.String())
}
func TestServer_PublishAttachmentExternalWithoutFilename(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic", "", map[string]string{
"Attach": "https://upload.wikimedia.org/wikipedia/commons/f/fd/Pink_flower.jpg",
})
msg := toMessage(t, response.Body.String())
require.Equal(t, "You received a file: Pink_flower.jpg", msg.Message)
require.Equal(t, "Pink_flower.jpg", msg.Attachment.Name)
require.Equal(t, "image/jpeg", msg.Attachment.Type)
require.Equal(t, int64(190173), msg.Attachment.Size)
require.Equal(t, int64(0), msg.Attachment.Expires)
require.Equal(t, "https://upload.wikimedia.org/wikipedia/commons/f/fd/Pink_flower.jpg", msg.Attachment.URL)
require.Equal(t, "", msg.Attachment.Owner)
}
func TestServer_PublishAttachmentExternalWithFilename(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic", "This is a custom message", map[string]string{
"X-Attach": "https://upload.wikimedia.org/wikipedia/commons/f/fd/Pink_flower.jpg",
"File": "some file.jpg",
})
msg := toMessage(t, response.Body.String())
require.Equal(t, "This is a custom message", msg.Message)
require.Equal(t, "some file.jpg", msg.Attachment.Name)
require.Equal(t, "image/jpeg", msg.Attachment.Type)
require.Equal(t, int64(190173), msg.Attachment.Size)
require.Equal(t, int64(0), msg.Attachment.Expires)
require.Equal(t, "https://upload.wikimedia.org/wikipedia/commons/f/fd/Pink_flower.jpg", msg.Attachment.URL)
require.Equal(t, "", msg.Attachment.Owner)
}
func TestServer_PublishAttachmentBadURL(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic?a=not+a+URL", "", nil)
err := toHTTPError(t, response.Body.String())
require.Equal(t, 400, response.Code)
require.Equal(t, 400, err.HTTPCode)
require.Equal(t, 40013, err.Code)
}
func TestServer_PublishAttachmentTooLargeContentLength(t *testing.T) {
content := util.RandomString(5000) // > 4096
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic", content, map[string]string{
"Content-Length": "20000000",
})
err := toHTTPError(t, response.Body.String())
require.Equal(t, 400, response.Code)
require.Equal(t, 400, err.HTTPCode)
require.Equal(t, 40012, err.Code)
}
func TestServer_PublishAttachmentTooLargeBodyAttachmentFileSizeLimit(t *testing.T) {
content := util.RandomString(5001) // > 5000, see below
c := newTestConfig(t)
c.AttachmentFileSizeLimit = 5000
s := newTestServer(t, c)
response := request(t, s, "PUT", "/mytopic", content, nil)
err := toHTTPError(t, response.Body.String())
require.Equal(t, 400, response.Code)
require.Equal(t, 400, err.HTTPCode)
require.Equal(t, 40012, err.Code)
}
func TestServer_PublishAttachmentExpiryBeforeDelivery(t *testing.T) {
c := newTestConfig(t)
c.AttachmentExpiryDuration = 10 * time.Minute
s := newTestServer(t, c)
response := request(t, s, "PUT", "/mytopic", util.RandomString(5000), map[string]string{
"Delay": "11 min", // > AttachmentExpiryDuration
})
err := toHTTPError(t, response.Body.String())
require.Equal(t, 400, response.Code)
require.Equal(t, 400, err.HTTPCode)
require.Equal(t, 40017, err.Code)
}
func TestServer_PublishAttachmentTooLargeBodyVisitorAttachmentTotalSizeLimit(t *testing.T) {
c := newTestConfig(t)
c.VisitorAttachmentTotalSizeLimit = 10000
s := newTestServer(t, c)
response := request(t, s, "PUT", "/mytopic", util.RandomString(5000), nil)
msg := toMessage(t, response.Body.String())
require.Equal(t, 200, response.Code)
require.Equal(t, "You received a file: attachment.txt", msg.Message)
require.Equal(t, int64(5000), msg.Attachment.Size)
content := util.RandomString(5001) // 5000+5001 > , see below
response = request(t, s, "PUT", "/mytopic", content, nil)
err := toHTTPError(t, response.Body.String())
require.Equal(t, 400, response.Code)
require.Equal(t, 400, err.HTTPCode)
require.Equal(t, 40012, err.Code)
}
func TestServer_PublishAttachmentAndPrune(t *testing.T) {
content := util.RandomString(5000) // > 4096
c := newTestConfig(t)
c.AttachmentExpiryDuration = time.Millisecond // Hack
s := newTestServer(t, c)
// Publish and make sure we can retrieve it
response := request(t, s, "PUT", "/mytopic", content, nil)
println(response.Body.String())
msg := toMessage(t, response.Body.String())
require.Contains(t, msg.Attachment.URL, "http://127.0.0.1:12345/file/")
file := filepath.Join(s.config.AttachmentCacheDir, msg.ID)
require.FileExists(t, file)
path := strings.TrimPrefix(msg.Attachment.URL, "http://127.0.0.1:12345")
response = request(t, s, "GET", path, "", nil)
require.Equal(t, 200, response.Code)
require.Equal(t, content, response.Body.String())
// Prune and makes sure it's gone
time.Sleep(time.Second) // Sigh ...
s.updateStatsAndPrune()
require.NoFileExists(t, file)
response = request(t, s, "GET", path, "", nil)
require.Equal(t, 404, response.Code)
}
func newTestConfig(t *testing.T) *Config { func newTestConfig(t *testing.T) *Config {
conf := NewConfig() conf := NewConfig()
conf.BaseURL = "http://127.0.0.1:12345"
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db") conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
conf.AttachmentCacheDir = t.TempDir()
return conf return conf
} }
@ -702,6 +909,13 @@ func toMessage(t *testing.T, s string) *message {
return &m return &m
} }
func tempFile(t *testing.T, length int) (filename string, content string) {
filename = filepath.Join(t.TempDir(), util.RandomString(10))
content = util.RandomString(length)
require.Nil(t, os.WriteFile(filename, []byte(content), 0600))
return
}
func toHTTPError(t *testing.T, s string) *errHTTP { func toHTTPError(t *testing.T, s string) *errHTTP {
var e errHTTP var e errHTTP
require.Nil(t, json.NewDecoder(strings.NewReader(s)).Decode(&e)) require.Nil(t, json.NewDecoder(strings.NewReader(s)).Decode(&e))