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

4
go.mod
View File

@ -5,7 +5,7 @@ go 1.18
require ( require (
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
github.com/Masterminds/semver/v3 v3.1.1 github.com/Masterminds/semver/v3 v3.1.1
github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0 github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-rfc5322 v0.11.0 github.com/ProtonMail/go-rfc5322 v0.11.0
github.com/ProtonMail/gopenpgp/v2 v2.4.10 github.com/ProtonMail/gopenpgp/v2 v2.4.10
@ -38,7 +38,7 @@ require (
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
github.com/urfave/cli/v2 v2.16.3 github.com/urfave/cli/v2 v2.16.3
gitlab.protontech.ch/go/liteapi v0.32.1 gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b
golang.org/x/exp v0.0.0-20220921164117-439092de6870 golang.org/x/exp v0.0.0-20220921164117-439092de6870
golang.org/x/net v0.1.0 golang.org/x/net v0.1.0
golang.org/x/sys v0.1.0 golang.org/x/sys v0.1.0

10
go.sum
View File

@ -31,6 +31,10 @@ github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkF
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0 h1:SsacIP40QP64FNZrBlm5XDLHZMIx0i36mUYmTSWI2Y4= github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0 h1:SsacIP40QP64FNZrBlm5XDLHZMIx0i36mUYmTSWI2Y4=
github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI= github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI=
github.com/ProtonMail/gluon v0.11.1-0.20221006085838-c527b37bb418 h1:YvWdTvj2s+ZqEy0HwCIRFnGcWRhF0OY0EuIiknGChwM=
github.com/ProtonMail/gluon v0.11.1-0.20221006085838-c527b37bb418/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI=
github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d h1:EgrtTr3sx5cHkkd8/fviO5YOIcT9iOTibqJek2uhwM0=
github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
@ -465,6 +469,12 @@ github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0
github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
gitlab.protontech.ch/go/liteapi v0.32.1 h1:EiaLP+LkVVDqFxU6Uux4w5HHfPj6dSnOILJPzIny5l4= gitlab.protontech.ch/go/liteapi v0.32.1 h1:EiaLP+LkVVDqFxU6Uux4w5HHfPj6dSnOILJPzIny5l4=
gitlab.protontech.ch/go/liteapi v0.32.1/go.mod h1:SVxEeF4uYYYpSlfeAj2ZqluVEP95pbZ8LyoieSxU0pM= gitlab.protontech.ch/go/liteapi v0.32.1/go.mod h1:SVxEeF4uYYYpSlfeAj2ZqluVEP95pbZ8LyoieSxU0pM=
gitlab.protontech.ch/go/liteapi v0.33.0 h1:lkerCG7Y3Tn+ICPwVkyZxPu5jJjiTTfaYiuqUwXRF0E=
gitlab.protontech.ch/go/liteapi v0.33.0/go.mod h1:+70trwxSrBP1fU1m2q5wzhYkr9/NUrFGuBr6cKl3ixk=
gitlab.protontech.ch/go/liteapi v0.33.1 h1:Ks8YdojRwYTLTUmGLG9MzFvxNeiwYJYQEaTHUQjOvsA=
gitlab.protontech.ch/go/liteapi v0.33.1/go.mod h1:9nsslyEJn7Utbielp4c+hc7qT6hqIJ52aGFR/tX+tYk=
gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b h1:Obu2CCCYdVi3NJmoYf/iJco1mat6EzJezInKoUTo+Dc=
gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b/go.mod h1:9nsslyEJn7Utbielp4c+hc7qT6hqIJ52aGFR/tX+tYk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=

View File

@ -22,7 +22,7 @@ import (
"github.com/bradenaw/juniper/xslices" "github.com/bradenaw/juniper/xslices"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.protontech.ch/go/liteapi/server" "gitlab.protontech.ch/go/liteapi/server"
"gitlab.protontech.ch/go/liteapi/server/account" "gitlab.protontech.ch/go/liteapi/server/backend"
) )
const ( const (
@ -39,7 +39,7 @@ var (
func init() { func init() {
user.DefaultEventPeriod = 100 * time.Millisecond user.DefaultEventPeriod = 100 * time.Millisecond
user.DefaultEventJitter = 0 user.DefaultEventJitter = 0
account.GenerateKey = tests.FastGenerateKey backend.GenerateKey = tests.FastGenerateKey
certs.GenerateCert = tests.FastGenerateCert certs.GenerateCert = tests.FastGenerateCert
} }

View File

@ -16,13 +16,13 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.protontech.ch/go/liteapi" "gitlab.protontech.ch/go/liteapi"
"gitlab.protontech.ch/go/liteapi/server" "gitlab.protontech.ch/go/liteapi/server"
"gitlab.protontech.ch/go/liteapi/server/account" "gitlab.protontech.ch/go/liteapi/server/backend"
) )
func init() { func init() {
user.DefaultEventPeriod = 100 * time.Millisecond user.DefaultEventPeriod = 100 * time.Millisecond
user.DefaultEventJitter = 0 user.DefaultEventJitter = 0
account.GenerateKey = tests.FastGenerateKey backend.GenerateKey = tests.FastGenerateKey
certs.GenerateCert = tests.FastGenerateCert certs.GenerateCert = tests.FastGenerateCert
} }

View File

@ -1,10 +1,7 @@
package tests package tests
import ( import (
"net/mail"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/rfc822"
"gitlab.protontech.ch/go/liteapi" "gitlab.protontech.ch/go/liteapi"
"gitlab.protontech.ch/go/liteapi/server" "gitlab.protontech.ch/go/liteapi/server"
) )
@ -23,8 +20,9 @@ type API interface {
GetLabels(userID string) ([]liteapi.Label, error) GetLabels(userID string) ([]liteapi.Label, error)
CreateLabel(userID, name string, labelType liteapi.LabelType) (string, error) CreateLabel(userID, name string, labelType liteapi.LabelType) (string, error)
GetMessages(userID string) ([]liteapi.Message, error) CreateMessage(userID, addrID string, literal []byte, flags liteapi.MessageFlag, unread, starred bool) (string, 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) LabelMessage(userID, messageID, labelID string) error
UnlabelMessage(userID, messageID, labelID string) error
Close() Close()
} }

View File

@ -28,12 +28,12 @@ import (
"github.com/ProtonMail/proton-bridge/v2/internal/user" "github.com/ProtonMail/proton-bridge/v2/internal/user"
"github.com/cucumber/godog" "github.com/cucumber/godog"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.protontech.ch/go/liteapi/server/account" "gitlab.protontech.ch/go/liteapi/server/backend"
) )
func init() { func init() {
// Use the fast key generation for tests. // Use the fast key generation for tests.
account.GenerateKey = FastGenerateKey backend.GenerateKey = FastGenerateKey
// Use the fast cert generation for tests. // Use the fast cert generation for tests.
certs.GenerateCert = FastGenerateCert 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() { go func() {
if err := client.Fetch( if err := client.Fetch(
&imap.SeqSet{Set: []imap.Seq{{Start: 1, Stop: status.Messages}}}, &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, resCh,
); err != nil { ); err != nil {
panic(err) panic(err)

View File

@ -4,15 +4,21 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"time" "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/cucumber/messages-go/v16"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
type Message struct { type Message struct {
Subject string `bdd:"subject"` Subject string `bdd:"subject"`
Body string `bdd:"body"`
Attachments string `bdd:"attachments"`
From string `bdd:"from"` From string `bdd:"from"`
To string `bdd:"to"` To string `bdd:"to"`
@ -22,10 +28,60 @@ type Message struct {
Unread bool `bdd:"unread"` 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 { 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{ message := Message{
Subject: msg.Envelope.Subject, Subject: msg.Envelope.Subject,
Unread: slices.Contains(msg.Flags, imap.SeenFlag), 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 { if len(msg.Envelope.From) > 0 {
@ -48,6 +104,14 @@ func newMessageFromIMAP(msg *imap.Message) Message {
} }
func matchMessages(have, want []Message) error { 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)) { if !IsSub(ToAny(have), ToAny(want)) {
return fmt.Errorf("missing messages: %v", want) return fmt.Errorf("missing messages: %v", want)
} }

View File

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