mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
Update ServerManager to follow the new expected behavior. The servers will only be started when one user is active. If all users are logged out or removed from the system, the servers will stop. If the network goes down, the servers will stop and resume once network has been restored.
522 lines
18 KiB
Go
522 lines
18 KiB
Go
// Copyright (c) 2023 Proton AG
|
|
//
|
|
// This file is part of Proton Mail Bridge.
|
|
//
|
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package bridge_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/go-proton-api"
|
|
"github.com/ProtonMail/go-proton-api/server"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
|
"github.com/emersion/go-imap"
|
|
"github.com/emersion/go-imap/client"
|
|
"github.com/emersion/go-sasl"
|
|
"github.com/emersion/go-smtp"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestBridge_Send(t *testing.T) {
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
|
_, _, err := s.CreateUser("recipient", password)
|
|
require.NoError(t, err)
|
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
|
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
defer smtpWaiter.Done()
|
|
|
|
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
recipientUserID, err := bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
smtpWaiter.Wait()
|
|
|
|
senderInfo, err := bridge.GetUserInfo(senderUserID)
|
|
require.NoError(t, err)
|
|
|
|
recipientInfo, err := bridge.GetUserInfo(recipientUserID)
|
|
require.NoError(t, err)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
// Dial the server.
|
|
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
|
require.NoError(t, err)
|
|
defer client.Close() //nolint:errcheck
|
|
|
|
// Upgrade to TLS.
|
|
require.NoError(t, client.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
|
|
|
if i%2 == 0 {
|
|
// Authorize with SASL PLAIN.
|
|
require.NoError(t, client.Auth(sasl.NewPlainClient(
|
|
senderInfo.Addresses[0],
|
|
senderInfo.Addresses[0],
|
|
string(senderInfo.BridgePass)),
|
|
))
|
|
} else {
|
|
// Authorize with SASL LOGIN.
|
|
require.NoError(t, client.Auth(sasl.NewLoginClient(
|
|
senderInfo.Addresses[0],
|
|
string(senderInfo.BridgePass)),
|
|
))
|
|
}
|
|
|
|
// Send the message.
|
|
require.NoError(t, client.SendMail(
|
|
senderInfo.Addresses[0],
|
|
[]string{recipientInfo.Addresses[0]},
|
|
strings.NewReader(fmt.Sprintf("Subject: Test %v\r\n\r\nHello world!", i)),
|
|
))
|
|
}
|
|
|
|
// Connect the sender IMAP client.
|
|
senderIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
|
require.NoError(t, err)
|
|
require.NoError(t, senderIMAPClient.Login(senderInfo.Addresses[0], string(senderInfo.BridgePass)))
|
|
defer senderIMAPClient.Logout() //nolint:errcheck
|
|
|
|
// Connect the recipient IMAP client.
|
|
recipientIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
|
require.NoError(t, err)
|
|
require.NoError(t, recipientIMAPClient.Login(recipientInfo.Addresses[0], string(recipientInfo.BridgePass)))
|
|
defer recipientIMAPClient.Logout() //nolint:errcheck
|
|
|
|
// Sender should have 10 messages in the sent folder.
|
|
// Recipient should have 10 messages in inbox.
|
|
require.Eventually(t, func() bool {
|
|
sent, err := senderIMAPClient.Status(`Sent`, []imap.StatusItem{imap.StatusMessages})
|
|
require.NoError(t, err)
|
|
|
|
inbox, err := recipientIMAPClient.Status(`Inbox`, []imap.StatusItem{imap.StatusMessages})
|
|
require.NoError(t, err)
|
|
|
|
return sent.Messages == 10 && inbox.Messages == 10
|
|
}, 10*time.Second, 100*time.Millisecond)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestBridge_SendDraftFlags(t *testing.T) {
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
|
// Create a recipient user.
|
|
_, _, err := s.CreateUser("recipient", password)
|
|
require.NoError(t, err)
|
|
|
|
// The sender should be fully synced.
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
|
defer done()
|
|
|
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, userID, (<-syncCh).UserID)
|
|
})
|
|
|
|
// Start the bridge.
|
|
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
|
// Get the sender user info.
|
|
userInfo, err := bridge.QueryUserInfo(username)
|
|
require.NoError(t, err)
|
|
|
|
// Connect the sender IMAP client.
|
|
imapClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
|
require.NoError(t, err)
|
|
require.NoError(t, imapClient.Login(userInfo.Addresses[0], string(userInfo.BridgePass)))
|
|
defer imapClient.Logout() //nolint:errcheck
|
|
|
|
// The message to send.
|
|
const message = `Subject: Test\r\n\r\nHello world!`
|
|
|
|
// Save a draft.
|
|
require.NoError(t, imapClient.Append("Drafts", []string{imap.DraftFlag}, time.Now(), strings.NewReader(message)))
|
|
|
|
// Assert that the draft exists and is marked as a draft.
|
|
{
|
|
messages, err := clientFetch(imapClient, "Drafts")
|
|
require.NoError(t, err)
|
|
require.Len(t, messages, 1)
|
|
require.Contains(t, messages[0].Flags, imap.DraftFlag)
|
|
}
|
|
|
|
// Connect the SMTP client.
|
|
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
|
require.NoError(t, err)
|
|
defer smtpClient.Close() //nolint:errcheck
|
|
|
|
// Upgrade to TLS.
|
|
require.NoError(t, smtpClient.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
|
|
|
// Authorize with SASL PLAIN.
|
|
require.NoError(t, smtpClient.Auth(sasl.NewPlainClient(
|
|
userInfo.Addresses[0],
|
|
userInfo.Addresses[0],
|
|
string(userInfo.BridgePass)),
|
|
))
|
|
|
|
// Send the message.
|
|
require.NoError(t, smtpClient.SendMail(
|
|
userInfo.Addresses[0],
|
|
[]string{"recipient@" + s.GetDomain()},
|
|
strings.NewReader(message),
|
|
))
|
|
|
|
// Delete the draft: add the \Deleted flag and expunge.
|
|
{
|
|
status, err := imapClient.Select("Drafts", false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint32(1), status.Messages)
|
|
|
|
// Add the \Deleted flag.
|
|
require.NoError(t, clientStore(imapClient, 1, 1, true, imap.FormatFlagsOp(imap.AddFlags, true), imap.DeletedFlag))
|
|
|
|
// Expunge.
|
|
require.NoError(t, imapClient.Expunge(nil))
|
|
}
|
|
|
|
// Assert that the draft is eventually gone.
|
|
require.Eventually(t, func() bool {
|
|
status, err := imapClient.Select("Drafts", false)
|
|
require.NoError(t, err)
|
|
return status.Messages == 0
|
|
}, 10*time.Second, 100*time.Millisecond)
|
|
|
|
// Assert that the message is eventually in the sent folder.
|
|
require.Eventually(t, func() bool {
|
|
messages, err := clientFetch(imapClient, "Sent")
|
|
require.NoError(t, err)
|
|
return len(messages) == 1
|
|
}, 10*time.Second, 100*time.Millisecond)
|
|
|
|
// Assert that the message is not marked as a draft.
|
|
{
|
|
messages, err := clientFetch(imapClient, "Sent")
|
|
require.NoError(t, err)
|
|
require.Len(t, messages, 1)
|
|
require.NotContains(t, messages[0].Flags, imap.DraftFlag)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestBridge_SendInvite(t *testing.T) {
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
|
// Create a recipient user.
|
|
_, _, err := s.CreateUser("recipient", password)
|
|
require.NoError(t, err)
|
|
|
|
// Set "attach public keys" to true for the user.
|
|
withClient(ctx, t, s, username, password, func(ctx context.Context, client *proton.Client) {
|
|
settings, err := client.SetAttachPublicKey(ctx, proton.SetAttachPublicKeyReq{AttachPublicKey: true})
|
|
require.NoError(t, err)
|
|
require.Equal(t, proton.Bool(true), settings.AttachPublicKey)
|
|
})
|
|
|
|
// The sender should be fully synced.
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
|
defer done()
|
|
|
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, userID, (<-syncCh).UserID)
|
|
})
|
|
|
|
// Start the bridge.
|
|
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
|
// Get the sender user info.
|
|
userInfo, err := bridge.QueryUserInfo(username)
|
|
require.NoError(t, err)
|
|
|
|
// Connect the sender IMAP client.
|
|
imapClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
|
require.NoError(t, err)
|
|
require.NoError(t, imapClient.Login(userInfo.Addresses[0], string(userInfo.BridgePass)))
|
|
defer imapClient.Logout() //nolint:errcheck
|
|
|
|
// The message to send.
|
|
b, err := os.ReadFile("testdata/invite.eml")
|
|
require.NoError(t, err)
|
|
|
|
// Save a draft.
|
|
require.NoError(t, imapClient.Append("Drafts", []string{imap.DraftFlag}, time.Now(), bytes.NewReader(b)))
|
|
|
|
// Assert that the draft exists and is marked as a draft.
|
|
{
|
|
messages, err := clientFetch(imapClient, "Drafts")
|
|
require.NoError(t, err)
|
|
require.Len(t, messages, 1)
|
|
require.Contains(t, messages[0].Flags, imap.DraftFlag)
|
|
}
|
|
|
|
// Connect the SMTP client.
|
|
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
|
require.NoError(t, err)
|
|
defer smtpClient.Close() //nolint:errcheck
|
|
|
|
// Upgrade to TLS.
|
|
require.NoError(t, smtpClient.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
|
|
|
// Authorize with SASL PLAIN.
|
|
require.NoError(t, smtpClient.Auth(sasl.NewPlainClient(
|
|
userInfo.Addresses[0],
|
|
userInfo.Addresses[0],
|
|
string(userInfo.BridgePass)),
|
|
))
|
|
|
|
// Send the message.
|
|
require.NoError(t, smtpClient.SendMail(
|
|
userInfo.Addresses[0],
|
|
[]string{"recipient@" + s.GetDomain()},
|
|
bytes.NewReader(b),
|
|
))
|
|
|
|
// Delete the draft: add the \Deleted flag and expunge.
|
|
{
|
|
status, err := imapClient.Select("Drafts", false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint32(1), status.Messages)
|
|
|
|
// Add the \Deleted flag.
|
|
require.NoError(t, clientStore(imapClient, 1, 1, true, imap.FormatFlagsOp(imap.AddFlags, true), imap.DeletedFlag))
|
|
|
|
// Expunge.
|
|
require.NoError(t, imapClient.Expunge(nil))
|
|
}
|
|
|
|
// Assert that the draft is eventually gone.
|
|
require.Eventually(t, func() bool {
|
|
status, err := imapClient.Select("Drafts", false)
|
|
require.NoError(t, err)
|
|
return status.Messages == 0
|
|
}, 10*time.Second, 100*time.Millisecond)
|
|
|
|
// Assert that the message is eventually in the sent folder.
|
|
require.Eventually(t, func() bool {
|
|
messages, err := clientFetch(imapClient, "Sent")
|
|
require.NoError(t, err)
|
|
return len(messages) == 1
|
|
}, 10*time.Second, 100*time.Millisecond)
|
|
|
|
// Assert that the message is not marked as a draft.
|
|
{
|
|
messages, err := clientFetch(imapClient, "Sent")
|
|
require.NoError(t, err)
|
|
require.Len(t, messages, 1)
|
|
require.NotContains(t, messages[0].Flags, imap.DraftFlag)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestBridge_SendAddTextBodyPartIfNotExists(t *testing.T) {
|
|
const messageMultipartWithoutText = `Content-Type: multipart/mixed;
|
|
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
|
Subject: A new message
|
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
|
|
|
|
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
|
Content-Disposition: inline;
|
|
filename=Cat_August_2010-4.jpeg
|
|
Content-Type: image/jpeg;
|
|
name="Cat_August_2010-4.jpeg"
|
|
Content-Transfer-Encoding: base64
|
|
|
|
SGVsbG8gd29ybGQ=
|
|
|
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
|
|
`
|
|
|
|
const messageMultipartWithText = `Content-Type: multipart/mixed;
|
|
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
|
Subject: A new message Part2
|
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
|
|
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
|
Content-Disposition: inline;
|
|
filename=Cat_August_2010-4.jpeg
|
|
Content-Type: image/jpeg;
|
|
name="Cat_August_2010-4.jpeg"
|
|
Content-Transfer-Encoding: base64
|
|
|
|
SGVsbG8gd29ybGQ=
|
|
|
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
|
Content-Type: text/html;charset=utf8
|
|
Content-Transfer-Encoding: quoted-printable
|
|
|
|
Hello world
|
|
|
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
|
|
`
|
|
|
|
const messageWithTextOnly = `Content-Type: text/plain;charset=utf8
|
|
Content-Transfer-Encoding: quoted-printable
|
|
Subject: A new message Part3
|
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
|
|
|
Hello world
|
|
|
|
`
|
|
|
|
const messageMultipartWithoutTextWithTextAttachment = `Content-Type: multipart/mixed;
|
|
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
|
Subject: A new message Part4
|
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
|
|
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
|
Content-Type: text/plain; charset=UTF-8; name="text.txt"
|
|
Content-Disposition: attachment; filename="text.txt"
|
|
Content-Transfer-Encoding: base64
|
|
|
|
SGVsbG8gd29ybGQK
|
|
|
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
|
|
`
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
|
_, _, err := s.CreateUser("recipient", password)
|
|
require.NoError(t, err)
|
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
|
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
defer smtpWaiter.Done()
|
|
|
|
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
recipientUserID, err := bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
senderInfo, err := bridge.GetUserInfo(senderUserID)
|
|
require.NoError(t, err)
|
|
|
|
recipientInfo, err := bridge.GetUserInfo(recipientUserID)
|
|
require.NoError(t, err)
|
|
|
|
messages := []string{
|
|
messageMultipartWithoutText,
|
|
messageMultipartWithText,
|
|
messageWithTextOnly,
|
|
messageMultipartWithoutTextWithTextAttachment,
|
|
}
|
|
|
|
smtpWaiter.Wait()
|
|
|
|
for _, m := range messages {
|
|
// Dial the server.
|
|
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
|
require.NoError(t, err)
|
|
defer client.Close() //nolint:errcheck
|
|
|
|
// Upgrade to TLS.
|
|
require.NoError(t, client.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
|
|
|
// Authorize with SASL LOGIN.
|
|
require.NoError(t, client.Auth(sasl.NewLoginClient(
|
|
senderInfo.Addresses[0],
|
|
string(senderInfo.BridgePass)),
|
|
))
|
|
|
|
// Send the message.
|
|
require.NoError(t, client.SendMail(
|
|
senderInfo.Addresses[0],
|
|
[]string{recipientInfo.Addresses[0]},
|
|
strings.NewReader(m),
|
|
))
|
|
}
|
|
|
|
// Connect the sender IMAP client.
|
|
senderIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
|
require.NoError(t, err)
|
|
require.NoError(t, senderIMAPClient.Login(senderInfo.Addresses[0], string(senderInfo.BridgePass)))
|
|
defer senderIMAPClient.Logout() //nolint:errcheck
|
|
|
|
// Connect the recipient IMAP client.
|
|
recipientIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
|
require.NoError(t, err)
|
|
require.NoError(t, recipientIMAPClient.Login(recipientInfo.Addresses[0], string(recipientInfo.BridgePass)))
|
|
defer recipientIMAPClient.Logout() //nolint:errcheck
|
|
|
|
require.Eventually(t, func() bool {
|
|
messages, err := clientFetch(senderIMAPClient, `Sent`, imap.FetchBodyStructure)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 4, len(messages))
|
|
|
|
// messages may not be in order
|
|
for _, message := range messages {
|
|
switch {
|
|
case message.Envelope.Subject == "A new message":
|
|
// The message that was sent should now include an empty text/plain body part since there was none
|
|
// in the original message.
|
|
require.Equal(t, 2, len(message.BodyStructure.Parts))
|
|
|
|
require.Equal(t, "text", message.BodyStructure.Parts[0].MIMEType)
|
|
require.Equal(t, "plain", message.BodyStructure.Parts[0].MIMESubType)
|
|
require.Equal(t, uint32(0), message.BodyStructure.Parts[0].Size)
|
|
require.Equal(t, "image", message.BodyStructure.Parts[1].MIMEType)
|
|
require.Equal(t, "jpeg", message.BodyStructure.Parts[1].MIMESubType)
|
|
|
|
case message.Envelope.Subject == "A new message Part2":
|
|
// This message already has a text body, should be unchanged
|
|
require.Equal(t, 2, len(message.BodyStructure.Parts))
|
|
|
|
require.Equal(t, "image", message.BodyStructure.Parts[1].MIMEType)
|
|
require.Equal(t, "jpeg", message.BodyStructure.Parts[1].MIMESubType)
|
|
require.Equal(t, "text", message.BodyStructure.Parts[0].MIMEType)
|
|
require.Equal(t, "html", message.BodyStructure.Parts[0].MIMESubType)
|
|
|
|
case message.Envelope.Subject == "A new message Part3":
|
|
// This message already has a text body, should be unchanged
|
|
require.Equal(t, 0, len(message.BodyStructure.Parts))
|
|
|
|
require.Equal(t, "text", message.BodyStructure.MIMEType)
|
|
require.Equal(t, "plain", message.BodyStructure.MIMESubType)
|
|
|
|
case message.Envelope.Subject == "A new message Part4":
|
|
// The message that was sent should now include an empty text/plain body part since even though
|
|
// there was only a text/plain attachment in the original message.
|
|
require.Equal(t, 2, len(message.BodyStructure.Parts))
|
|
|
|
require.Equal(t, "text", message.BodyStructure.Parts[0].MIMEType)
|
|
require.Equal(t, "plain", message.BodyStructure.Parts[0].MIMESubType)
|
|
require.Equal(t, uint32(0), message.BodyStructure.Parts[0].Size)
|
|
require.Equal(t, "text", message.BodyStructure.Parts[1].MIMEType)
|
|
require.Equal(t, "plain", message.BodyStructure.Parts[1].MIMESubType)
|
|
require.Equal(t, "attachment", message.BodyStructure.Parts[1].Disposition)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}, 10*time.Second, 100*time.Millisecond)
|
|
})
|
|
})
|
|
}
|