test(GODT-3036): Also sort attachment for sent message + add unitary test.

This commit is contained in:
Romain LE JEUNE
2023-10-23 11:53:11 +02:00
committed by Romain
parent 11d1254a95
commit 6955dba06e
3 changed files with 210 additions and 12 deletions

View File

@@ -728,10 +728,7 @@ func (b *Backend) SendMessage(userID, messageID string, packages []*proton.Messa
newMsg.unread = true
messages[newMsg.messageID] = newMsg
// collect attachment with contentID
attIDs := sortAttachment(atts, msg.attIDs)
for _, attID := range attIDs {
for _, attID := range msg.attIDs {
attKey, err := base64.StdEncoding.DecodeString(recipient.AttachmentKeyPackets[attID])
if err != nil {
return err
@@ -749,7 +746,11 @@ func (b *Backend) SendMessage(userID, messageID string, packages []*proton.Messa
atts[att.attachID] = att
messages[newMsg.messageID].attIDs = append(messages[newMsg.messageID].attIDs, att.attachID)
}
// Sort Message attachments
messages[newMsg.messageID].attIDs = sortAttachment(atts, messages[newMsg.messageID].attIDs)
msg.attIDs = sortAttachment(atts, msg.attIDs)
// Send the update event
updateID, err := b.newUpdate(&messageCreated{messageID: newMsg.messageID})
if err != nil {
return err
@@ -780,28 +781,35 @@ func sortAttachment(atts map[string]*attachment, attIDs []string) []string {
attContentId[atts[attID].contentID] = attID
}
}
// sort contentID
contentIDs := make([]string, 0, len(attContentId))
for k := range attContentId {
contentIDs = append(contentIDs, k)
}
sort.Strings(contentIDs)
// set contentID attachment first
attachments := make([]string, len(attIDs))
attachments := make([]string, 0, len(attIDs))
for _, id := range contentIDs {
attachments = append(attachments, attContentId[id])
}
// follow with attachment without contentID
// treat the rest by filename
attNames := make(map[string]string, len(attIDs))
for _, attID := range attIDs {
if atts[attID].contentID != "" {
continue
if atts[attID].contentID == "" {
attNames[atts[attID].filename] = attID
}
attachments = append(attachments, attContentId[attID])
}
// sort name
names := make([]string, 0, len(attNames))
for k := range attNames {
names = append(names, k)
}
sort.Strings(names)
// Append by alphabetical order
for _, name := range names {
attachments = append(attachments, attNames[name])
}
return attachments
}
@@ -838,7 +846,10 @@ func (b *Backend) CreateAttachment(
atts[att.attachID] = att
// append attachment
messages[messageID].attIDs = append(messages[messageID].attIDs, att.attachID)
// sort the attachment list
messages[messageID].attIDs = sortAttachment(atts, messages[messageID].attIDs)
updateID, err := b.newUpdate(&messageUpdated{messageID: messageID})
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/ProtonMail/go-proton-api/server/backend"
"net/http"
"net/mail"
"net/url"
@@ -818,6 +819,124 @@ func TestServer_SendMessage(t *testing.T) {
})
}
func TestServer_SendMessageAttachmentSort(t *testing.T) {
withServer(t, func(ctx context.Context, s *Server, m *proton.Manager) {
withUser(ctx, t, s, m, "user", "pass", func(c *proton.Client) {
user, err := c.GetUser(ctx)
require.NoError(t, err)
addr, err := c.GetAddresses(ctx)
require.NoError(t, err)
salt, err := c.GetSalts(ctx)
require.NoError(t, err)
pass, err := salt.SaltForKey([]byte("pass"), user.Keys.Primary().ID)
require.NoError(t, err)
_, addrKRs, err := proton.Unlock(user, addr, pass, async.NoopPanicHandler{})
require.NoError(t, err)
body, err := os.ReadFile("../testdata/MultipleAttachments.eml")
require.NoError(t, err)
// create the draft
draft, err := c.CreateDraft(ctx, addrKRs[addr[0].ID], proton.CreateDraftReq{
Message: proton.DraftTemplate{
Subject: "My subject",
Sender: &mail.Address{Address: addr[0].Email},
ToList: []*mail.Address{{Address: "user@proton.local"}},
Body: string(body),
},
})
require.NoError(t, err)
// upload attachments in mixed order
{
_, err = c.UploadAttachment(ctx, addrKRs[addr[0].ID], proton.CreateAttachmentReq{
MessageID: draft.ID,
Filename: "inlinepart2.png",
MIMEType: "image/png",
Disposition: proton.InlineDisposition,
ContentID: "part2.erfefw",
})
require.NoError(t, err)
_, err = c.UploadAttachment(ctx, addrKRs[addr[0].ID], proton.CreateAttachmentReq{
MessageID: draft.ID,
Filename: "alphabeticalZZZZ.png",
MIMEType: "image/png",
Disposition: proton.AttachmentDisposition,
})
require.NoError(t, err)
_, err = c.UploadAttachment(ctx, addrKRs[addr[0].ID], proton.CreateAttachmentReq{
MessageID: draft.ID,
Filename: "inlinepart1.png",
MIMEType: "image/png",
Disposition: proton.InlineDisposition,
ContentID: "part1.erfefw",
})
require.NoError(t, err)
_, err = c.UploadAttachment(ctx, addrKRs[addr[0].ID], proton.CreateAttachmentReq{
MessageID: draft.ID,
Filename: "alphabeticalAAA.png",
MIMEType: "image/png",
Disposition: proton.AttachmentDisposition,
})
require.NoError(t, err)
}
// prepare the package
var req proton.SendDraftReq
attkeys := make(map[string]*crypto.SessionKey)
err = req.AddTextPackage(addrKRs[addr[0].ID], string(body), "text/html", map[string]proton.SendPreferences{"user@proton.local": {
Encrypt: true,
PubKey: addrKRs[addr[0].ID],
SignatureType: proton.DetachedSignature,
EncryptionScheme: proton.InternalScheme,
MIMEType: rfc822.TextHTML,
}}, attkeys)
require.NoError(t, err)
// send the draft
sent, err := c.SendDraft(ctx, draft.ID, req)
require.NoError(t, err)
// Check attachment order
require.Equal(t, 4, len(sent.Attachments))
require.Equal(t, "inlinepart1.png", sent.Attachments[0].Name)
require.Equal(t, "inlinepart2.png", sent.Attachments[1].Name)
require.Equal(t, "alphabeticalAAA.png", sent.Attachments[2].Name)
require.Equal(t, "alphabeticalZZZZ.png", sent.Attachments[3].Name)
// catch the message created event from the receiver
rawEventID, err := c.GetLatestEventID(ctx)
require.NoError(t, err)
var eventID backend.ID
err = eventID.FromString(rawEventID)
require.NoError(t, err)
eventID = eventID - 1
events, _, err := c.GetEvent(ctx, eventID.String())
require.NoError(t, err)
require.Equal(t, 1, len(events))
require.Equal(t, 1, len(events[0].Messages))
// get the message from receiver inbox
rcv, err := c.GetMessage(ctx, events[0].Messages[0].Message.ID)
require.NoError(t, err)
// Check attachment order
require.Equal(t, 4, len(rcv.Attachments))
require.Equal(t, "inlinepart1.png", rcv.Attachments[0].Name)
require.Equal(t, "inlinepart2.png", rcv.Attachments[1].Name)
require.Equal(t, "alphabeticalAAA.png", sent.Attachments[2].Name)
require.Equal(t, "alphabeticalZZZZ.png", sent.Attachments[3].Name)
})
})
}
func TestServer_AuthDelete(t *testing.T) {
withServer(t, func(ctx context.Context, s *Server, m *proton.Manager) {
withUser(ctx, t, s, m, "user", "pass", func(c *proton.Client) {

68
testdata/MultipleAttachments.eml vendored Normal file
View File

@@ -0,0 +1,68 @@
Content-Type: multipart/related;boundary="------------pXWj190lQsd0d77xbCjkhoss"
User-Agent: Mozilla Thunderbird
Content-Language: en-GB
To: <[user:to]@[domain]>
From: <[user:user]@[domain]>
Subject: HTML message with multiple inline images
--------------pXWj190lQsd0d77xbCjkhoss
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Inline image 1</p>
<p><img src="cid:part1.g6ktVAf2.OzOgqU7w@protonmail.com"
moz-do-not-send="false"></p>
<p>Inline image 2</p>
<p><img src="cid:part2.rAUlK0aY.qNBo3Y1b@protonmail.com"
moz-do-not-send="false"></p>
<p>End<br>
</p>
<br>
</body>
</html>
--------------pXWj190lQsd0d77xbCjkhoss
Content-Type: image/png; name="inlinepart2.png"
Content-Disposition: inline; filename="inlinepart2.png"
Content-Id: <part2.g6ktVAf2.OzOgqU7w@protonmail.com>
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAh1BMVEX///8AAAABAQGbm5sP
Qm1ljU7fCDEb0EoXQHyzccFedRPEVPtOY91tb9BVtzebhEdH0R1oLSwO3QvtoFl8YgQXV7eL
BYPFOEzNxiFmI0jD8eL1M3D/A1pNGGh1w+TvAAAAAElFTkSuQmCC
--------------pXWj190lQsd0d77xbCjkhoss
Content-Type: image/png; name="alphabeticalZZZZ.png"
Content-Disposition: attachment; filename="alphabeticalZZZZ.png"
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAh1BMVEX///8AAAABAQGbm5sP
Qm1ljU7fCDEb0EoXQHyzccFedRPEVPtOY91tb9BVtzebhEdH0R1oLSwO3QvtoFl8YgQXV7eL
BYPFOEzNxiFmI0jD8eL1M3D/A1pNGGh1w+TvAAAAAElFTkSuQmCC
--------------pXWj190lQsd0d77xbCjkhoss
Content-Type: image/png; name="inlinepart1.png"
Content-Disposition: inline; filename="inlinepart1.png"
Content-Id: <part1.g6ktVAf2.OzOgqU7w@protonmail.com>
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAh1BMVEX///8AAAABAQGbm5sP
Qm1ljU7fCDEb0EoXQHyzccFedRPEVPtOY91tb9BVtzebhEdH0R1oLSwO3QvtoFl8YgQXV7eL
BYPFOEzNxiFmI0jD8eL1M3D/A1pNGGh1w+TvAAAAAElFTkSuQmCC
--------------pXWj190lQsd0d77xbCjkhoss
Content-Type: image/png; name="alphabeticalAAA.png"
Content-Disposition: attachment; filename="alphabeticalAAA.png"
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAh1BMVEX///8AAAABAQGbm5sP
Qm1ljU7fCDEb0EoXQHyzccFedRPEVPtOY91tb9BVtzebhEdH0R1oLSwO3QvtoFl8YgQXV7eL
BYPFOEzNxiFmI0jD8eL1M3D/A1pNGGh1w+TvAAAAAElFTkSuQmCC
--------------pXWj190lQsd0d77xbCjkhoss--