github.com/status-im/status-go@v1.1.0/protocol/common/message_test.go (about)

     1  package common
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"io/ioutil"
     7  	"os"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/status-im/status-go/eth-node/crypto"
    13  	"github.com/status-im/status-go/eth-node/types"
    14  	"github.com/status-im/status-go/protocol/protobuf"
    15  )
    16  
    17  const expectedJPEG = "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k="
    18  const expectedAAC = "data:audio/aac;base64,//FQgBw//NoATGF2YzUyLjcwLjAAQniptokphEFCg5qs1v9fn48+qz1rfWNhwvz+CqB5dipmq3T2PlT1Ld6sPj+19fUt1C3NKV0KowiqohZVCrdf19WMatvV3YbIvAuy/q2RafA8UiZPmZY7DdmHZtP9ri25kedWSiMKQRt79ttlod55LkuX7/f7/f7/f7/YGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYHNqo8g5qs1v9fn48+qz1rfWNhwvz+CqAAAAAAAAAAAAAAAAAAAAAAABw//FQgCNf/CFXbUZfDKFRgsYlKDegtXJH9eLkT54uRM1ckDYDcXRzZGF6Kz5Yps5fTeLY6w7gclwly+0PJL3udY3PyekTFI65bdniF3OjvHeafzZfWTs0qRMSkdll1sbb4SNT5e8vX98ytot6jEZ0NhJi2pBVP/tKV2JMyo36n9uxR2tKR+FoLCsP4SVi49kmvaSCWm5bQD96OmVQA9Q40bqnOa7rT8j9N0TlK991XdcenGTLbyS6eUnN2U1ckf14uRPni5EzVyQAAAAAAAAAAx6Q1flBp+KH2LhgH2Xx+14QB2/jcizm6ngck4vB9DoH9/Vcb7E8Dy+D/1ii1pSPwsUUUXCSsXHsk17SBfKwn2uHr6QAAAAAAAHA//FQgBt//CF3VO1KFCFWcd/r04m+O0758/tXHUlvaqEK9lvhUZXEZMXKMV/LQ6B3/mOl/Mrfs6jpD2b7f+n4yt+tm2x5ZmnpD++dZo/V9VgblI3OW/s1b8qt0h1RBiIRIIYIYQIBeCM8yy7etkwt1JAajRSoZGwwNZ07TTFTyMR1mTUVVUTW97vaDaHU5DV1snBf0mN4fraa+rf/vpdZ8FxqatGjNxPh35UuVfpNqc48W4nZ6rOO/16cTfHad8+f2rjqS3tVAAAAAAAAAAAAAAAAAAAAAAAAAAAO//FQgBm//CEXVPU+GiFsPr7x6+N6v+m+q511I4SgtYVyoyWjcMWMxkaxxDGSx1qVcarjDESt8zLQehx/lkil/GrHBy/NfJcHek0XtfanZJLHNXO2rUnFklPAlQSBS4l0pIoXIfORcXx0UYj1nTsSe1/0wXDkkFCfxWHtqRayOmWm3oS6JGdnZdtjesjByefiS8dLW1tVVVC58ijoxN3gmGFYj07+YJ6eth9fePXxvV/031XOupHCUAAAAAAAAAAAAAAAAAAAAAAAAAAA4P/xUIAcf/whN1T9NsMOEK5rxxxxXnid+f0/Ia195vi6oGH1ZVr6kjqScdSF9lt3qXH+Lxf0fo/Oe53r99IUPzybv/YWGZ7Vgk31MGw+DMp05+3y9fPERUTHlt1c9sUyoqCaD5bdXVz2wkG0hnpDmFy8r0fr3VBn/C7Rmg+L0/45EWfdocGq3HQ1uRro0GJK+vsvo837NR82s01l/n97rsWn7RYNBM3WRcDY3cJKosqMJhgdHtj9yflthd65rxxxxXnid+f0/Ia195vi6oAAAAAAAAAAAAAAAAAAAAAAAAAAAABw"
    19  
    20  func TestPrepareContentImage(t *testing.T) {
    21  	file, err := os.Open("../../_assets/tests/test.jpg")
    22  	require.NoError(t, err)
    23  	defer file.Close()
    24  
    25  	payload, err := ioutil.ReadAll(file)
    26  	require.NoError(t, err)
    27  
    28  	message := NewMessage()
    29  	message.ContentType = protobuf.ChatMessage_IMAGE
    30  	image := protobuf.ImageMessage{
    31  		Payload: payload,
    32  		Format:  protobuf.ImageFormat_JPEG,
    33  	}
    34  	message.Payload = &protobuf.ChatMessage_Image{Image: &image}
    35  
    36  	require.NoError(t, message.PrepareContent(""))
    37  	require.Equal(t, expectedJPEG, message.Base64Image)
    38  }
    39  
    40  func TestPrepareContentAudio(t *testing.T) {
    41  	file, err := os.Open("../../_assets/tests/test.aac")
    42  	require.NoError(t, err)
    43  	defer file.Close()
    44  
    45  	payload, err := ioutil.ReadAll(file)
    46  	require.NoError(t, err)
    47  
    48  	message := NewMessage()
    49  	message.ContentType = protobuf.ChatMessage_AUDIO
    50  	audio := protobuf.AudioMessage{
    51  		Payload: payload,
    52  		Type:    protobuf.AudioMessage_AAC,
    53  	}
    54  	message.Payload = &protobuf.ChatMessage_Audio{Audio: &audio}
    55  
    56  	require.NoError(t, message.PrepareContent(""))
    57  	require.Equal(t, expectedAAC, message.Base64Audio)
    58  }
    59  
    60  func TestGetAudioMessageMIME(t *testing.T) {
    61  	aac := &protobuf.AudioMessage{Type: protobuf.AudioMessage_AAC}
    62  	mime, err := getAudioMessageMIME(aac)
    63  	require.NoError(t, err)
    64  	require.Equal(t, "aac", mime)
    65  
    66  	amr := &protobuf.AudioMessage{Type: protobuf.AudioMessage_AMR}
    67  	mime, err = getAudioMessageMIME(amr)
    68  	require.NoError(t, err)
    69  	require.Equal(t, "amr", mime)
    70  }
    71  
    72  func TestPrepareContentMentions(t *testing.T) {
    73  	message := NewMessage()
    74  	pk1, err := crypto.GenerateKey()
    75  	require.NoError(t, err)
    76  	pk1String := types.EncodeHex(crypto.FromECDSAPub(&pk1.PublicKey))
    77  
    78  	pk2, err := crypto.GenerateKey()
    79  	require.NoError(t, err)
    80  	pk2String := types.EncodeHex(crypto.FromECDSAPub(&pk2.PublicKey))
    81  
    82  	message.Text = "hey @" + pk1String + " @" + pk2String
    83  
    84  	require.NoError(t, message.PrepareContent(pk2String))
    85  	require.Len(t, message.Mentions, 2)
    86  	require.Equal(t, message.Mentions[0], pk1String)
    87  	require.Equal(t, message.Mentions[1], pk2String)
    88  	require.True(t, message.Mentioned)
    89  }
    90  
    91  func TestPrepareContentLinks(t *testing.T) {
    92  	message := NewMessage()
    93  
    94  	link1 := "https://github.com/status-im/status-mobile"
    95  	link2 := "https://www.youtube.com/watch?v=6RYO8KCY6YE"
    96  
    97  	message.Text = "Just look at that repo " + link1 + " . And watch this video - " + link2
    98  
    99  	require.NoError(t, message.PrepareContent(""))
   100  	require.Len(t, message.Links, 2)
   101  	require.Equal(t, message.Links[0], link1)
   102  	require.Equal(t, message.Links[1], link2)
   103  }
   104  
   105  func TestPrepareSimplifiedText(t *testing.T) {
   106  	canonicalName1 := "canonical-name-1"
   107  	canonicalName2 := "canonical-name-2"
   108  
   109  	message := NewMessage()
   110  	pk1, err := crypto.GenerateKey()
   111  	require.NoError(t, err)
   112  	pk1String := types.EncodeHex(crypto.FromECDSAPub(&pk1.PublicKey))
   113  
   114  	pk2, err := crypto.GenerateKey()
   115  	require.NoError(t, err)
   116  	pk2String := types.EncodeHex(crypto.FromECDSAPub(&pk2.PublicKey))
   117  
   118  	message.Text = "hey @" + pk1String + " @" + pk2String
   119  
   120  	require.NoError(t, message.PrepareContent(""))
   121  	require.Len(t, message.Mentions, 2)
   122  	require.Equal(t, message.Mentions[0], pk1String)
   123  	require.Equal(t, message.Mentions[1], pk2String)
   124  
   125  	canonicalNames := make(map[string]string)
   126  	canonicalNames[pk1String] = canonicalName1
   127  	canonicalNames[pk2String] = canonicalName2
   128  
   129  	simplifiedText, err := message.GetSimplifiedText("", canonicalNames)
   130  	require.NoError(t, err)
   131  	require.Equal(t, "hey "+canonicalName1+" "+canonicalName2, simplifiedText)
   132  }
   133  
   134  func TestConvertLinkPreviewsToProto(t *testing.T) {
   135  	msg := Message{
   136  		LinkPreviews: []LinkPreview{
   137  			{
   138  				Type:        protobuf.UnfurledLink_LINK,
   139  				Description: "GitHub is where people build software.",
   140  				Hostname:    "github.com",
   141  				Title:       "Build software better, together",
   142  				URL:         "https://github.com",
   143  				Thumbnail: LinkPreviewThumbnail{
   144  					Width:   100,
   145  					Height:  200,
   146  					URL:     "http://localhost:9999",
   147  					DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=",
   148  				},
   149  				Favicon: LinkPreviewThumbnail{
   150  					DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=",
   151  				},
   152  			},
   153  		},
   154  	}
   155  
   156  	unfurledLinks, err := msg.ConvertLinkPreviewsToProto()
   157  	require.NoError(t, err)
   158  	require.Len(t, unfurledLinks, 1)
   159  
   160  	l := unfurledLinks[0]
   161  	validPreview := msg.LinkPreviews[0]
   162  	require.Equal(t, validPreview.Type, l.Type)
   163  	require.Equal(t, validPreview.Description, l.Description)
   164  	require.Equal(t, validPreview.Title, l.Title)
   165  	require.Equal(t, uint32(validPreview.Thumbnail.Width), l.ThumbnailWidth)
   166  	require.Equal(t, uint32(validPreview.Thumbnail.Height), l.ThumbnailHeight)
   167  
   168  	expectedPayload, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUg=")
   169  	require.NoError(t, err)
   170  	require.Equal(t, expectedPayload, l.ThumbnailPayload)
   171  
   172  	expectedFaviconPayload, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUg=")
   173  	require.NoError(t, err)
   174  	require.Equal(t, expectedFaviconPayload, l.FaviconPayload)
   175  
   176  	// Test any invalid link preview causes an early return.
   177  	invalidPreview := validPreview
   178  	invalidPreview.Title = ""
   179  	msg.LinkPreviews = []LinkPreview{invalidPreview}
   180  	_, err = msg.ConvertLinkPreviewsToProto()
   181  	require.ErrorContains(t, err, "invalid link preview, url='https://github.com'")
   182  
   183  	// Test invalid data URI invalidates a preview.
   184  	invalidPreview = validPreview
   185  	invalidPreview.Thumbnail.DataURI = "data:hello/png,iVBOR"
   186  	msg.LinkPreviews = []LinkPreview{invalidPreview}
   187  	_, err = msg.ConvertLinkPreviewsToProto()
   188  	require.ErrorContains(t, err, "could not get data URI payload for link preview thumbnail, url='https://github.com': wrong uri format")
   189  
   190  	// Test thumbnail is optional.
   191  	somePreview := validPreview
   192  	somePreview.Thumbnail.DataURI = ""
   193  	somePreview.Thumbnail.Width = 0
   194  	somePreview.Thumbnail.Height = 0
   195  	msg.LinkPreviews = []LinkPreview{somePreview}
   196  	unfurledLinks, err = msg.ConvertLinkPreviewsToProto()
   197  	require.NoError(t, err)
   198  	require.Len(t, unfurledLinks, 1)
   199  	require.Nil(t, unfurledLinks[0].ThumbnailPayload)
   200  }
   201  
   202  func TestConvertFromProtoToLinkPreviews(t *testing.T) {
   203  
   204  	thumbnailPayload, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUg=")
   205  	require.NoError(t, err)
   206  	FaviconPayload, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUg=")
   207  	require.NoError(t, err)
   208  
   209  	l := &protobuf.UnfurledLink{
   210  		Description:      "GitHub is where people build software.",
   211  		Title:            "Build software better, together",
   212  		Type:             protobuf.UnfurledLink_LINK,
   213  		Url:              "https://github.com",
   214  		ThumbnailPayload: thumbnailPayload,
   215  		FaviconPayload:   FaviconPayload,
   216  		ThumbnailWidth:   100,
   217  		ThumbnailHeight:  200,
   218  	}
   219  	msg := Message{
   220  		ID: "42",
   221  		ChatMessage: &protobuf.ChatMessage{
   222  			UnfurledLinks: []*protobuf.UnfurledLink{l},
   223  		},
   224  	}
   225  
   226  	urlMaker := func(msgID string, linkURL string) string {
   227  		return "https://localhost:6666/" + msgID + "-" + linkURL
   228  	}
   229  
   230  	previews := msg.ConvertFromProtoToLinkPreviews(urlMaker, urlMaker)
   231  	require.Len(t, previews, 1)
   232  	p := previews[0]
   233  	require.Equal(t, l.Type, p.Type)
   234  	require.Equal(t, "github.com", p.Hostname)
   235  	require.Equal(t, l.Description, p.Description)
   236  	require.Equal(t, l.Url, p.URL)
   237  	require.Equal(t, int(l.ThumbnailHeight), p.Thumbnail.Height)
   238  	require.Equal(t, int(l.ThumbnailWidth), p.Thumbnail.Width)
   239  	// Important, don't build up a data URI because the thumbnail should be
   240  	// fetched from the media server.
   241  	require.Equal(t, "", p.Thumbnail.DataURI)
   242  	require.Equal(t, "https://localhost:6666/42-https://github.com", p.Thumbnail.URL)
   243  	require.Equal(t, "https://localhost:6666/42-https://github.com", p.Favicon.URL)
   244  
   245  	// Test when the URL is not parseable by url.Parse.
   246  	l.Url = "postgres://user:abc{DEf1=ghi@example.com:5432/db?sslmode=require"
   247  	msg.ChatMessage.UnfurledLinks = []*protobuf.UnfurledLink{l}
   248  	previews = msg.ConvertFromProtoToLinkPreviews(urlMaker, urlMaker)
   249  	require.Len(t, previews, 1)
   250  	p = previews[0]
   251  	require.Equal(t, l.Url, p.Hostname)
   252  
   253  	// Test when there's no thumbnail payload.
   254  	l = &protobuf.UnfurledLink{
   255  		Description: "GitHub is where people build software.",
   256  		Title:       "Build software better, together",
   257  		Url:         "https://github.com",
   258  	}
   259  	msg.ChatMessage.UnfurledLinks = []*protobuf.UnfurledLink{l}
   260  	previews = msg.ConvertFromProtoToLinkPreviews(urlMaker, urlMaker)
   261  	require.Len(t, previews, 1)
   262  	p = previews[0]
   263  	require.Equal(t, 0, p.Thumbnail.Height)
   264  	require.Equal(t, 0, p.Thumbnail.Width)
   265  	require.Equal(t, "", p.Thumbnail.URL)
   266  }
   267  
   268  func TestConvertStatusLinkPreviewsToProto(t *testing.T) {
   269  	pk1, err := crypto.GenerateKey()
   270  	require.NoError(t, err)
   271  
   272  	compressedContactPublicKey := crypto.CompressPubkey(&pk1.PublicKey)
   273  	contactID := types.EncodeHex(crypto.FromECDSAPub(&pk1.PublicKey))
   274  
   275  	pk2, err := crypto.GenerateKey()
   276  	require.NoError(t, err)
   277  
   278  	compressedCommunityPublicKey := crypto.CompressPubkey(&pk2.PublicKey)
   279  	communityID := types.EncodeHex(compressedCommunityPublicKey)
   280  
   281  	pk3, err := crypto.GenerateKey()
   282  	require.NoError(t, err)
   283  
   284  	compressedCommunityPublicKey2 := crypto.CompressPubkey(&pk3.PublicKey)
   285  	communityID2 := types.EncodeHex(compressedCommunityPublicKey2)
   286  
   287  	contact := &StatusContactLinkPreview{
   288  		PublicKey:   contactID,
   289  		DisplayName: "DisplayName_2",
   290  		Description: "Description_3",
   291  		Icon: LinkPreviewThumbnail{
   292  			Width:   10,
   293  			Height:  20,
   294  			DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=",
   295  		},
   296  	}
   297  
   298  	community := &StatusCommunityLinkPreview{
   299  		CommunityID:  communityID,
   300  		DisplayName:  "DisplayName_5",
   301  		Description:  "Description_6",
   302  		MembersCount: 7,
   303  		Color:        "Color_8",
   304  		Icon: LinkPreviewThumbnail{
   305  			Width:   30,
   306  			Height:  40,
   307  			DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=",
   308  		},
   309  		Banner: LinkPreviewThumbnail{
   310  			Width:   50,
   311  			Height:  60,
   312  			DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=",
   313  		},
   314  	}
   315  
   316  	channel := &StatusCommunityChannelLinkPreview{
   317  		ChannelUUID: "ChannelUUID_11",
   318  		Emoji:       "Emoji_12",
   319  		DisplayName: "DisplayName_13",
   320  		Description: "Description_14",
   321  		Color:       "Color_15",
   322  		Community: &StatusCommunityLinkPreview{
   323  			CommunityID:  communityID2,
   324  			DisplayName:  "DisplayName_17",
   325  			Description:  "Description_18",
   326  			MembersCount: 19,
   327  			Color:        "Color_20",
   328  			Icon: LinkPreviewThumbnail{
   329  				Width:   70,
   330  				Height:  80,
   331  				DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=",
   332  			},
   333  			Banner: LinkPreviewThumbnail{
   334  				Width:   90,
   335  				Height:  100,
   336  				DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=",
   337  			},
   338  		},
   339  	}
   340  
   341  	message := Message{
   342  		StatusLinkPreviews: []StatusLinkPreview{
   343  			{
   344  				URL:     "https://status.app/u/",
   345  				Contact: contact,
   346  			},
   347  			{
   348  				URL:       "https://status.app/c/",
   349  				Community: community,
   350  			},
   351  			{
   352  				URL:     "https://status.app/cc/",
   353  				Channel: channel,
   354  			},
   355  		},
   356  	}
   357  
   358  	expectedThumbnailPayload, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUg=")
   359  	require.NoError(t, err)
   360  
   361  	unfurledLinks, err := message.ConvertStatusLinkPreviewsToProto()
   362  	require.NoError(t, err)
   363  	require.Len(t, unfurledLinks.UnfurledStatusLinks, 3)
   364  
   365  	// Contact link
   366  
   367  	l1 := unfurledLinks.UnfurledStatusLinks[0]
   368  	require.Equal(t, "https://status.app/u/", l1.Url)
   369  	require.NotNil(t, l1.GetContact())
   370  	require.Nil(t, l1.GetCommunity())
   371  	require.Nil(t, l1.GetChannel())
   372  	c1 := l1.GetContact()
   373  	require.Equal(t, compressedContactPublicKey, c1.PublicKey)
   374  	require.Equal(t, contact.DisplayName, c1.DisplayName)
   375  	require.Equal(t, contact.Description, c1.Description)
   376  	require.NotNil(t, c1.Icon)
   377  	require.Equal(t, uint32(contact.Icon.Width), c1.Icon.Width)
   378  	require.Equal(t, uint32(contact.Icon.Height), c1.Icon.Height)
   379  	require.Equal(t, expectedThumbnailPayload, c1.Icon.Payload)
   380  
   381  	// Community link
   382  
   383  	l2 := unfurledLinks.UnfurledStatusLinks[1]
   384  	require.Equal(t, "https://status.app/c/", l2.Url)
   385  	require.NotNil(t, l2.GetCommunity())
   386  	require.Nil(t, l2.GetContact())
   387  	require.Nil(t, l2.GetChannel())
   388  	c2 := l2.GetCommunity()
   389  	require.Equal(t, compressedCommunityPublicKey, c2.CommunityId)
   390  	require.Equal(t, community.DisplayName, c2.DisplayName)
   391  	require.Equal(t, community.Description, c2.Description)
   392  	require.Equal(t, community.MembersCount, c2.MembersCount)
   393  	require.Equal(t, community.Color, c2.Color)
   394  	require.NotNil(t, c2.Icon)
   395  	require.Equal(t, uint32(community.Icon.Width), c2.Icon.Width)
   396  	require.Equal(t, uint32(community.Icon.Height), c2.Icon.Height)
   397  	require.Equal(t, expectedThumbnailPayload, c2.Icon.Payload)
   398  	require.NotNil(t, c2.Banner)
   399  	require.Equal(t, uint32(community.Banner.Width), c2.Banner.Width)
   400  	require.Equal(t, uint32(community.Banner.Height), c2.Banner.Height)
   401  	require.Equal(t, expectedThumbnailPayload, c2.Banner.Payload)
   402  
   403  	// Channel link
   404  
   405  	l3 := unfurledLinks.UnfurledStatusLinks[2]
   406  	require.Equal(t, "https://status.app/cc/", l3.Url)
   407  	require.NotNil(t, l3.GetChannel())
   408  	require.Nil(t, l3.GetContact())
   409  	require.Nil(t, l3.GetCommunity())
   410  
   411  	c3 := l3.GetChannel()
   412  	require.Equal(t, channel.ChannelUUID, c3.ChannelUuid)
   413  	require.Equal(t, channel.Emoji, c3.Emoji)
   414  	require.Equal(t, channel.DisplayName, c3.DisplayName)
   415  	require.Equal(t, channel.Description, c3.Description)
   416  	require.Equal(t, channel.Color, c3.Color)
   417  
   418  	require.NotNil(t, c3.Community)
   419  	require.Equal(t, compressedCommunityPublicKey2, c3.Community.CommunityId)
   420  	require.Equal(t, channel.Community.DisplayName, c3.Community.DisplayName)
   421  	require.Equal(t, channel.Community.Color, c3.Community.Color)
   422  	require.Equal(t, channel.Community.Description, c3.Community.Description)
   423  	require.Equal(t, channel.Community.MembersCount, c3.Community.MembersCount)
   424  	require.NotNil(t, c3.Community.Icon)
   425  	require.Equal(t, uint32(channel.Community.Icon.Width), c3.Community.Icon.Width)
   426  	require.Equal(t, uint32(channel.Community.Icon.Height), c3.Community.Icon.Height)
   427  	require.Equal(t, expectedThumbnailPayload, c3.Community.Icon.Payload)
   428  	require.NotNil(t, c3.Community.Banner)
   429  	require.Equal(t, uint32(channel.Community.Banner.Width), c3.Community.Banner.Width)
   430  	require.Equal(t, uint32(channel.Community.Banner.Height), c3.Community.Banner.Height)
   431  	require.Equal(t, expectedThumbnailPayload, c3.Community.Banner.Payload)
   432  
   433  	// Test any invalid link preview causes an early return.
   434  	invalidContactPreview := contact
   435  	invalidContactPreview.PublicKey = ""
   436  	invalidPreview := message.StatusLinkPreviews[0]
   437  	invalidPreview.Contact = invalidContactPreview
   438  	message.StatusLinkPreviews = []StatusLinkPreview{invalidPreview}
   439  	_, err = message.ConvertStatusLinkPreviewsToProto()
   440  	require.ErrorContains(t, err, "invalid status link preview, url='https://status.app/u/'")
   441  }
   442  
   443  func TestConvertFromProtoToStatusLinkPreviews(t *testing.T) {
   444  	pk1, err := crypto.GenerateKey()
   445  	require.NoError(t, err)
   446  
   447  	compressedContactPublicKey := crypto.CompressPubkey(&pk1.PublicKey)
   448  	contactID := types.EncodeHex(crypto.FromECDSAPub(&pk1.PublicKey))
   449  
   450  	pk2, err := crypto.GenerateKey()
   451  	require.NoError(t, err)
   452  
   453  	compressedCommunityPublicKey := crypto.CompressPubkey(&pk2.PublicKey)
   454  	communityID := types.EncodeHex(compressedCommunityPublicKey)
   455  
   456  	pk3, err := crypto.GenerateKey()
   457  	require.NoError(t, err)
   458  
   459  	compressedCommunityPublicKey2 := crypto.CompressPubkey(&pk3.PublicKey)
   460  	communityID2 := types.EncodeHex(compressedCommunityPublicKey2)
   461  
   462  	thumbnailPayload, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUg=")
   463  	require.NoError(t, err)
   464  
   465  	contact := &protobuf.UnfurledStatusContactLink{
   466  		PublicKey:   compressedContactPublicKey,
   467  		DisplayName: "DisplayName_2",
   468  		Description: "Description_3",
   469  		Icon: &protobuf.UnfurledLinkThumbnail{
   470  			Width:   10,
   471  			Height:  20,
   472  			Payload: thumbnailPayload,
   473  		},
   474  	}
   475  
   476  	community := &protobuf.UnfurledStatusCommunityLink{
   477  		CommunityId:  compressedCommunityPublicKey,
   478  		DisplayName:  "DisplayName_5",
   479  		Description:  "Description_6",
   480  		MembersCount: 7,
   481  		Color:        "Color_8",
   482  		Icon: &protobuf.UnfurledLinkThumbnail{
   483  			Width:   30,
   484  			Height:  40,
   485  			Payload: thumbnailPayload,
   486  		},
   487  		Banner: &protobuf.UnfurledLinkThumbnail{
   488  			Width:   50,
   489  			Height:  60,
   490  			Payload: thumbnailPayload,
   491  		},
   492  	}
   493  
   494  	channel := &protobuf.UnfurledStatusChannelLink{
   495  		ChannelUuid: "ChannelUuid_11",
   496  		Emoji:       "Emoji_12",
   497  		DisplayName: "DisplayName_13",
   498  		Description: "Description_14",
   499  		Color:       "Color_15",
   500  		Community: &protobuf.UnfurledStatusCommunityLink{
   501  			CommunityId:  compressedCommunityPublicKey2,
   502  			DisplayName:  "DisplayName_17",
   503  			Description:  "Description_18",
   504  			MembersCount: 19,
   505  			Color:        "Color_20",
   506  			Icon: &protobuf.UnfurledLinkThumbnail{
   507  				Width:   70,
   508  				Height:  80,
   509  				Payload: thumbnailPayload,
   510  			},
   511  			Banner: &protobuf.UnfurledLinkThumbnail{
   512  				Width:   90,
   513  				Height:  100,
   514  				Payload: thumbnailPayload,
   515  			},
   516  		},
   517  	}
   518  
   519  	msg := Message{
   520  		ID: "42",
   521  		ChatMessage: &protobuf.ChatMessage{
   522  			UnfurledStatusLinks: &protobuf.UnfurledStatusLinks{
   523  				UnfurledStatusLinks: []*protobuf.UnfurledStatusLink{
   524  					{
   525  						Url: "https://status.app/u/",
   526  						Payload: &protobuf.UnfurledStatusLink_Contact{
   527  							Contact: contact,
   528  						},
   529  					},
   530  					{
   531  						Url: "https://status.app/c/",
   532  						Payload: &protobuf.UnfurledStatusLink_Community{
   533  							Community: community,
   534  						},
   535  					},
   536  					{
   537  						Url: "https://status.app/cc/",
   538  						Payload: &protobuf.UnfurledStatusLink_Channel{
   539  							Channel: channel,
   540  						},
   541  					},
   542  				},
   543  			},
   544  		},
   545  	}
   546  
   547  	urlMaker := func(msgID string, linkURL string, imageID MediaServerImageID) string {
   548  		return "https://localhost:6666/" + msgID + "-" + linkURL + "-" + string(imageID)
   549  	}
   550  
   551  	previews := msg.ConvertFromProtoToStatusLinkPreviews(urlMaker)
   552  	require.Len(t, previews, 3)
   553  
   554  	// Contact preview
   555  
   556  	p1 := previews[0]
   557  	require.Equal(t, "https://status.app/u/", p1.URL)
   558  	require.NotNil(t, p1.Contact)
   559  	require.Nil(t, p1.Community)
   560  	require.Nil(t, p1.Channel)
   561  
   562  	c1 := p1.Contact
   563  	require.NotNil(t, c1)
   564  	require.Equal(t, contactID, c1.PublicKey)
   565  	require.Equal(t, contact.DisplayName, c1.DisplayName)
   566  	require.Equal(t, contact.Description, c1.Description)
   567  	require.NotNil(t, c1.Icon)
   568  	require.Equal(t, int(contact.Icon.Width), c1.Icon.Width)
   569  	require.Equal(t, int(contact.Icon.Height), c1.Icon.Height)
   570  	require.Equal(t, "", c1.Icon.DataURI)
   571  	require.Equal(t, "https://localhost:6666/42-https://status.app/u/-contact-icon", c1.Icon.URL)
   572  
   573  	// Community preview
   574  
   575  	p2 := previews[1]
   576  	require.Equal(t, "https://status.app/c/", p2.URL)
   577  	require.NotNil(t, p2.Community)
   578  	require.Nil(t, p2.Contact)
   579  	require.Nil(t, p2.Channel)
   580  
   581  	c2 := p2.Community
   582  	require.Equal(t, communityID, c2.CommunityID)
   583  	require.Equal(t, community.DisplayName, c2.DisplayName)
   584  	require.Equal(t, community.Description, c2.Description)
   585  	require.Equal(t, community.MembersCount, c2.MembersCount)
   586  	require.Equal(t, community.Color, c2.Color)
   587  	require.NotNil(t, c2.Icon)
   588  	require.Equal(t, int(community.Icon.Width), c2.Icon.Width)
   589  	require.Equal(t, int(community.Icon.Height), c2.Icon.Height)
   590  	require.Equal(t, "", c2.Icon.DataURI)
   591  	require.Equal(t, "https://localhost:6666/42-https://status.app/c/-community-icon", c2.Icon.URL)
   592  	require.NotNil(t, c2.Banner)
   593  	require.Equal(t, int(community.Banner.Width), c2.Banner.Width)
   594  	require.Equal(t, int(community.Banner.Height), c2.Banner.Height)
   595  	require.Equal(t, "", c2.Banner.DataURI)
   596  	require.Equal(t, "https://localhost:6666/42-https://status.app/c/-community-banner", c2.Banner.URL)
   597  
   598  	// Channel preview
   599  
   600  	p3 := previews[2]
   601  	require.Equal(t, "https://status.app/cc/", p3.URL)
   602  	require.NotNil(t, p3.Channel)
   603  	require.Nil(t, p3.Contact)
   604  	require.Nil(t, p3.Community)
   605  
   606  	c3 := previews[2].Channel
   607  	require.Equal(t, channel.ChannelUuid, c3.ChannelUUID)
   608  	require.Equal(t, channel.Emoji, c3.Emoji)
   609  	require.Equal(t, channel.DisplayName, c3.DisplayName)
   610  	require.Equal(t, channel.Description, c3.Description)
   611  	require.Equal(t, channel.Color, c3.Color)
   612  
   613  	require.NotNil(t, p3.Channel.Community)
   614  	require.Equal(t, communityID2, c3.Community.CommunityID)
   615  	require.Equal(t, channel.Community.DisplayName, c3.Community.DisplayName)
   616  	require.Equal(t, channel.Community.Color, c3.Community.Color)
   617  	require.Equal(t, channel.Community.Description, c3.Community.Description)
   618  	require.Equal(t, channel.Community.MembersCount, c3.Community.MembersCount)
   619  	require.NotNil(t, c3.Community.Icon)
   620  	require.Equal(t, int(channel.Community.Icon.Width), c3.Community.Icon.Width)
   621  	require.Equal(t, int(channel.Community.Icon.Height), c3.Community.Icon.Height)
   622  	require.Equal(t, "", c3.Community.Icon.DataURI)
   623  	require.Equal(t, "https://localhost:6666/42-https://status.app/cc/-community-channel-icon", c3.Community.Icon.URL)
   624  	require.NotNil(t, c3.Community.Banner)
   625  	require.Equal(t, int(channel.Community.Banner.Width), c3.Community.Banner.Width)
   626  	require.Equal(t, int(channel.Community.Banner.Height), c3.Community.Banner.Height)
   627  	require.Equal(t, "", c3.Community.Banner.DataURI)
   628  	require.Equal(t, "https://localhost:6666/42-https://status.app/cc/-community-channel-banner", c3.Community.Banner.URL)
   629  
   630  }
   631  
   632  func assertMarshalAndUnmarshalJSON[T any](t *testing.T, obj *T, msgAndArgs ...any) {
   633  	rawJSON, err := json.Marshal(obj)
   634  	require.NoError(t, err, msgAndArgs...)
   635  
   636  	var unmarshalled T
   637  	err = json.Unmarshal(rawJSON, &unmarshalled)
   638  	require.NoError(t, err, msgAndArgs...)
   639  	require.Equal(t, obj, &unmarshalled, msgAndArgs...)
   640  }
   641  
   642  func TestMarshalMessageJSON(t *testing.T) {
   643  	msg := &Message{
   644  		ID:   "1",
   645  		From: "0x04c51631b3354242d5a56f044c3b7703bcc001e8c725c4706928b3fac3c2a12ec9019e1e224d487f5c893389405bcec998bc687307f290a569d6a97d24b711bca8",
   646  		LinkPreviews: []LinkPreview{
   647  			{
   648  				Type:        protobuf.UnfurledLink_LINK,
   649  				Description: "GitHub is where people build software.",
   650  				Hostname:    "github.com",
   651  				Title:       "Build software better, together",
   652  				URL:         "https://github.com",
   653  				Thumbnail: LinkPreviewThumbnail{
   654  					Width:   100,
   655  					Height:  200,
   656  					URL:     "http://localhost:9999",
   657  					DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=",
   658  				},
   659  			},
   660  		},
   661  	}
   662  
   663  	assertMarshalAndUnmarshalJSON(t, msg, "message ID='%s'", msg.ID)
   664  }