Files
im_server/server/push/jpush/push_jpush_test.go
T

210 lines
6.7 KiB
Go

package jpush
import (
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/tinode/chat/server/push"
"github.com/tinode/chat/server/store"
"github.com/tinode/chat/server/store/types"
)
type testThirdPartyConfigStore map[string]string
func (s testThirdPartyConfigStore) Get(_ string, keys ...string) (map[string]string, error) {
result := make(map[string]string, len(keys))
for _, key := range keys {
if value, ok := s[key]; ok {
result[key] = value
}
}
return result, nil
}
type testDevicePushTokenStore struct {
devices map[types.Uid][]types.DeviceDef
}
func (s *testDevicePushTokenStore) Upsert(types.TenantID, *types.DevicePushToken) (*types.DevicePushToken, error) {
return nil, nil
}
func (s *testDevicePushTokenStore) GetAllByProvider(types.TenantID, string, ...types.Uid) (map[types.Uid][]types.DeviceDef, int, error) {
count := 0
for _, devices := range s.devices {
count += len(devices)
}
return s.devices, count, nil
}
func TestNormalizeConfigLoadsThirdPartyConfig(t *testing.T) {
oldConfig := store.ThirdPartyConfig
store.ThirdPartyConfig = testThirdPartyConfigStore{
"jpush_enabled": "1",
"jpush_app_key": "app-key",
"jpush_master_secret": "master",
"jpush_api_domain": "https://push.example.com/",
"jpush_push_path": "v3/push",
"jpush_default_ttl_seconds": "123",
"jpush_call_ttl_seconds": "45",
"jpush_use_alias": "false",
"jpush_classification": "1",
}
defer func() { store.ThirdPartyConfig = oldConfig }()
var config configType
loadThirdPartyConfig(&config)
config, err := normalizeConfig(config)
if err != nil {
t.Fatalf("normalize config: %v", err)
}
if !config.Enabled || config.AppKey != "app-key" || config.MasterSecret != "master" {
t.Fatalf("unexpected credentials: %#v", config)
}
if config.APIDomain != "https://push.example.com" || config.PushPath != "/v3/push" {
t.Fatalf("unexpected endpoint: %#v", config)
}
if config.DefaultTTLSeconds != 123 || config.CallTTLSeconds != 45 || config.UseAlias == nil || *config.UseAlias {
t.Fatalf("unexpected ttl/use_alias: %#v", config)
}
}
func TestPreparePayloadsUsesAliasAndNormalizesP2PTopic(t *testing.T) {
caller := types.Uid(42)
callee := types.Uid(43)
topic := caller.P2PName(callee)
config := &configType{Title: "MeetingIM", DefaultTTLSeconds: 86400, CallTTLSeconds: 60}
payloads, uids := preparePayloads(&push.Receipt{
TenantID: 7,
To: map[types.Uid]push.Recipient{
callee: {Unread: 3},
},
Payload: push.Payload{
What: push.ActMsg,
Topic: topic,
From: caller.UserId(),
SeqId: 12,
ContentType: "text/plain",
Content: map[string]any{"text": "hello"},
Timestamp: time.Date(2026, 9, 4, 10, 0, 0, 0, time.UTC),
},
}, config)
if len(payloads) != 1 || len(uids) != 1 || uids[0] != callee {
t.Fatalf("unexpected payload count: %d %#v", len(payloads), uids)
}
payload := payloads[0]
if got := payload.Audience["alias"][0]; got != callee.UserId() {
t.Fatalf("unexpected alias: %s", got)
}
userTopic, _ := types.P2PNameForUser(callee, topic)
if got := payload.Notification.Android.Extras["topic"]; got != userTopic {
t.Fatalf("topic was not normalized for callee: got %s want %s", got, userTopic)
}
if payload.Notification.IOS.Badge != "3" {
t.Fatalf("unexpected badge: %s", payload.Notification.IOS.Badge)
}
}
func TestPreparePayloadsCanUseRegistrationID(t *testing.T) {
oldTokens := store.DevicePushTokens
uid := types.Uid(42)
store.DevicePushTokens = &testDevicePushTokenStore{devices: map[types.Uid][]types.DeviceDef{
uid: {
{DeviceId: "online-token", Platform: "ios"},
{DeviceId: "offline-token", Platform: "android"},
},
}}
defer func() { store.DevicePushTokens = oldTokens }()
useAlias := false
config := &configType{Title: "MeetingIM", DefaultTTLSeconds: 86400, CallTTLSeconds: 60, UseAlias: &useAlias}
payloads, _ := preparePayloads(&push.Receipt{
TenantID: 7,
To: map[types.Uid]push.Recipient{
uid: {Devices: []string{"online-token"}},
},
Payload: push.Payload{What: push.ActMsg, Topic: "grpTopic", SeqId: 9, Timestamp: time.Now()},
}, config)
if len(payloads) != 1 {
t.Fatalf("unexpected payload count: %d", len(payloads))
}
got := payloads[0].Audience["registration_id"]
if len(got) != 1 || got[0] != "offline-token" {
t.Fatalf("unexpected registration ids: %#v", got)
}
}
func TestBuildPayloadUsesShortTTLForCalls(t *testing.T) {
config := &configType{Title: "MeetingIM", DefaultTTLSeconds: 86400, CallTTLSeconds: 60}
payload := buildPayload(map[string][]string{"alias": {"usr1"}}, "p2pTopic", map[string]string{
"type": "call_invite",
"topic": "p2pTopic",
"seq": "30",
"call_type": "audio",
}, 0, config)
if payload.Options.TimeToLive != 60 {
t.Fatalf("unexpected ttl: %d", payload.Options.TimeToLive)
}
if payload.Notification.Android.Title != "语音通话" {
t.Fatalf("unexpected title: %s", payload.Notification.Android.Title)
}
}
func TestPushDataExtractsCallFields(t *testing.T) {
data, err := pushData(push.Payload{
What: push.ActMsg,
Topic: "p2pTopic",
From: "usrCaller",
SeqId: 30,
Webrtc: "started",
Content: map[string]any{
"callId": "call_1",
"callType": "audio",
},
Timestamp: time.Date(2026, 9, 4, 10, 0, 0, 0, time.UTC),
})
if err != nil {
t.Fatalf("push data: %v", err)
}
if data["type"] != "call_invite" || data["call_id"] != "call_1" || data["call_type"] != "audio" || data["caller_id"] != "usrCaller" {
t.Fatalf("unexpected call data: %#v", data)
}
}
func TestSendPayloadUsesBasicAuth(t *testing.T) {
var gotAuth string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotAuth = r.Header.Get("Authorization")
if r.URL.Path != "/v3/push" {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
var payload jpushPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
t.Fatalf("decode payload: %v", err)
}
if payload.Audience["alias"][0] != "usr1" {
t.Fatalf("unexpected audience: %#v", payload.Audience)
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"sendno":"0","msg_id":"1"}`))
}))
defer srv.Close()
config := &configType{AppKey: "key", MasterSecret: "secret", APIDomain: srv.URL, PushPath: "/v3/push"}
err := sendPayload(&jpushPayload{
Platform: "all",
Audience: map[string][]string{"alias": {"usr1"}},
Message: &jpushMessage{MsgContent: "hello"},
Options: jpushOptions{TimeToLive: 60},
}, config, srv.Client())
if err != nil {
t.Fatalf("send payload: %v", err)
}
wantAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte("key:secret"))
if gotAuth != wantAuth {
t.Fatalf("unexpected auth: got %s want %s", gotAuth, wantAuth)
}
}