GODT-1136 DB Cache header from builder and test

This commit is contained in:
Jakub
2021-04-13 07:49:13 +02:00
committed by James Houlahan
parent 454d248819
commit 8ab05a000c
21 changed files with 471 additions and 306 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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"

View 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 |

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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)
}