mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-11 05:06:51 +00:00
GODT-1650: Test of end-to-end send with attachments (internal)
This commit is contained in:
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
142
tests/features/smtp/send/one_account_to_another.feature
Normal file
142
tests/features/smtp/send/one_account_to_another.feature
Normal 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 |
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user