|
|
@@ -7,6 +7,7 @@ import (
|
|
|
"firebase.google.com/go/messaging"
|
|
|
"fmt"
|
|
|
"github.com/stretchr/testify/require"
|
|
|
+ "heckel.io/ntfy/util"
|
|
|
"net/http"
|
|
|
"net/http/httptest"
|
|
|
"os"
|
|
|
@@ -163,20 +164,13 @@ func TestServer_StaticSites(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)
|
|
|
- truncated := body[0:4096]
|
|
|
response := request(t, s, "PUT", "/mytopic", body, nil)
|
|
|
- msg := toMessage(t, response.Body.String())
|
|
|
- require.NotEmpty(t, msg.ID)
|
|
|
- require.Equal(t, truncated, msg.Message)
|
|
|
- require.Equal(t, 4096, len(msg.Message))
|
|
|
-
|
|
|
- response = request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
|
|
|
- messages := toMessages(t, response.Body.String())
|
|
|
- require.Equal(t, 1, len(messages))
|
|
|
- require.Equal(t, truncated, messages[0].Message)
|
|
|
+ require.Equal(t, 400, response.Code)
|
|
|
}
|
|
|
|
|
|
func TestServer_PublishPriority(t *testing.T) {
|
|
|
@@ -205,6 +199,9 @@ func TestServer_PublishPriority(t *testing.T) {
|
|
|
|
|
|
response = request(t, s, "GET", "/mytopic/trigger?priority=urgent", "test", nil)
|
|
|
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) {
|
|
|
@@ -268,13 +265,28 @@ func TestServer_PublishAtTooShortDelay(t *testing.T) {
|
|
|
|
|
|
func TestServer_PublishAtTooLongDelay(t *testing.T) {
|
|
|
s := newTestServer(t, newTestConfig(t))
|
|
|
-
|
|
|
response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{
|
|
|
"In": "99999999h",
|
|
|
})
|
|
|
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) {
|
|
|
s := newTestServer(t, newTestConfig(t))
|
|
|
|
|
|
@@ -356,6 +368,19 @@ func TestServer_PublishAndPollSince(t *testing.T) {
|
|
|
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=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) {
|
|
|
@@ -396,6 +421,13 @@ func TestServer_PublishFirebase(t *testing.T) {
|
|
|
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) {
|
|
|
s := newTestServer(t, newTestConfig(t))
|
|
|
|
|
|
@@ -649,9 +681,241 @@ func TestServer_MaybeTruncateFCMMessage_NotTooLong(t *testing.T) {
|
|
|
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())
|
|
|
+
|
|
|
+ // Slightly unrelated cross-test: make sure we add an owner for internal attachments
|
|
|
+ size, err := s.cache.AttachmentsSize("9.9.9.9") // See request()
|
|
|
+ require.Nil(t, err)
|
|
|
+ require.Equal(t, int64(5000), size)
|
|
|
+}
|
|
|
+
|
|
|
+func TestServer_PublishAttachmentShortWithFilename(t *testing.T) {
|
|
|
+ c := newTestConfig(t)
|
|
|
+ c.BehindProxy = true
|
|
|
+ s := newTestServer(t, c)
|
|
|
+ content := "this is an ATTACHMENT"
|
|
|
+ response := request(t, s, "PUT", "/mytopic?f=myfile.txt", content, map[string]string{
|
|
|
+ "X-Forwarded-For": "1.2.3.4",
|
|
|
+ })
|
|
|
+ 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())
|
|
|
+
|
|
|
+ // Slightly unrelated cross-test: make sure we add an owner for internal attachments
|
|
|
+ size, err := s.cache.AttachmentsSize("1.2.3.4")
|
|
|
+ require.Nil(t, err)
|
|
|
+ require.Equal(t, int64(21), size)
|
|
|
+}
|
|
|
+
|
|
|
+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)
|
|
|
+
|
|
|
+ // Slightly unrelated cross-test: make sure we don't add an owner for external attachments
|
|
|
+ size, err := s.cache.AttachmentsSize("127.0.0.1")
|
|
|
+ require.Nil(t, err)
|
|
|
+ require.Equal(t, int64(0), size)
|
|
|
+}
|
|
|
+
|
|
|
+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)
|
|
|
+ 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 TestServer_PublishAttachmentBandwidthLimit(t *testing.T) {
|
|
|
+ content := util.RandomString(5000) // > 4096
|
|
|
+
|
|
|
+ c := newTestConfig(t)
|
|
|
+ c.VisitorAttachmentDailyBandwidthLimit = 5*5000 + 123 // A little more than 1 upload and 3 downloads
|
|
|
+ s := newTestServer(t, c)
|
|
|
+
|
|
|
+ // Publish attachment
|
|
|
+ response := request(t, s, "PUT", "/mytopic", content, nil)
|
|
|
+ msg := toMessage(t, response.Body.String())
|
|
|
+ require.Contains(t, msg.Attachment.URL, "http://127.0.0.1:12345/file/")
|
|
|
+
|
|
|
+ // Get it 4 times successfully
|
|
|
+ path := strings.TrimPrefix(msg.Attachment.URL, "http://127.0.0.1:12345")
|
|
|
+ for i := 1; i <= 4; i++ { // 4 successful downloads
|
|
|
+ response = request(t, s, "GET", path, "", nil)
|
|
|
+ require.Equal(t, 200, response.Code)
|
|
|
+ require.Equal(t, content, response.Body.String())
|
|
|
+ }
|
|
|
+
|
|
|
+ // And then fail with a 429
|
|
|
+ response = request(t, s, "GET", path, "", nil)
|
|
|
+ err := toHTTPError(t, response.Body.String())
|
|
|
+ require.Equal(t, 429, response.Code)
|
|
|
+ require.Equal(t, 42905, err.Code)
|
|
|
+}
|
|
|
+
|
|
|
+func TestServer_PublishAttachmentBandwidthLimitUploadOnly(t *testing.T) {
|
|
|
+ content := util.RandomString(5000) // > 4096
|
|
|
+
|
|
|
+ c := newTestConfig(t)
|
|
|
+ c.VisitorAttachmentDailyBandwidthLimit = 5*5000 + 500 // 5 successful uploads
|
|
|
+ s := newTestServer(t, c)
|
|
|
+
|
|
|
+ // 5 successful uploads
|
|
|
+ for i := 1; i <= 5; i++ {
|
|
|
+ response := request(t, s, "PUT", "/mytopic", content, nil)
|
|
|
+ msg := toMessage(t, response.Body.String())
|
|
|
+ require.Contains(t, msg.Attachment.URL, "http://127.0.0.1:12345/file/")
|
|
|
+ }
|
|
|
+
|
|
|
+ // And a failed one
|
|
|
+ response := request(t, s, "PUT", "/mytopic", content, nil)
|
|
|
+ err := toHTTPError(t, response.Body.String())
|
|
|
+ require.Equal(t, 400, response.Code)
|
|
|
+ require.Equal(t, 40012, err.Code)
|
|
|
+}
|
|
|
+
|
|
|
func newTestConfig(t *testing.T) *Config {
|
|
|
conf := NewConfig()
|
|
|
+ conf.BaseURL = "http://127.0.0.1:12345"
|
|
|
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
|
|
|
+ conf.AttachmentCacheDir = t.TempDir()
|
|
|
return conf
|
|
|
}
|
|
|
|
|
|
@@ -669,6 +933,7 @@ func request(t *testing.T, s *Server, method, url, body string, headers map[stri
|
|
|
if err != nil {
|
|
|
t.Fatal(err)
|
|
|
}
|
|
|
+ req.RemoteAddr = "9.9.9.9" // Used for tests
|
|
|
for k, v := range headers {
|
|
|
req.Header.Set(k, v)
|
|
|
}
|