forked from Silverfish/proton-bridge
GODT-1136 DB Cache header from builder and test
This commit is contained in:
@ -18,20 +18,51 @@
|
||||
package fakeapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"net/textproto"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
// dataPacketOutlineLightInstagram48png is data packet with encrypted data and
|
||||
// session key
|
||||
//
|
||||
// gpg: encrypted with 2048-bit RSA key, ID 70B8CA23079F2167, created 2019-09-23
|
||||
// "james-test@protonmail.blue <james-test@protonmail.blue>"
|
||||
//
|
||||
// If you need to rebuild you can dump KeyPacket string from `CreateAttachment`
|
||||
// function when called during message sending test.
|
||||
const dataPacketOutlineLightInstagram48png = `wcBMA3C4yiMHnyFnAQgAnH7Qs4lvnbpwdFh5fTgJVTwKZnoLHcbukczf1dld1h9+83wv1FUNosy08KAX3IbDPGnFf5hMTGyEvEcNI0/2HWgootPPCWVvHKfKjjBmstUxbBgRJWi35bBz0WzMarB4BWM8xO2ffqrylhgUhBdK5c26qZvU6veq4cfkKA4SFT9fld8KHY+Ph+v1Q3lP5p3lLX0CyH0CtX7HxdPXk30J+HkugyYOcQ/2upiGzmnIobmZE9kedEA5CWNQa1gxoQTzuxDOzYRZFQPidywMj8pOPCK+1825O/8IeIL/NbXpb1qEW0roOAjYVO/NQzWyWSuIOc6F/Y+ZxzpjhRhe8son+tLBuQEm1rzWfj5Yg4x56fkRPCtfNKaIfYCGEexQNXZWX8zMxjDWM93JDjLeb0C5FzlY2tME2+zYM/SegzFHiagWlZROgQvoiPxcl67FbcOd1YrNP63gw1dwBt4tg1kbwlcSQ76cHeJ6r/Sjg2Q2v52Ee7h3K5Q2h8UDCcgIfTrS4SAKbWjPRRypMJXBp+GH6LASD4m/A6cjjcuOg5Ssh2KaKjGsYrrHVnplmWfKZ18/OrFYSHKxytiIHrLd3GE7SxbyI6LvfKxa5QAKbPBxL39FjcryaJ9l7iWI63zYhOS6bi6fRWLCq6vK30kPvgn+mivGtAxLfSrgAlODmXPxBM4ZIVQNxYv35Fhxk/ENEvRALRv4Lmdv+lHwK1DdtAY/XozkghpLG9ySThqoktK16BKtPeCAEjktlRUt8x/F3Nl5IhOCxxt21xTnne4erz4g6HtCy1Q4fblJTi5iS4C27MdDAPZLZYxz70vxuoeXK4g61pPNVaYNLRE6vdlQUM0YYy58pHarW1+YQ54HXT5eFP/wz+xvNDW/2RCkvIBxioxiTiJNRzEtfivYGowImkxJkMlhv6g9i17Dz5ANca9y4hEKi+u55drRn1Nfikw8c8JmopFELF1eQ1gbDYdb4X40qV1AApTEH1hRYNZeaMwmLkGDvBUDy99p0i6+BTO/nr9wghRCyxv9urDvNMbGxLnbNq2wdNtYdR4UurjmrpyctqyeaERxUbHhGFqAlkRGbd8ZOFHWrCVpKKYihptSbITZnK4d1vJ7IKu/+nIRI9mwN5IRnJjVo31AvzWpSGnJDtidAjAHeIVwrKk/eONETXwBXuAaVUd8PuMJCv4xuw==`
|
||||
|
||||
func newTestAttachment(iAtt int, msgID string) *pmapi.Attachment {
|
||||
attID := fmt.Sprintf("attID%d", iAtt)
|
||||
attContentID := fmt.Sprintf("<%s@protonmail.com>", attID)
|
||||
return &pmapi.Attachment{
|
||||
ID: attID,
|
||||
MessageID: msgID,
|
||||
Name: "outline-light-instagram-48.png",
|
||||
MIMEType: "image/png",
|
||||
Disposition: "attachment",
|
||||
ContentID: attContentID,
|
||||
Header: textproto.MIMEHeader{},
|
||||
KeyPackets: dataPacketOutlineLightInstagram48png,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) GetAttachment(attachmentID string) (io.ReadCloser, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/mail/v4/attachments/"+attachmentID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := strings.NewReader("data")
|
||||
return ioutil.NopCloser(data), nil
|
||||
b, err := base64.StdEncoding.DecodeString(dataPacketOutlineLightInstagram48png)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := bytes.NewReader(b)
|
||||
return ioutil.NopCloser(r), nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CreateAttachment(attachment *pmapi.Attachment, data io.Reader, signature io.Reader) (*pmapi.Attachment, error) {
|
||||
|
||||
@ -142,6 +142,11 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) (
|
||||
message.ID = ctl.messageIDGenerator.next("")
|
||||
message.LabelIDs = append(message.LabelIDs, pmapi.AllMailLabel)
|
||||
message.Header = mail.Header(messageUtils.GetHeader(message))
|
||||
|
||||
for iAtt := 0; iAtt < message.NumAttachments; iAtt++ {
|
||||
message.Attachments = append(message.Attachments, newTestAttachment(iAtt, message.ID))
|
||||
}
|
||||
|
||||
ctl.messagesByUsername[username] = append(ctl.messagesByUsername[username], message)
|
||||
ctl.resetUsers()
|
||||
return message.ID, nil
|
||||
|
||||
@ -63,8 +63,8 @@ Feature: IMAP fetch messages
|
||||
|
||||
Scenario: Fetch of very old message sent from the moon succeeds with modified date
|
||||
Given there are messages in mailbox "Folders/mbox" for "user"
|
||||
| from | to | subject | time |
|
||||
| john.doe@mail.com | user@pm.me | foo | 1969-07-20T00:00:00 |
|
||||
| from | to | subject | time |
|
||||
| john.doe@mail.com | user@pm.me | Very old email | 1969-07-20T00:00:00 |
|
||||
And there is IMAP client logged in as "user"
|
||||
And there is IMAP client selected in "Folders/mbox"
|
||||
When IMAP client sends command "FETCH 1:* rfc822"
|
||||
|
||||
41
test/features/bridge/imap/message/fetch_header.feature
Normal file
41
test/features/bridge/imap/message/fetch_header.feature
Normal file
@ -0,0 +1,41 @@
|
||||
Feature: IMAP fetch header of message
|
||||
Background: Fetch header deterministic content type and boundary
|
||||
Given there is connected user "user"
|
||||
And there are messages in mailbox "INBOX" for "user"
|
||||
| id | from | to | subject | n attachments | content type | body |
|
||||
| 1 | f@m.co | t@pm.me | A message with attachment | 2 | html | body |
|
||||
| 2 | f@m.co | t@pm.me | A simple html message | 0 | html | body |
|
||||
| 3 | f@m.co | t@pm.me | A simple plain message | 0 | plain | body |
|
||||
# | 4 | f@m.co | t@pm.me | An externally encrypted message | 0 | mixed | body |
|
||||
# | 5 | f@m.co | t@pm.me | A simple plain message in latin1 | 0 | plain-latin1 | body |
|
||||
|
||||
And there is IMAP client logged in as "user"
|
||||
And there is IMAP client selected in "INBOX"
|
||||
|
||||
@ignore-live
|
||||
Scenario Outline: Fetch header deterministic content type and boundary
|
||||
Given header is not cached for message "<id>" in "INBOX" for "user"
|
||||
# First time need to download and cache
|
||||
When IMAP client fetches header of "<id>"
|
||||
Then IMAP response is "OK"
|
||||
And IMAP response contains "Content-Type: <contentType>"
|
||||
And IMAP response contains "<parameter>"
|
||||
And header is cached for message "<id>" in "INBOX" for "user"
|
||||
# Second time it's taken from imap cache
|
||||
When IMAP client fetches body "<id>"
|
||||
Then IMAP response is "OK"
|
||||
And IMAP response contains "Content-Type: <contentType>"
|
||||
And IMAP response contains "<parameter>"
|
||||
# Third time header taken from DB
|
||||
When IMAP client fetches header of "<id>"
|
||||
Then IMAP response is "OK"
|
||||
And IMAP response contains "Content-Type: <contentType>"
|
||||
And IMAP response contains "<parameter>"
|
||||
|
||||
Examples:
|
||||
| id | contentType | parameter |
|
||||
| 1 | multipart/mixed | boundary=4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce |
|
||||
| 2 | text/html | charset=utf-8 |
|
||||
| 3 | text/plain | charset=utf-8 |
|
||||
|
||||
|
||||
@ -30,6 +30,8 @@ import (
|
||||
func IMAPActionsMessagesFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^IMAP client sends command "([^"]*)"$`, imapClientSendsCommand)
|
||||
s.Step(`^IMAP client fetches "([^"]*)"$`, imapClientFetches)
|
||||
s.Step(`^IMAP client fetches header(?:s)? of "([^"]*)"$`, imapClientFetchesHeader)
|
||||
s.Step(`^IMAP client fetches body "([^"]*)"$`, imapClientFetchesBody)
|
||||
s.Step(`^IMAP client fetches by UID "([^"]*)"$`, imapClientFetchesByUID)
|
||||
s.Step(`^IMAP client searches for "([^"]*)"$`, imapClientSearchesFor)
|
||||
s.Step(`^IMAP client copies message seq "([^"]*)" to "([^"]*)"$`, imapClientCopiesMessagesTo)
|
||||
@ -76,6 +78,18 @@ func imapClientFetches(fetchRange string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func imapClientFetchesHeader(fetchRange string) error {
|
||||
res := ctx.GetIMAPClient("imap").Fetch(fetchRange, "BODY.PEEK[HEADER]")
|
||||
ctx.SetIMAPLastResponse("imap", res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func imapClientFetchesBody(fetchRange string) error {
|
||||
res := ctx.GetIMAPClient("imap").Fetch(fetchRange, "BODY.PEEK[]")
|
||||
ctx.SetIMAPLastResponse("imap", res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func imapClientFetchesByUID(fetchRange string) error {
|
||||
res := ctx.GetIMAPClient("imap").FetchUID(fetchRange, "UID")
|
||||
ctx.SetIMAPLastResponse("imap", res)
|
||||
|
||||
@ -30,6 +30,10 @@ import (
|
||||
)
|
||||
|
||||
func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) (string, error) {
|
||||
if message.NumAttachments != 0 {
|
||||
return "", errors.New("add user messages with attachments is not implemented for live")
|
||||
}
|
||||
|
||||
client, ok := ctl.pmapiByUsername[username]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("user %s does not exist", username)
|
||||
|
||||
@ -46,6 +46,8 @@ func StoreChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^message "([^"]*)" in "([^"]*)" for "([^"]*)" is marked as unstarred$`, messagesInMailboxForUserIsMarkedAsUnstarred)
|
||||
s.Step(`^message "([^"]*)" in "([^"]*)" for "([^"]*)" is marked as deleted$`, messagesInMailboxForUserIsMarkedAsDeleted)
|
||||
s.Step(`^message "([^"]*)" in "([^"]*)" for "([^"]*)" is marked as undeleted$`, messagesInMailboxForUserIsMarkedAsUndeleted)
|
||||
s.Step(`^header is not cached for message "([^"]*)" in "([^"]*)" for "([^"]*)"$`, messageHeaderIsNotCached)
|
||||
s.Step(`^header is cached for message "([^"]*)" in "([^"]*)" for "([^"]*)"$`, messageHeaderIsCached)
|
||||
}
|
||||
|
||||
func userHasMailbox(bddUserID, mailboxName string) error {
|
||||
@ -260,7 +262,7 @@ func messagesContainsMessageRow(account *accounts.TestAccount, allMessages []int
|
||||
if message.Unread != unread {
|
||||
matches = false
|
||||
}
|
||||
case "deleted":
|
||||
case "deleted": //nolint[goconst]
|
||||
if storeMessage == nil {
|
||||
return false, fmt.Errorf("deleted column not supported for pmapi message object")
|
||||
}
|
||||
@ -345,6 +347,24 @@ func messagesInMailboxForUserIsMarkedAsUndeleted(bddMessageIDs, mailboxName, bdd
|
||||
})
|
||||
}
|
||||
|
||||
func messageHeaderIsNotCached(bddMessageIDs, mailboxName, bddUserID string) error {
|
||||
return checkMessages(bddUserID, mailboxName, bddMessageIDs, func(message *store.Message) error {
|
||||
if !message.IsFullHeaderCached() {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("message %s \"%s\" is expected to not have cached header but has one", message.ID(), message.Message().Subject)
|
||||
})
|
||||
}
|
||||
|
||||
func messageHeaderIsCached(bddMessageIDs, mailboxName, bddUserID string) error {
|
||||
return checkMessages(bddUserID, mailboxName, bddMessageIDs, func(message *store.Message) error {
|
||||
if message.IsFullHeaderCached() {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("message %s \"%s\" is expected to have cached header but it doesn't have", message.ID(), message.Message().Subject)
|
||||
})
|
||||
}
|
||||
|
||||
func checkMessages(bddUserID, mailboxName, bddMessageIDs string, callback func(*store.Message) error) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
|
||||
@ -94,7 +94,7 @@ func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bdd
|
||||
for i := len(messages.Rows) - 1; i > 0; i-- {
|
||||
row := messages.Rows[i]
|
||||
message := &pmapi.Message{
|
||||
MIMEType: "text/plain",
|
||||
MIMEType: pmapi.ContentTypePlainText,
|
||||
LabelIDs: labelIDs,
|
||||
AddressID: account.AddressID(),
|
||||
}
|
||||
@ -108,48 +108,16 @@ func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bdd
|
||||
hasDeletedFlag := false
|
||||
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "id":
|
||||
column := head[n].Value
|
||||
if column == "id" {
|
||||
bddMessageID = cell.Value
|
||||
case "from":
|
||||
message.Sender = &mail.Address{
|
||||
Address: ctx.EnsureAddress(account.Username(), cell.Value),
|
||||
}
|
||||
case "to":
|
||||
message.AddressID = ctx.EnsureAddressID(account.Username(), cell.Value)
|
||||
message.ToList = []*mail.Address{{
|
||||
Address: ctx.EnsureAddress(account.Username(), cell.Value),
|
||||
}}
|
||||
case "cc":
|
||||
message.AddressID = ctx.EnsureAddressID(account.Username(), cell.Value)
|
||||
message.CCList = []*mail.Address{{
|
||||
Address: ctx.EnsureAddress(account.Username(), cell.Value),
|
||||
}}
|
||||
case "subject":
|
||||
message.Subject = cell.Value
|
||||
case "body":
|
||||
message.Body = cell.Value
|
||||
case "read":
|
||||
unread := 1
|
||||
if cell.Value == "true" {
|
||||
unread = 0
|
||||
}
|
||||
message.Unread = unread
|
||||
case "starred":
|
||||
if cell.Value == "true" {
|
||||
message.LabelIDs = append(message.LabelIDs, "10")
|
||||
}
|
||||
case "time": //nolint[goconst]
|
||||
date, err := time.Parse(timeFormat, cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "parsing time")
|
||||
}
|
||||
message.Time = date.Unix()
|
||||
header.Set("Date", date.Format(time.RFC1123Z))
|
||||
case "deleted":
|
||||
}
|
||||
if column == "deleted" {
|
||||
hasDeletedFlag = cell.Value == "true"
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
err := processMessageTableCell(column, cell.Value, account.Username(), message, header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
message.Header = mail.Header(header)
|
||||
@ -183,6 +151,64 @@ func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bdd
|
||||
return nil
|
||||
}
|
||||
|
||||
func processMessageTableCell(column, cellValue, username string, message *pmapi.Message, header textproto.MIMEHeader) error {
|
||||
switch column {
|
||||
case "deleted", "id": // it is processed in the main function
|
||||
case "from":
|
||||
message.Sender = &mail.Address{
|
||||
Address: ctx.EnsureAddress(username, cellValue),
|
||||
}
|
||||
case "to":
|
||||
message.AddressID = ctx.EnsureAddressID(username, cellValue)
|
||||
message.ToList = []*mail.Address{{
|
||||
Address: ctx.EnsureAddress(username, cellValue),
|
||||
}}
|
||||
case "cc":
|
||||
message.AddressID = ctx.EnsureAddressID(username, cellValue)
|
||||
message.CCList = []*mail.Address{{
|
||||
Address: ctx.EnsureAddress(username, cellValue),
|
||||
}}
|
||||
case "subject":
|
||||
message.Subject = cellValue
|
||||
case "body":
|
||||
message.Body = cellValue
|
||||
case "read":
|
||||
unread := 1
|
||||
if cellValue == "true" {
|
||||
unread = 0
|
||||
}
|
||||
message.Unread = unread
|
||||
case "starred":
|
||||
if cellValue == "true" {
|
||||
message.LabelIDs = append(message.LabelIDs, "10")
|
||||
}
|
||||
case "time": //nolint[goconst] It is more easy to read like this
|
||||
date, err := time.Parse(timeFormat, cellValue)
|
||||
if err != nil {
|
||||
return internalError(err, "parsing time")
|
||||
}
|
||||
message.Time = date.Unix()
|
||||
header.Set("Date", date.Format(time.RFC1123Z))
|
||||
case "n attachments":
|
||||
numAttachments, err := strconv.Atoi(cellValue)
|
||||
if err != nil {
|
||||
return internalError(err, "parsing n attachments")
|
||||
}
|
||||
message.NumAttachments = numAttachments
|
||||
case "content type":
|
||||
switch cellValue {
|
||||
case "html":
|
||||
message.MIMEType = pmapi.ContentTypeHTML
|
||||
case "plain":
|
||||
message.MIMEType = pmapi.ContentTypePlainText
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", column)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func thereAreSomeMessagesInMailboxesForUser(numberOfMessages int, mailboxNames, bddUserID string) error {
|
||||
return thereAreSomeMessagesInMailboxesForAddressOfUser(numberOfMessages, mailboxNames, "", bddUserID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user