548 lines
16 KiB
Go
548 lines
16 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/stretchr/testify/suite"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/status-im/status-go/appdatabase"
|
|
"github.com/status-im/status-go/protocol/common"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
"github.com/status-im/status-go/protocol/sqlite"
|
|
"github.com/status-im/status-go/protocol/tt"
|
|
"github.com/status-im/status-go/t/helpers"
|
|
)
|
|
|
|
func TestHandlersSuite(t *testing.T) {
|
|
suite.Run(t, new(HandlersSuite))
|
|
}
|
|
|
|
type HandlersSuite struct {
|
|
suite.Suite
|
|
db *sql.DB
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func (s *HandlersSuite) SetupTest() {
|
|
db, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
|
|
s.Require().NoError(err)
|
|
|
|
err = sqlite.Migrate(db)
|
|
s.Require().NoError(err)
|
|
|
|
s.logger = tt.MustCreateTestLogger()
|
|
s.db = db
|
|
}
|
|
|
|
func (s *HandlersSuite) saveUserMessage(msg *common.Message) {
|
|
whisperTimestamp := 0
|
|
source := ""
|
|
text := ""
|
|
contentType := 0
|
|
timestamp := 0
|
|
chatID := "1"
|
|
localChatID := "1"
|
|
responseTo := ""
|
|
clockValue := 0
|
|
|
|
stmt, err := s.db.Prepare(`
|
|
INSERT INTO user_messages (
|
|
id,
|
|
whisper_timestamp,
|
|
source,
|
|
text,
|
|
content_type,
|
|
timestamp,
|
|
chat_id,
|
|
local_chat_id,
|
|
response_to,
|
|
clock_value,
|
|
unfurled_links,
|
|
unfurled_status_links
|
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
|
`)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
links := []byte{}
|
|
statusLinks := []byte{}
|
|
|
|
if msg.UnfurledLinks != nil {
|
|
links, err = json.Marshal(msg.UnfurledLinks)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
if msg.UnfurledStatusLinks != nil {
|
|
statusLinks, err = proto.Marshal(msg.UnfurledStatusLinks)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
_, err = stmt.Exec(
|
|
msg.ID,
|
|
whisperTimestamp,
|
|
source,
|
|
text,
|
|
contentType,
|
|
timestamp,
|
|
chatID,
|
|
localChatID,
|
|
responseTo,
|
|
clockValue,
|
|
links,
|
|
statusLinks,
|
|
)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *HandlersSuite) httpGetReqRecorder(handler http.HandlerFunc, reqURL string) *httptest.ResponseRecorder {
|
|
req, err := http.NewRequest("GET", reqURL, nil)
|
|
s.Require().NoError(err)
|
|
|
|
rr := httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
return rr
|
|
}
|
|
|
|
func (s *HandlersSuite) verifyHTTPResponseThumbnail(rr *httptest.ResponseRecorder, expectedPayload []byte) {
|
|
s.Require().Equal(expectedPayload, rr.Body.Bytes())
|
|
s.Require().Equal("image/jpeg", rr.HeaderMap.Get("Content-Type"))
|
|
s.Require().Equal("no-store", rr.HeaderMap.Get("Cache-Control"))
|
|
}
|
|
|
|
func (s *HandlersSuite) TestHandleLinkPreviewThumbnail() {
|
|
previewURL := "https://github.com"
|
|
defaultPayload := []byte{0xff, 0xd8, 0xff, 0xdb, 0x0, 0x84, 0x0, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x50}
|
|
|
|
msg := common.Message{
|
|
ID: "1",
|
|
ChatMessage: &protobuf.ChatMessage{
|
|
UnfurledLinks: []*protobuf.UnfurledLink{
|
|
{
|
|
Type: protobuf.UnfurledLink_LINK,
|
|
Url: previewURL,
|
|
ThumbnailWidth: 100,
|
|
ThumbnailHeight: 200,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
s.saveUserMessage(&msg)
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
ExpectedHTTPStatusCode int
|
|
ThumbnailPayload []byte
|
|
Parameters url.Values
|
|
CheckFunc func(s *HandlersSuite, rr *httptest.ResponseRecorder)
|
|
}{
|
|
{
|
|
Name: "Test happy path",
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
ThumbnailPayload: defaultPayload,
|
|
Parameters: url.Values{
|
|
"message-id": {msg.ID},
|
|
"url": {previewURL},
|
|
},
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, msg.UnfurledLinks[0].ThumbnailPayload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test request with missing 'url' parameter",
|
|
ThumbnailPayload: defaultPayload,
|
|
ExpectedHTTPStatusCode: http.StatusBadRequest,
|
|
Parameters: url.Values{
|
|
"message-id": {msg.ID},
|
|
},
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.Require().Equal("missing query parameter 'url'\n", rr.Body.String())
|
|
},
|
|
},
|
|
{
|
|
Name: "Test request with missing 'message-id' parameter",
|
|
ThumbnailPayload: defaultPayload,
|
|
ExpectedHTTPStatusCode: http.StatusBadRequest,
|
|
Parameters: url.Values{
|
|
"url": {previewURL},
|
|
},
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.Require().Equal("missing query parameter 'message-id'\n", rr.Body.String())
|
|
},
|
|
},
|
|
{
|
|
Name: "Test mime type not supported",
|
|
ThumbnailPayload: []byte("unsupported image"),
|
|
ExpectedHTTPStatusCode: http.StatusNotImplemented,
|
|
Parameters: url.Values{
|
|
"message-id": {msg.ID},
|
|
"url": {previewURL},
|
|
},
|
|
},
|
|
}
|
|
|
|
handler := handleLinkPreviewThumbnail(s.db, s.logger)
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.Name, func() {
|
|
msg.UnfurledLinks[0].ThumbnailPayload = tc.ThumbnailPayload
|
|
s.saveUserMessage(&msg)
|
|
|
|
requestURL := "/dummy?" + tc.Parameters.Encode()
|
|
rr := s.httpGetReqRecorder(handler, requestURL)
|
|
s.Require().Equal(tc.ExpectedHTTPStatusCode, rr.Code)
|
|
if tc.CheckFunc != nil {
|
|
tc.CheckFunc(s, rr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *HandlersSuite) TestHandleStatusLinkPreviewThumbnail() {
|
|
contact := &protobuf.UnfurledStatusContactLink{
|
|
PublicKey: []byte("PublicKey_1"),
|
|
Icon: &protobuf.UnfurledLinkThumbnail{
|
|
Width: 10,
|
|
Height: 20,
|
|
Payload: []byte{0xff, 0xd8, 0xff, 0xdb, 0x0, 0x84, 0x0, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x50},
|
|
},
|
|
}
|
|
|
|
contactWithUnsupportedImage := &protobuf.UnfurledStatusContactLink{
|
|
PublicKey: []byte("PublicKey_2"),
|
|
Icon: &protobuf.UnfurledLinkThumbnail{
|
|
Width: 10,
|
|
Height: 20,
|
|
Payload: []byte("unsupported image"),
|
|
},
|
|
}
|
|
|
|
community := &protobuf.UnfurledStatusCommunityLink{
|
|
CommunityId: []byte("CommunityId_1"),
|
|
Icon: &protobuf.UnfurledLinkThumbnail{
|
|
Width: 30,
|
|
Height: 40,
|
|
Payload: []byte{0xff, 0xd8, 0xff, 0xdb, 0x0, 0x84, 0x0, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x51},
|
|
},
|
|
Banner: &protobuf.UnfurledLinkThumbnail{
|
|
Width: 50,
|
|
Height: 60,
|
|
Payload: []byte{0xff, 0xd8, 0xff, 0xdb, 0x0, 0x84, 0x0, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x52},
|
|
},
|
|
}
|
|
|
|
channel := &protobuf.UnfurledStatusChannelLink{
|
|
ChannelUuid: "ChannelUuid_1",
|
|
Community: &protobuf.UnfurledStatusCommunityLink{
|
|
CommunityId: []byte("CommunityId_2"),
|
|
Icon: &protobuf.UnfurledLinkThumbnail{
|
|
Width: 70,
|
|
Height: 80,
|
|
Payload: []byte{0xff, 0xd8, 0xff, 0xdb, 0x0, 0x84, 0x0, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x53},
|
|
},
|
|
Banner: &protobuf.UnfurledLinkThumbnail{
|
|
Width: 90,
|
|
Height: 100,
|
|
Payload: []byte{0xff, 0xd8, 0xff, 0xdb, 0x0, 0x84, 0x0, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x54},
|
|
},
|
|
},
|
|
}
|
|
|
|
unfurledContact := &protobuf.UnfurledStatusLink{
|
|
Url: "https://status.app/u/",
|
|
Payload: &protobuf.UnfurledStatusLink_Contact{
|
|
Contact: contact,
|
|
},
|
|
}
|
|
|
|
unfurledContactWithUnsupportedImage := &protobuf.UnfurledStatusLink{
|
|
Url: "https://status.app/u/",
|
|
Payload: &protobuf.UnfurledStatusLink_Contact{
|
|
Contact: contactWithUnsupportedImage,
|
|
},
|
|
}
|
|
|
|
unfurledCommunity := &protobuf.UnfurledStatusLink{
|
|
Url: "https://status.app/c/",
|
|
Payload: &protobuf.UnfurledStatusLink_Community{
|
|
Community: community,
|
|
},
|
|
}
|
|
|
|
unfurledChannel := &protobuf.UnfurledStatusLink{
|
|
Url: "https://status.app/cc/",
|
|
Payload: &protobuf.UnfurledStatusLink_Channel{
|
|
Channel: channel,
|
|
},
|
|
}
|
|
|
|
const (
|
|
messageIDContactOnly = "1"
|
|
messageIDCommunityOnly = "2"
|
|
messageIDChannelOnly = "3"
|
|
messageIDAllLinks = "4"
|
|
messageIDUnsupportedImage = "5"
|
|
)
|
|
|
|
s.saveUserMessage(&common.Message{
|
|
ID: messageIDContactOnly,
|
|
ChatMessage: &protobuf.ChatMessage{
|
|
UnfurledStatusLinks: &protobuf.UnfurledStatusLinks{
|
|
UnfurledStatusLinks: []*protobuf.UnfurledStatusLink{
|
|
unfurledContact,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
s.saveUserMessage(&common.Message{
|
|
ID: messageIDCommunityOnly,
|
|
ChatMessage: &protobuf.ChatMessage{
|
|
UnfurledStatusLinks: &protobuf.UnfurledStatusLinks{
|
|
UnfurledStatusLinks: []*protobuf.UnfurledStatusLink{
|
|
unfurledCommunity,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
s.saveUserMessage(&common.Message{
|
|
ID: messageIDChannelOnly,
|
|
ChatMessage: &protobuf.ChatMessage{
|
|
UnfurledStatusLinks: &protobuf.UnfurledStatusLinks{
|
|
UnfurledStatusLinks: []*protobuf.UnfurledStatusLink{
|
|
unfurledChannel,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
s.saveUserMessage(&common.Message{
|
|
ID: messageIDAllLinks,
|
|
ChatMessage: &protobuf.ChatMessage{
|
|
UnfurledStatusLinks: &protobuf.UnfurledStatusLinks{
|
|
UnfurledStatusLinks: []*protobuf.UnfurledStatusLink{
|
|
unfurledContact,
|
|
unfurledCommunity,
|
|
unfurledChannel,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
s.saveUserMessage(&common.Message{
|
|
ID: messageIDUnsupportedImage,
|
|
ChatMessage: &protobuf.ChatMessage{
|
|
UnfurledStatusLinks: &protobuf.UnfurledStatusLinks{
|
|
UnfurledStatusLinks: []*protobuf.UnfurledStatusLink{
|
|
unfurledContactWithUnsupportedImage,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
ExpectedHTTPStatusCode int
|
|
Parameters url.Values
|
|
CheckFunc func(s *HandlersSuite, rr *httptest.ResponseRecorder)
|
|
}{
|
|
{
|
|
Name: "Test valid contact icon link",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDContactOnly},
|
|
"url": {unfurledContact.Url},
|
|
"image-id": {string(common.MediaServerContactIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, unfurledContact.GetContact().Icon.Payload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test invalid request for community icon in a contact link",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDContactOnly},
|
|
"url": {unfurledContact.Url},
|
|
"image-id": {string(common.MediaServerCommunityIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusBadRequest,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.Require().Equal("invalid query parameter 'image-id' value: this is not a community link\n", rr.Body.String())
|
|
},
|
|
},
|
|
{
|
|
Name: "Test invalid request for cahnnel community banner in a contact link",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDContactOnly},
|
|
"url": {unfurledContact.Url},
|
|
"image-id": {string(common.MediaServerChannelCommunityBanner)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusBadRequest,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.Require().Equal("invalid query parameter 'image-id' value: this is not a community channel link\n", rr.Body.String())
|
|
},
|
|
},
|
|
{
|
|
Name: "Test invalid request for channel community banner in a contact link",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDContactOnly},
|
|
"url": {unfurledContact.Url},
|
|
"image-id": {"contact-banner"},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusBadRequest,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.Require().Equal("invalid query parameter 'image-id' value: value not supported\n", rr.Body.String())
|
|
},
|
|
},
|
|
{
|
|
Name: "Test valid community icon link",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDCommunityOnly},
|
|
"url": {unfurledCommunity.Url},
|
|
"image-id": {string(common.MediaServerCommunityIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, unfurledCommunity.GetCommunity().Icon.Payload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test valid community banner link",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDCommunityOnly},
|
|
"url": {unfurledCommunity.Url},
|
|
"image-id": {string(common.MediaServerCommunityBanner)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, unfurledCommunity.GetCommunity().Banner.Payload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test valid channel community icon link",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDChannelOnly},
|
|
"url": {unfurledChannel.Url},
|
|
"image-id": {string(common.MediaServerChannelCommunityIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, unfurledChannel.GetChannel().GetCommunity().Icon.Payload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test valid channel community banner link",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDChannelOnly},
|
|
"url": {unfurledChannel.Url},
|
|
"image-id": {string(common.MediaServerChannelCommunityBanner)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, unfurledChannel.GetChannel().GetCommunity().Banner.Payload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test valid contact icon link in a diverse message",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDAllLinks},
|
|
"url": {unfurledContact.Url},
|
|
"image-id": {string(common.MediaServerContactIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, unfurledContact.GetContact().Icon.Payload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test valid community icon link in a diverse message",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDAllLinks},
|
|
"url": {unfurledCommunity.Url},
|
|
"image-id": {string(common.MediaServerCommunityIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, unfurledCommunity.GetCommunity().Icon.Payload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test valid channel community icon link in a diverse message",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDAllLinks},
|
|
"url": {unfurledChannel.Url},
|
|
"image-id": {string(common.MediaServerChannelCommunityIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusOK,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.verifyHTTPResponseThumbnail(rr, unfurledChannel.GetChannel().GetCommunity().Icon.Payload)
|
|
},
|
|
},
|
|
{
|
|
Name: "Test mime type not supported",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDUnsupportedImage},
|
|
"url": {unfurledContactWithUnsupportedImage.Url},
|
|
"image-id": {string(common.MediaServerContactIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusNotImplemented,
|
|
},
|
|
{
|
|
Name: "Test request with missing 'message-id' parameter",
|
|
Parameters: url.Values{
|
|
"url": {unfurledCommunity.Url},
|
|
"image-id": {string(common.MediaServerCommunityIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusBadRequest,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.Require().Equal("missing query parameter 'message-id'\n", rr.Body.String())
|
|
},
|
|
},
|
|
{
|
|
Name: "Test request with missing 'url' parameter",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDCommunityOnly},
|
|
"image-id": {string(common.MediaServerCommunityIcon)},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusBadRequest,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.Require().Equal("missing query parameter 'url'\n", rr.Body.String())
|
|
},
|
|
},
|
|
{
|
|
Name: "Test request with missing 'image-id' parameter",
|
|
Parameters: url.Values{
|
|
"message-id": {messageIDCommunityOnly},
|
|
"url": {unfurledCommunity.Url},
|
|
},
|
|
ExpectedHTTPStatusCode: http.StatusBadRequest,
|
|
CheckFunc: func(s *HandlersSuite, rr *httptest.ResponseRecorder) {
|
|
s.Require().Equal("missing query parameter 'image-id'\n", rr.Body.String())
|
|
},
|
|
},
|
|
}
|
|
|
|
handler := handleStatusLinkPreviewThumbnail(s.db, s.logger)
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.Name, func() {
|
|
requestURL := "/dummy?" + tc.Parameters.Encode()
|
|
|
|
rr := s.httpGetReqRecorder(handler, requestURL)
|
|
s.Require().Equal(tc.ExpectedHTTPStatusCode, rr.Code)
|
|
|
|
if tc.CheckFunc != nil {
|
|
tc.CheckFunc(s, rr)
|
|
}
|
|
})
|
|
}
|
|
}
|