GODT-1650: Test of end-to-end send with attachments (internal)

This commit is contained in:
James Houlahan
2022-10-06 00:34:12 +02:00
parent df7479f506
commit cce372fc50
10 changed files with 248 additions and 47 deletions

View File

@ -1,10 +1,7 @@
package tests
import (
"net/mail"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/rfc822"
"gitlab.protontech.ch/go/liteapi"
"gitlab.protontech.ch/go/liteapi/server"
)
@ -23,8 +20,9 @@ type API interface {
GetLabels(userID string) ([]liteapi.Label, error)
CreateLabel(userID, name string, labelType liteapi.LabelType) (string, error)
GetMessages(userID string) ([]liteapi.Message, error)
CreateMessage(userID, addrID string, labelIDs []string, subject string, sender *mail.Address, toList, ccList, bccList []*mail.Address, decBody string, mimeType rfc822.MIMEType, read, starred bool) (string, error)
CreateMessage(userID, addrID string, literal []byte, flags liteapi.MessageFlag, unread, starred bool) (string, error)
LabelMessage(userID, messageID, labelID string) error
UnlabelMessage(userID, messageID, labelID string) error
Close()
}

View File

@ -28,12 +28,12 @@ import (
"github.com/ProtonMail/proton-bridge/v2/internal/user"
"github.com/cucumber/godog"
"github.com/stretchr/testify/require"
"gitlab.protontech.ch/go/liteapi/server/account"
"gitlab.protontech.ch/go/liteapi/server/backend"
)
func init() {
// Use the fast key generation for tests.
account.GenerateKey = FastGenerateKey
backend.GenerateKey = FastGenerateKey
// Use the fast cert generation for tests.
certs.GenerateCert = FastGenerateCert

View File

@ -0,0 +1,142 @@
Feature: SMTP sending two messages
Background:
Given there exists an account with username "user@pm.me" and password "password"
And there exists an account with username "other@pm.me" and password "other"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
And the user logs in with username "other@pm.me" and password "other"
Scenario: Send from one account to the other
When user "user@pm.me" connects and authenticates SMTP client "1"
And SMTP client "1" sends the following message from "user@pm.me" to "other@pm.me":
"""
From: Bridge Test <user@pm.me>
To: Internal Bridge <other@pm.me>
Subject: One account to the other
hello
"""
Then it succeeds
And the body in the "POST" request to "/mail/v4/messages" is:
"""
{
"Message": {
"Subject": "One account to the other",
"Sender": {
"Name": "Bridge Test",
"Address": "user@pm.me"
},
"ToList": [
{
"Name": "Internal Bridge",
"Address": "other@pm.me"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
And the body in the "POST" request to "/mail/v4/messages/.*" is:
"""
{
"Packages":[
{
"Addresses":{
"other@pm.me":{
"Type":1
}
},
"Type":1,
"MIMEType":"text/plain"
}
]
}
"""
When user "other@pm.me" connects and authenticates IMAP client "1"
Then IMAP client "1" eventually sees the following messages in "Inbox":
| from | to | subject | body |
| user@pm.me | other@pm.me | One account to the other | hello |
Scenario: Send from one account to the other with attachments
When user "user@pm.me" connects and authenticates SMTP client "1"
And SMTP client "1" sends the following message from "user@pm.me" to "other@pm.me":
"""
From: Bridge Test <user@pm.me>
To: Internal Bridge <other@pm.me>
Subject: Plain with attachment internal
Content-Type: multipart/related; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=utf-8
This is the body
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: attachment; filename=outline-light-instagram-48.png
Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com>
Content-Transfer-Encoding: base64
Content-Type: image/png
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--
"""
Then it succeeds
And the body in the "POST" request to "/mail/v4/messages" is:
"""
{
"Message": {
"Subject": "Plain with attachment internal",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "other@pm.me",
"Name": "Internal Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
And the body in the "POST" request to "/mail/v4/messages/.*" is:
"""
{
"Packages":[
{
"Addresses":{
"other@pm.me":{
"Type":1
}
},
"Type":1,
"MIMEType":"text/plain"
}
]
}
"""
When user "user@pm.me" connects and authenticates IMAP client "1"
Then IMAP client "1" eventually sees the following messages in "Sent":
| from | to | subject | body | attachments | unread |
| user@pm.me | other@pm.me | Plain with attachment internal | This is the body | outline-light-instagram-48.png | false |
When user "other@pm.me" connects and authenticates IMAP client "2"
Then IMAP client "2" eventually sees the following messages in "Inbox":
| from | to | subject | body | attachments | unread |
| user@pm.me | other@pm.me | Plain with attachment internal | This is the body | outline-light-instagram-48.png | true |

View File

@ -444,7 +444,7 @@ func clientFetch(client *client.Client, mailbox string) ([]*imap.Message, error)
go func() {
if err := client.Fetch(
&imap.SeqSet{Set: []imap.Seq{{Start: 1, Stop: status.Messages}}},
[]imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid},
[]imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid, "BODY.PEEK[]"},
resCh,
); err != nil {
panic(err)

View File

@ -4,15 +4,21 @@ import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/ProtonMail/gluon/rfc822"
"github.com/ProtonMail/proton-bridge/v2/pkg/message"
"github.com/bradenaw/juniper/xslices"
"github.com/cucumber/messages-go/v16"
"github.com/emersion/go-imap"
"golang.org/x/exp/slices"
)
type Message struct {
Subject string `bdd:"subject"`
Subject string `bdd:"subject"`
Body string `bdd:"body"`
Attachments string `bdd:"attachments"`
From string `bdd:"from"`
To string `bdd:"to"`
@ -22,10 +28,60 @@ type Message struct {
Unread bool `bdd:"unread"`
}
func (msg Message) Build() []byte {
var b []byte
if msg.From != "" {
b = append(b, "From: "+msg.From+"\r\n"...)
}
if msg.To != "" {
b = append(b, "To: "+msg.To+"\r\n"...)
}
if msg.CC != "" {
b = append(b, "Cc: "+msg.CC+"\r\n"...)
}
if msg.BCC != "" {
b = append(b, "Bcc: "+msg.BCC+"\r\n"...)
}
if msg.Subject != "" {
b = append(b, "Subject: "+msg.Subject+"\r\n"...)
}
if msg.Body != "" {
b = append(b, "\r\n"+msg.Body+"\r\n"...)
}
return b
}
func newMessageFromIMAP(msg *imap.Message) Message {
section, err := imap.ParseBodySectionName("BODY[]")
if err != nil {
panic(err)
}
m, err := message.Parse(msg.GetBody(section))
if err != nil {
panic(err)
}
var body string
if m.MIMEType == rfc822.TextPlain {
body = strings.TrimSpace(string(m.PlainBody))
} else {
body = strings.TrimSpace(string(m.RichBody))
}
message := Message{
Subject: msg.Envelope.Subject,
Unread: slices.Contains(msg.Flags, imap.SeenFlag),
Subject: msg.Envelope.Subject,
Body: body,
Attachments: strings.Join(xslices.Map(m.Attachments, func(att message.Attachment) string { return att.Name }), ", "),
Unread: !slices.Contains(msg.Flags, imap.SeenFlag),
}
if len(msg.Envelope.From) > 0 {
@ -48,6 +104,14 @@ func newMessageFromIMAP(msg *imap.Message) Message {
}
func matchMessages(have, want []Message) error {
slices.SortFunc(have, func(a, b Message) bool {
return a.Subject < b.Subject
})
slices.SortFunc(want, func(a, b Message) bool {
return a.Subject < b.Subject
})
if !IsSub(ToAny(have), ToAny(want)) {
return fmt.Errorf("missing messages: %v", want)
}

View File

@ -4,10 +4,8 @@ import (
"context"
"errors"
"fmt"
"net/mail"
"time"
"github.com/ProtonMail/gluon/rfc822"
"github.com/cucumber/godog"
"github.com/google/uuid"
"gitlab.protontech.ch/go/liteapi"
@ -120,20 +118,12 @@ func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address,
}
for _, wantMessage := range wantMessages {
if _, err := s.t.api.CreateMessage(
userID,
addrID,
[]string{mboxID},
wantMessage.Subject,
&mail.Address{Address: wantMessage.From},
[]*mail.Address{{Address: wantMessage.To}},
[]*mail.Address{{Address: wantMessage.CC}},
[]*mail.Address{{Address: wantMessage.BCC}},
"some body goes here",
rfc822.TextPlain,
wantMessage.Unread,
false,
); err != nil {
messageID, err := s.t.api.CreateMessage(userID, addrID, wantMessage.Build(), liteapi.MessageFlagReceived, wantMessage.Unread, false)
if err != nil {
return err
}
if err := s.t.api.LabelMessage(userID, messageID, mboxID); err != nil {
return err
}
}
@ -147,20 +137,17 @@ func (s *scenario) theAddressOfAccountHasMessagesInMailbox(address, username str
mboxID := s.t.getMBoxID(userID, mailbox)
for idx := 0; idx < count; idx++ {
if _, err := s.t.api.CreateMessage(
userID,
addrID,
[]string{mboxID},
fmt.Sprintf("subject %d", idx),
&mail.Address{Address: fmt.Sprintf("sender %d", idx)},
[]*mail.Address{{Address: fmt.Sprintf("recipient %d", idx)}},
[]*mail.Address{},
[]*mail.Address{},
fmt.Sprintf("body %d", idx),
rfc822.TextPlain,
idx%2 == 0,
false,
); err != nil {
messageID, err := s.t.api.CreateMessage(userID, addrID, Message{
Subject: fmt.Sprintf("subject %d", idx),
To: fmt.Sprintf("to %d", idx),
From: fmt.Sprintf("from %d", idx),
Body: fmt.Sprintf("body %d", idx),
}.Build(), liteapi.MessageFlagReceived, idx%2 == 0, false)
if err != nil {
return err
}
if err := s.t.api.LabelMessage(userID, messageID, mboxID); err != nil {
return err
}
}