Files
Igor Sirotin f3e4363808 refactor: move protocol to internal/protocol
Part of the Go project layout migration, item 27.

Pure move plus import-path rewrite across 502 files. No API or behaviour
change. `internal/` keeps the messaging application logic unimportable
from outside the module, which is what the issue asks for -- status-go is
consumed through the C-bindings in mobile/, not as a Go library.

Things that had to follow the move, beyond the Go imports:

- tools/generate-handlers/template.txt. messenger_handlers.go is
  generated, and the template hard-codes the imports it emits, so the
  generated file kept importing protocol/common and failed typecheck.
- .gitignore. The ignore rule for that generated file was pinned to the
  old path; without moving it, a 1486-line generated file starts being
  tracked.
- Makefile: the logosstorage and torrent test targets (both the archive
  packages and ./protocol itself), the archive README, migration-protocol.
- scripts/run_unit_tests.sh, which names the protocol package explicitly
  to shard its tests.
- scripts/cleanup_generated_files.sh and .golangci.yml.

scripts/migration_check.sh also needed a fix that is not specific to this
move: it validated every file the branch touched under a migration dir
against the timestamp naming rule, and a directory rename makes every
migration in it look newly added. It now excludes renames, so moving a
migration is not mistaken for adding one.

refs #7067
2026-08-21 10:11:05 +02:00

962 lines
32 KiB
Go

package pushnotificationserver
import (
"crypto/ecdsa"
"crypto/rand"
"testing"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/internal/crypto"
"github.com/status-im/status-go/internal/db/appdatabase"
"github.com/status-im/status-go/internal/protocol/common"
"github.com/status-im/status-go/internal/protocol/protobuf"
"github.com/status-im/status-go/internal/protocol/sqlite"
"github.com/status-im/status-go/internal/testutils"
)
func TestServerSuite(t *testing.T) {
s := new(ServerSuite)
s.accessToken = "b6ae4fde-bb65-11ea-b3de-0242ac130004"
s.installationID = "c6ae4fde-bb65-11ea-b3de-0242ac130004"
suite.Run(t, s)
}
type ServerSuite struct {
suite.Suite
persistence Persistence
accessToken string
installationID string
identity *ecdsa.PrivateKey
key *ecdsa.PrivateKey
sharedKey []byte
grant []byte
server *Server
}
func (s *ServerSuite) SetupTest() {
db, err := testutils.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
s.Require().NoError(err)
err = sqlite.Migrate(db)
s.Require().NoError(err)
s.persistence = NewSQLitePersistence(db)
identity, err := crypto.GenerateKey()
s.Require().NoError(err)
s.identity = identity
key, err := crypto.GenerateKey()
s.Require().NoError(err)
s.key = key
config := &Config{
Identity: identity,
Logger: testutils.MustCreateTestLogger(),
}
s.server = New(config)
s.server.persistence = s.persistence // Set persistence without starting the server
sharedKey, err := s.server.generateSharedKey(&s.key.PublicKey)
s.Require().NoError(err)
s.sharedKey = sharedKey
signatureMaterial := s.server.buildGrantSignatureMaterial(&s.key.PublicKey, &identity.PublicKey, s.accessToken)
grant, err := crypto.Sign(signatureMaterial, s.key)
s.Require().NoError(err)
s.grant = grant
}
func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
// Empty payload
_, err := s.server.validateRegistration(&s.key.PublicKey, nil)
s.Require().Equal(ErrEmptyPushNotificationRegistrationPayload, err)
// Empty key
_, err = s.server.validateRegistration(nil, []byte("payload"))
s.Require().Equal(ErrEmptyPushNotificationRegistrationPublicKey, err)
// Invalid cyphertext length
_, err = s.server.validateRegistration(&s.key.PublicKey, []byte("too short"))
s.Require().Equal(common.ErrInvalidCiphertextLength, err)
// Invalid cyphertext length
_, err = s.server.validateRegistration(&s.key.PublicKey, []byte("too short"))
s.Require().Equal(common.ErrInvalidCiphertextLength, err)
// Invalid ciphertext
_, err = s.server.validateRegistration(&s.key.PublicKey, []byte("not too short but invalid"))
s.Require().Error(common.ErrInvalidCiphertextLength, err)
// Different key ciphertext
cyphertext, err := common.Encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Error(err)
// Right cyphertext but non unmarshable payload
cyphertext, err = common.Encrypt([]byte("plaintext"), s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrCouldNotUnmarshalPushNotificationRegistration, err)
// Missing installationID
payload, err := proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationInstallationID, err)
// Malformed installationID
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
Grant: s.grant,
InstallationId: "abc",
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationInstallationID, err)
// Version set to 0
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
Grant: s.grant,
InstallationId: s.installationID,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrInvalidPushNotificationRegistrationVersion, err)
// Version lower than previous one
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
// Setup persistence
s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
InstallationId: s.installationID,
Version: 2}))
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrInvalidPushNotificationRegistrationVersion, err)
// Cleanup persistence
s.Require().NoError(s.persistence.DeletePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), s.installationID))
// Unregistering message
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
InstallationId: s.installationID,
Unregister: true,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Nil(err)
// Missing access token
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
InstallationId: s.installationID,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationAccessToken, err)
// Invalid access token
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: "bc",
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
Grant: s.grant,
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationAccessToken, err)
// Missing device token
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
Grant: s.grant,
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationDeviceToken, err)
// Missing grant
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
DeviceToken: "device-token",
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationGrant, err)
// Invalid grant
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
DeviceToken: "device-token",
Grant: crypto.Keccak256([]byte("invalid")),
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationGrant, err)
// Missing token type
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
DeviceToken: "device-token",
Grant: s.grant,
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrUnknownPushNotificationRegistrationTokenType, err)
// Successful
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
DeviceToken: "abc",
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
s.Require().NoError(err)
}
func (s *ServerSuite) TestPushNotificationHandleRegistration() {
// Empty payload
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, nil)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Empty key
response = s.server.buildPushNotificationRegistrationResponse(nil, []byte("payload"))
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Invalid cyphertext length
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, []byte("too short"))
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Invalid cyphertext length
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, []byte("too short"))
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Invalid ciphertext
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, []byte("not too short but invalid"))
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Different key ciphertext
cyphertext, err := common.Encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Right cyphertext but non unmarshable payload
cyphertext, err = common.Encrypt([]byte("plaintext"), s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Missing installationID
payload, err := proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
Grant: s.grant,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Malformed installationID
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
InstallationId: "abc",
Grant: s.grant,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Version set to 0
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
Grant: s.grant,
InstallationId: s.installationID,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_VERSION_MISMATCH)
// Version lower than previous one
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
Grant: s.grant,
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
// Setup persistence
s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
Grant: s.grant,
InstallationId: s.installationID,
Version: 2}))
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_VERSION_MISMATCH)
// Cleanup persistence
s.Require().NoError(s.persistence.DeletePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), s.installationID))
// Missing access token
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
InstallationId: s.installationID,
Grant: s.grant,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Invalid access token
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: "bc",
Grant: s.grant,
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Missing device token
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
Grant: s.grant,
InstallationId: s.installationID,
Version: 1,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().False(response.Success)
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Successful
registration := &protobuf.PushNotificationRegistration{
DeviceToken: "abc",
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
InstallationId: s.installationID,
Version: 1,
}
payload, err = proto.Marshal(registration)
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().True(response.Success)
// Pull from the db
retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&s.key.PublicKey), s.installationID)
s.Require().NoError(err)
s.Require().NotNil(retrievedRegistration)
s.Require().True(proto.Equal(retrievedRegistration, registration))
// Unregistering message
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
DeviceToken: "token",
InstallationId: s.installationID,
Unregister: true,
Version: 2,
})
s.Require().NoError(err)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().True(response.Success)
s.Require().Equal(common.Shake256(cyphertext), response.RequestId)
// Check is gone from the db
retrievedRegistration, err = s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&s.key.PublicKey), s.installationID)
s.Require().NoError(err)
s.Require().Nil(retrievedRegistration)
// Check version is maintained
version, err := s.persistence.GetPushNotificationRegistrationVersion(common.HashPublicKey(&s.key.PublicKey), s.installationID)
s.Require().NoError(err)
s.Require().Equal(uint64(2), version)
}
func (s *ServerSuite) TestbuildPushNotificationQueryResponseNoFiltering() {
hashedPublicKey := common.HashPublicKey(&s.key.PublicKey)
// Successful
registration := &protobuf.PushNotificationRegistration{
DeviceToken: "abc",
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
InstallationId: s.installationID,
Version: 1,
}
payload, err := proto.Marshal(registration)
s.Require().NoError(err)
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().True(response.Success)
query := &protobuf.PushNotificationQuery{
PublicKeys: [][]byte{[]byte("non-existing"), hashedPublicKey},
}
queryResponse := s.server.buildPushNotificationQueryResponse(query)
s.Require().NotNil(queryResponse)
s.Require().True(queryResponse.Success)
s.Require().Len(queryResponse.Info, 1)
s.Require().Equal(s.accessToken, queryResponse.Info[0].AccessToken)
s.Require().Equal(hashedPublicKey, queryResponse.Info[0].PublicKey)
s.Require().Equal(s.installationID, queryResponse.Info[0].InstallationId)
s.Require().Nil(queryResponse.Info[0].AllowedKeyList)
}
func (s *ServerSuite) TestbuildPushNotificationQueryResponseWithFiltering() {
hashedPublicKey := common.HashPublicKey(&s.key.PublicKey)
allowedKeyList := [][]byte{[]byte("a")}
// Successful
registration := &protobuf.PushNotificationRegistration{
DeviceToken: "abc",
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
InstallationId: s.installationID,
AllowFromContactsOnly: true,
AllowedKeyList: allowedKeyList,
Version: 1,
}
payload, err := proto.Marshal(registration)
s.Require().NoError(err)
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().True(response.Success)
query := &protobuf.PushNotificationQuery{
PublicKeys: [][]byte{[]byte("non-existing"), hashedPublicKey},
}
queryResponse := s.server.buildPushNotificationQueryResponse(query)
s.Require().NotNil(queryResponse)
s.Require().True(queryResponse.Success)
s.Require().Len(queryResponse.Info, 1)
s.Require().Equal(hashedPublicKey, queryResponse.Info[0].PublicKey)
s.Require().Equal(s.installationID, queryResponse.Info[0].InstallationId)
s.Require().Equal(allowedKeyList, queryResponse.Info[0].AllowedKeyList)
}
func (s *ServerSuite) TestPushNotificationMentions() {
existingChatID := []byte("existing-chat-id")
nonExistingChatID := []byte("non-existing-chat-id")
registration := &protobuf.PushNotificationRegistration{
DeviceToken: "abc",
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
InstallationId: s.installationID,
AllowedMentionsChatList: [][]byte{existingChatID},
Version: 1,
}
payload, err := proto.Marshal(registration)
s.Require().NoError(err)
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().True(response.Success)
pushNotificationRequest := &protobuf.PushNotificationRequest{
MessageId: []byte("message-id"),
Requests: []*protobuf.PushNotification{
{
AccessToken: s.accessToken,
PublicKey: common.HashPublicKey(&s.key.PublicKey),
ChatId: existingChatID,
InstallationId: s.installationID,
Type: protobuf.PushNotification_MENTION,
},
{
AccessToken: s.accessToken,
PublicKey: common.HashPublicKey(&s.key.PublicKey),
ChatId: nonExistingChatID,
InstallationId: s.installationID,
Type: protobuf.PushNotification_MENTION,
},
},
}
pushNotificationResponse, requestAndRegistrations := s.server.buildPushNotificationRequestResponse(pushNotificationRequest)
s.Require().NotNil(pushNotificationResponse)
s.Require().NotNil(requestAndRegistrations)
// only one should succeed
s.Require().Len(requestAndRegistrations, 1)
}
func (s *ServerSuite) TestPushNotificationDisabledMentions() {
existingChatID := []byte("existing-chat-id")
registration := &protobuf.PushNotificationRegistration{
DeviceToken: "abc",
AccessToken: s.accessToken,
Grant: s.grant,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
BlockMentions: true,
InstallationId: s.installationID,
AllowedMentionsChatList: [][]byte{existingChatID},
Version: 1,
}
payload, err := proto.Marshal(registration)
s.Require().NoError(err)
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().True(response.Success)
pushNotificationRequest := &protobuf.PushNotificationRequest{
MessageId: []byte("message-id"),
Requests: []*protobuf.PushNotification{
{
AccessToken: s.accessToken,
PublicKey: common.HashPublicKey(&s.key.PublicKey),
ChatId: existingChatID,
InstallationId: s.installationID,
Type: protobuf.PushNotification_MENTION,
},
},
}
pushNotificationResponse, requestAndRegistrations := s.server.buildPushNotificationRequestResponse(pushNotificationRequest)
s.Require().NotNil(pushNotificationResponse)
s.Require().Nil(requestAndRegistrations)
}
func (s *ServerSuite) TestBuildPushNotificationReport() {
accessToken := "a"
chatID := []byte("chat-id")
author := []byte("author")
blockedAuthor := []byte("blocked-author")
blockedChatID := []byte("blocked-chat-id")
mutedChatID := []byte("muted-chat-id")
mutedChatList := [][]byte{mutedChatID, author}
blockedChatList := [][]byte{blockedChatID, blockedAuthor}
nonJoinedChatID := []byte("non-joined-chat-id")
allowedMentionsChatList := [][]byte{chatID}
validMessagePN := &protobuf.PushNotification{
Type: protobuf.PushNotification_MESSAGE,
ChatId: chatID,
Author: author,
AccessToken: accessToken,
}
validMentionPN := &protobuf.PushNotification{
Type: protobuf.PushNotification_MENTION,
ChatId: chatID,
AccessToken: accessToken,
}
validContactRequestPN := &protobuf.PushNotification{
Type: protobuf.PushNotification_CONTACT_REQUEST,
ChatId: chatID,
Author: author,
AccessToken: accessToken,
}
validRegistration := &protobuf.PushNotificationRegistration{
AccessToken: accessToken,
BlockedChatList: blockedChatList,
AllowedMentionsChatList: allowedMentionsChatList,
MutedChatList: mutedChatList,
}
blockedMentionsRegistration := &protobuf.PushNotificationRegistration{
AccessToken: accessToken,
BlockMentions: true,
BlockedChatList: blockedChatList,
AllowedMentionsChatList: allowedMentionsChatList,
}
testCases := []struct {
name string
pn *protobuf.PushNotification
registration *protobuf.PushNotificationRegistration
expectedError error
expectedResponse *reportResult
}{
{
name: "valid message",
pn: validMessagePN,
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: true,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "valid mention",
pn: validMentionPN,
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: true,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "valid contact request",
pn: validContactRequestPN,
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: true,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "unknow push notification",
pn: &protobuf.PushNotification{
ChatId: chatID,
AccessToken: accessToken,
},
registration: validRegistration,
expectedError: errUnhandledPushNotificationType,
},
{
name: "empty registration",
pn: validMessagePN,
registration: nil,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: false,
Error: protobuf.PushNotificationReport_NOT_REGISTERED,
},
},
},
{
name: "invalid access token message",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MESSAGE,
Author: author,
ChatId: chatID,
AccessToken: "invalid",
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: false,
Error: protobuf.PushNotificationReport_WRONG_TOKEN,
},
},
},
{
name: "invalid access token mention",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MENTION,
Author: author,
ChatId: chatID,
AccessToken: "invalid",
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: false,
Error: protobuf.PushNotificationReport_WRONG_TOKEN,
},
},
},
{
name: "blocked chat list message",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MESSAGE,
ChatId: blockedChatID,
Author: author,
AccessToken: accessToken,
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "muted chat list message",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MESSAGE,
ChatId: mutedChatID,
Author: author,
AccessToken: accessToken,
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "unmuted chat and user muted author",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MESSAGE,
ChatId: chatID,
Author: author,
AccessToken: accessToken,
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: true,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "muted chat list mention",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MENTION,
ChatId: mutedChatID,
Author: author,
AccessToken: accessToken,
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "blocked group chat message",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MESSAGE,
Author: blockedAuthor,
ChatId: chatID,
AccessToken: accessToken,
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "blocked chat list mention",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MENTION,
Author: blockedAuthor,
ChatId: chatID,
AccessToken: accessToken,
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "blocked contact request author",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_CONTACT_REQUEST,
Author: blockedAuthor,
ChatId: chatID,
AccessToken: accessToken,
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "blocked mentions",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MENTION,
Author: author,
ChatId: chatID,
AccessToken: accessToken,
},
registration: blockedMentionsRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
{
name: "not in allowed mention chat list",
pn: &protobuf.PushNotification{
Type: protobuf.PushNotification_MENTION,
Author: author,
ChatId: nonJoinedChatID,
AccessToken: accessToken,
},
registration: validRegistration,
expectedResponse: &reportResult{
sendNotification: false,
report: &protobuf.PushNotificationReport{
Success: true,
},
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
response, err := s.server.buildPushNotificationReport(tc.pn, tc.registration)
s.Require().Equal(tc.expectedError, err)
s.Require().Equal(tc.expectedResponse, response)
})
}
}