Test Firebase stuff
parent
b1bbbf0103
commit
a77f89d302
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
firebase "firebase.google.com/go"
|
||||
"firebase.google.com/go/messaging"
|
||||
"fmt"
|
||||
|
@ -10,6 +11,29 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
fcmMessageLimit = 4000
|
||||
)
|
||||
|
||||
// maybeTruncateFCMMessage performs best-effort truncation of FCM messages.
|
||||
// The docs say the limit is 4000 characters, but during testing it wasn't quite clear
|
||||
// what fields matter; so we're just capping the serialized JSON to 4000 bytes.
|
||||
func maybeTruncateFCMMessage(m *messaging.Message) *messaging.Message {
|
||||
s, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return m
|
||||
}
|
||||
if len(s) > fcmMessageLimit {
|
||||
over := len(s) - fcmMessageLimit + 16 // = len("truncated":"1",), sigh ...
|
||||
message, ok := m.Data["message"]
|
||||
if ok && len(message) > over {
|
||||
m.Data["truncated"] = "1"
|
||||
m.Data["message"] = message[:len(message)-over]
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func createFirebaseSubscriber(credentialsFile string, auther auth.Auther) (subscriber, error) {
|
||||
fb, err := firebase.NewApp(context.Background(), nil, option.WithCredentialsFile(credentialsFile))
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"firebase.google.com/go/messaging"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/auth"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testAuther struct {
|
||||
Allow bool
|
||||
}
|
||||
|
||||
func (t testAuther) Authenticate(username, password string) (*auth.User, error) {
|
||||
return nil, errors.New("not used")
|
||||
}
|
||||
|
||||
func (t testAuther) Authorize(user *auth.User, topic string, perm auth.Permission) error {
|
||||
if t.Allow {
|
||||
return nil
|
||||
}
|
||||
return errors.New("unauthorized")
|
||||
}
|
||||
|
||||
func TestToFirebaseMessage_Keepalive(t *testing.T) {
|
||||
m := newKeepaliveMessage("mytopic")
|
||||
fbm, err := toFirebaseMessage(m, nil)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "mytopic", fbm.Topic)
|
||||
require.Nil(t, fbm.Android)
|
||||
require.Equal(t, map[string]string{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": m.Event,
|
||||
"topic": m.Topic,
|
||||
}, fbm.Data)
|
||||
}
|
||||
|
||||
func TestToFirebaseMessage_Open(t *testing.T) {
|
||||
m := newOpenMessage("mytopic")
|
||||
fbm, err := toFirebaseMessage(m, nil)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "mytopic", fbm.Topic)
|
||||
require.Nil(t, fbm.Android)
|
||||
require.Equal(t, map[string]string{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": m.Event,
|
||||
"topic": m.Topic,
|
||||
}, fbm.Data)
|
||||
}
|
||||
|
||||
func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
|
||||
m := newDefaultMessage("mytopic", "this is a message")
|
||||
m.Priority = 4
|
||||
m.Tags = []string{"tag 1", "tag2"}
|
||||
m.Click = "https://google.com"
|
||||
m.Title = "some title"
|
||||
m.Attachment = &attachment{
|
||||
Name: "some file.jpg",
|
||||
Type: "image/jpeg",
|
||||
Size: 12345,
|
||||
Expires: 98765543,
|
||||
URL: "https://example.com/file.jpg",
|
||||
Owner: "some-owner",
|
||||
}
|
||||
fbm, err := toFirebaseMessage(m, &testAuther{Allow: true})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "mytopic", fbm.Topic)
|
||||
require.Equal(t, &messaging.AndroidConfig{
|
||||
Priority: "high",
|
||||
}, fbm.Android)
|
||||
require.Equal(t, map[string]string{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": "message",
|
||||
"topic": "mytopic",
|
||||
"priority": "4",
|
||||
"tags": strings.Join(m.Tags, ","),
|
||||
"click": "https://google.com",
|
||||
"title": "some title",
|
||||
"message": "this is a message",
|
||||
"encoding": "",
|
||||
"attachment_name": "some file.jpg",
|
||||
"attachment_type": "image/jpeg",
|
||||
"attachment_size": "12345",
|
||||
"attachment_expires": "98765543",
|
||||
"attachment_url": "https://example.com/file.jpg",
|
||||
}, fbm.Data)
|
||||
}
|
||||
|
||||
func TestToFirebaseMessage_Message_Normal_Not_Allowed(t *testing.T) {
|
||||
m := newDefaultMessage("mytopic", "this is a message")
|
||||
m.Priority = 5
|
||||
fbm, err := toFirebaseMessage(m, &testAuther{Allow: false}) // Not allowed!
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "mytopic", fbm.Topic)
|
||||
require.Equal(t, &messaging.AndroidConfig{
|
||||
Priority: "high",
|
||||
}, fbm.Android)
|
||||
require.Equal(t, "", fbm.Data["message"])
|
||||
require.Equal(t, "", fbm.Data["priority"])
|
||||
require.Equal(t, map[string]string{
|
||||
"id": m.ID,
|
||||
"time": fmt.Sprintf("%d", m.Time),
|
||||
"event": "poll_request",
|
||||
"topic": "mytopic",
|
||||
}, fbm.Data)
|
||||
}
|
||||
|
||||
func TestMaybeTruncateFCMMessage(t *testing.T) {
|
||||
origMessage := strings.Repeat("this is a long string", 300)
|
||||
origFCMMessage := &messaging.Message{
|
||||
Topic: "mytopic",
|
||||
Data: map[string]string{
|
||||
"id": "abcdefg",
|
||||
"time": "1641324761",
|
||||
"event": "message",
|
||||
"topic": "mytopic",
|
||||
"priority": "0",
|
||||
"tags": "",
|
||||
"title": "",
|
||||
"message": origMessage,
|
||||
},
|
||||
Android: &messaging.AndroidConfig{
|
||||
Priority: "high",
|
||||
},
|
||||
}
|
||||
origMessageLength := len(origFCMMessage.Data["message"])
|
||||
serializedOrigFCMMessage, _ := json.Marshal(origFCMMessage)
|
||||
require.Greater(t, len(serializedOrigFCMMessage), fcmMessageLimit) // Pre-condition
|
||||
|
||||
truncatedFCMMessage := maybeTruncateFCMMessage(origFCMMessage)
|
||||
truncatedMessageLength := len(truncatedFCMMessage.Data["message"])
|
||||
serializedTruncatedFCMMessage, _ := json.Marshal(truncatedFCMMessage)
|
||||
require.Equal(t, fcmMessageLimit, len(serializedTruncatedFCMMessage))
|
||||
require.Equal(t, "1", truncatedFCMMessage.Data["truncated"])
|
||||
require.NotEqual(t, origMessageLength, truncatedMessageLength)
|
||||
}
|
||||
|
||||
func TestMaybeTruncateFCMMessage_NotTooLong(t *testing.T) {
|
||||
origMessage := "not really a long string"
|
||||
origFCMMessage := &messaging.Message{
|
||||
Topic: "mytopic",
|
||||
Data: map[string]string{
|
||||
"id": "abcdefg",
|
||||
"time": "1641324761",
|
||||
"event": "message",
|
||||
"topic": "mytopic",
|
||||
"priority": "0",
|
||||
"tags": "",
|
||||
"title": "",
|
||||
"message": origMessage,
|
||||
},
|
||||
}
|
||||
origMessageLength := len(origFCMMessage.Data["message"])
|
||||
serializedOrigFCMMessage, _ := json.Marshal(origFCMMessage)
|
||||
require.LessOrEqual(t, len(serializedOrigFCMMessage), fcmMessageLimit) // Pre-condition
|
||||
|
||||
notTruncatedFCMMessage := maybeTruncateFCMMessage(origFCMMessage)
|
||||
notTruncatedMessageLength := len(notTruncatedFCMMessage.Data["message"])
|
||||
serializedNotTruncatedFCMMessage, _ := json.Marshal(notTruncatedFCMMessage)
|
||||
require.Equal(t, origMessageLength, notTruncatedMessageLength)
|
||||
require.Equal(t, len(serializedOrigFCMMessage), len(serializedNotTruncatedFCMMessage))
|
||||
require.Equal(t, "", notTruncatedFCMMessage.Data["truncated"])
|
||||
}
|
|
@ -1,35 +1,10 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"firebase.google.com/go/messaging"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
fcmMessageLimit = 4000
|
||||
)
|
||||
|
||||
// maybeTruncateFCMMessage performs best-effort truncation of FCM messages.
|
||||
// The docs say the limit is 4000 characters, but during testing it wasn't quite clear
|
||||
// what fields matter; so we're just capping the serialized JSON to 4000 bytes.
|
||||
func maybeTruncateFCMMessage(m *messaging.Message) *messaging.Message {
|
||||
s, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return m
|
||||
}
|
||||
if len(s) > fcmMessageLimit {
|
||||
over := len(s) - fcmMessageLimit + 16 // = len("truncated":"1",), sigh ...
|
||||
message, ok := m.Data["message"]
|
||||
if ok && len(message) > over {
|
||||
m.Data["truncated"] = "1"
|
||||
m.Data["message"] = message[:len(message)-over]
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func readBoolParam(r *http.Request, defaultValue bool, names ...string) bool {
|
||||
value := strings.ToLower(readParam(r, names...))
|
||||
if value == "" {
|
||||
|
|
|
@ -1,66 +1,29 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"firebase.google.com/go/messaging"
|
||||
"github.com/stretchr/testify/require"
|
||||
"strings"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMaybeTruncateFCMMessage(t *testing.T) {
|
||||
origMessage := strings.Repeat("this is a long string", 300)
|
||||
origFCMMessage := &messaging.Message{
|
||||
Topic: "mytopic",
|
||||
Data: map[string]string{
|
||||
"id": "abcdefg",
|
||||
"time": "1641324761",
|
||||
"event": "message",
|
||||
"topic": "mytopic",
|
||||
"priority": "0",
|
||||
"tags": "",
|
||||
"title": "",
|
||||
"message": origMessage,
|
||||
},
|
||||
Android: &messaging.AndroidConfig{
|
||||
Priority: "high",
|
||||
},
|
||||
}
|
||||
origMessageLength := len(origFCMMessage.Data["message"])
|
||||
serializedOrigFCMMessage, _ := json.Marshal(origFCMMessage)
|
||||
require.Greater(t, len(serializedOrigFCMMessage), fcmMessageLimit) // Pre-condition
|
||||
func TestReadBoolParam(t *testing.T) {
|
||||
r, _ := http.NewRequest("GET", "https://ntfy.sh/mytopic?up=1&firebase=no", nil)
|
||||
up := readBoolParam(r, false, "x-up", "up")
|
||||
firebase := readBoolParam(r, true, "x-firebase", "firebase")
|
||||
require.Equal(t, true, up)
|
||||
require.Equal(t, false, firebase)
|
||||
|
||||
truncatedFCMMessage := maybeTruncateFCMMessage(origFCMMessage)
|
||||
truncatedMessageLength := len(truncatedFCMMessage.Data["message"])
|
||||
serializedTruncatedFCMMessage, _ := json.Marshal(truncatedFCMMessage)
|
||||
require.Equal(t, fcmMessageLimit, len(serializedTruncatedFCMMessage))
|
||||
require.Equal(t, "1", truncatedFCMMessage.Data["truncated"])
|
||||
require.NotEqual(t, origMessageLength, truncatedMessageLength)
|
||||
}
|
||||
r, _ = http.NewRequest("GET", "https://ntfy.sh/mytopic", nil)
|
||||
r.Header.Set("X-Up", "yes")
|
||||
r.Header.Set("X-Firebase", "0")
|
||||
up = readBoolParam(r, false, "x-up", "up")
|
||||
firebase = readBoolParam(r, true, "x-firebase", "firebase")
|
||||
require.Equal(t, true, up)
|
||||
require.Equal(t, false, firebase)
|
||||
|
||||
func TestMaybeTruncateFCMMessage_NotTooLong(t *testing.T) {
|
||||
origMessage := "not really a long string"
|
||||
origFCMMessage := &messaging.Message{
|
||||
Topic: "mytopic",
|
||||
Data: map[string]string{
|
||||
"id": "abcdefg",
|
||||
"time": "1641324761",
|
||||
"event": "message",
|
||||
"topic": "mytopic",
|
||||
"priority": "0",
|
||||
"tags": "",
|
||||
"title": "",
|
||||
"message": origMessage,
|
||||
},
|
||||
}
|
||||
origMessageLength := len(origFCMMessage.Data["message"])
|
||||
serializedOrigFCMMessage, _ := json.Marshal(origFCMMessage)
|
||||
require.LessOrEqual(t, len(serializedOrigFCMMessage), fcmMessageLimit) // Pre-condition
|
||||
|
||||
notTruncatedFCMMessage := maybeTruncateFCMMessage(origFCMMessage)
|
||||
notTruncatedMessageLength := len(notTruncatedFCMMessage.Data["message"])
|
||||
serializedNotTruncatedFCMMessage, _ := json.Marshal(notTruncatedFCMMessage)
|
||||
require.Equal(t, origMessageLength, notTruncatedMessageLength)
|
||||
require.Equal(t, len(serializedOrigFCMMessage), len(serializedNotTruncatedFCMMessage))
|
||||
require.Equal(t, "", notTruncatedFCMMessage.Data["truncated"])
|
||||
r, _ = http.NewRequest("GET", "https://ntfy.sh/mytopic", nil)
|
||||
up = readBoolParam(r, false, "x-up", "up")
|
||||
firebase = readBoolParam(r, true, "x-up", "up")
|
||||
require.Equal(t, false, up)
|
||||
require.Equal(t, true, firebase)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue