From 8049c47aa821514098db06668d663958c65b817e Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Mon, 14 Nov 2022 13:33:54 +0100 Subject: [PATCH] GODT-1989: Handle Move with Append and Expunge Resurrect Bridge feature test for move with append. Only for drafts, as it has been established that moving/appending to All Mail is no longer valid. --- tests/bdd_test.go | 1 + .../imap/message/move_without_support.feature | 64 +++++++++++++ tests/imap_test.go | 95 +++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 tests/features/imap/message/move_without_support.feature diff --git a/tests/bdd_test.go b/tests/bdd_test.go index 5e60020c..8073172f 100644 --- a/tests/bdd_test.go +++ b/tests/bdd_test.go @@ -178,6 +178,7 @@ func TestFeatures(testingT *testing.T) { ctx.Step(`^IMAP client "([^"]*)" appends the following message to "([^"]*)":$`, s.imapClientAppendsTheFollowingMessageToMailbox) ctx.Step(`^IMAP client "([^"]*)" appends the following messages to "([^"]*)":$`, s.imapClientAppendsTheFollowingMessagesToMailbox) ctx.Step(`^IMAP client "([^"]*)" appends "([^"]*)" to "([^"]*)"$`, s.imapClientAppendsToMailbox) + ctx.Step(`^IMAP clients "([^"]*)" and "([^"]*)" move message seq "([^"]*)" of "([^"]*)" to "([^"]*)" by ([^"]*) ([^"]*) ([^"]*)`, s.imapClientsMoveMessageSeqOfUserFromToByOrderedOperations) // ==== SMTP ==== ctx.Step(`^user "([^"]*)" connects SMTP client "([^"]*)"$`, s.userConnectsSMTPClient) diff --git a/tests/features/imap/message/move_without_support.feature b/tests/features/imap/message/move_without_support.feature new file mode 100644 index 00000000..d29d919a --- /dev/null +++ b/tests/features/imap/message/move_without_support.feature @@ -0,0 +1,64 @@ +Feature: IMAP move messages by append and delete (without MOVE support, e.g., Outlook) + Background: + Given there exists an account with username "user@pm.me" and password "password" + And the account "user@pm.me" has the following custom mailboxes: + | name | type | + | mbox | folder | + And bridge starts + And the user logs in with username "user@pm.me" and password "password" + And user "user@pm.me" finishes syncing + And user "user@pm.me" connects and authenticates IMAP client "source" + And user "user@pm.me" connects and authenticates IMAP client "target" + + Scenario Outline: Move message from to by + When IMAP client "source" appends the following message to "": + """ + Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000 + From: sndr1@pm.me + To: rcvr1@pm.me + Subject: subj1 + + body1 + """ + Then it succeeds + When IMAP client "source" appends the following message to "": + """ + Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000 + From: sndr2@pm.me + To: rcvr2@pm.me + Subject: subj2 + + body2 + """ + Then it succeeds + And IMAP client "source" selects "" + And IMAP client "target" selects "" + When IMAP clients "source" and "target" move message seq "2" of "user" to "" by + And IMAP client "source" sees 1 messages in "" + And IMAP client "source" sees the following messages in "": + | from | to | subject | + | sndr1@pm.me | rcvr1@pm.me | subj1 | + And IMAP client "target" sees 1 messages in "" + And IMAP client "target" sees the following messages in "": + | from | to | subject | + | sndr2@pm.me | rcvr2@pm.me | subj2 | + Examples: + | srcMailbox | dstMailbox | order | + | Trash | INBOX | APPEND DELETE EXPUNGE | + | Spam | INBOX | APPEND DELETE EXPUNGE | + | INBOX | Archive | APPEND DELETE EXPUNGE | + | INBOX | Folders/mbox | APPEND DELETE EXPUNGE | + | INBOX | Spam | APPEND DELETE EXPUNGE | + | INBOX | Trash | APPEND DELETE EXPUNGE | + | Trash | INBOX | DELETE APPEND EXPUNGE | + | Spam | INBOX | DELETE APPEND EXPUNGE | + | INBOX | Archive | DELETE APPEND EXPUNGE | + | INBOX | Folders/mbox | DELETE APPEND EXPUNGE | + | INBOX | Spam | DELETE APPEND EXPUNGE | + | INBOX | Trash | DELETE APPEND EXPUNGE | + | Trash | INBOX | DELETE EXPUNGE APPEND | + | Spam | INBOX | DELETE EXPUNGE APPEND | + | INBOX | Archive | DELETE EXPUNGE APPEND | + | INBOX | Folders/mbox | DELETE EXPUNGE APPEND | + | INBOX | Spam | DELETE EXPUNGE APPEND | + | INBOX | Trash | DELETE EXPUNGE APPEND | diff --git a/tests/imap_test.go b/tests/imap_test.go index 29b99492..0b92f8ed 100644 --- a/tests/imap_test.go +++ b/tests/imap_test.go @@ -18,9 +18,12 @@ package tests import ( + "bytes" "fmt" + "io" "os" "path/filepath" + "strconv" "strings" "time" @@ -408,6 +411,77 @@ func (s *scenario) imapClientAppendsToMailbox(clientID string, file, mailbox str return clientAppend(client, mailbox, string(b)) } +func (s *scenario) imapClientsMoveMessageSeqOfUserFromToByOrderedOperations(sourceIMAPClient, targetIMAPClient, messageSeq, bddUserID, targetMailboxName, op1, op2, op3 string) error { + // call NOOP to prevent unilateral updates in following FETCH + _, sourceClient := s.t.getIMAPClient(sourceIMAPClient) + _, targetClient := s.t.getIMAPClient(targetIMAPClient) + + sequenceID, err := strconv.Atoi(messageSeq) + if err != nil { + return err + } + + if err := sourceClient.Noop(); err != nil { + return err + } + + if err := targetClient.Noop(); err != nil { + return err + } + + // get the original message + messages, err := clientFetchSequence(sourceClient, messageSeq) + if err != nil { + return err + } + + if len(messages) != 1 { + return fmt.Errorf("more than one message in sequence set") + } + + bodySection, err := imap.ParseBodySectionName("BODY[]") + if err != nil { + return err + } + + literal, err := io.ReadAll(messages[0].GetBody(bodySection)) + if err != nil { + return err + } + + var targetErr error + var storeErr error + var expungeErr error + + for _, op := range []string{op1, op2, op3} { + switch op { + case "APPEND": + + flags := messages[0].Flags + if index := xslices.Index(flags, imap.RecentFlag); index >= 0 { + flags = xslices.Remove(flags, index, 1) + } + + targetErr = targetClient.Append(targetMailboxName, flags, time.Now(), bytes.NewReader(literal)) + case "DELETE": + if _, err := clientStore(sourceClient, sequenceID, sequenceID, false, imap.FormatFlagsOp(imap.AddFlags, true), imap.DeletedFlag); err != nil { + storeErr = err + } + case "EXPUNGE": + expungeErr = sourceClient.Expunge(nil) + default: + return fmt.Errorf("unknown IMAP operation " + op) + } + time.Sleep(100 * time.Millisecond) + } + + if targetErr != nil || storeErr != nil || expungeErr != nil { + return fmt.Errorf("one or more operations failed: append=%v store=%v expunge=%v", targetErr, storeErr, expungeErr) + } + + return nil +} + func clientList(client *client.Client) []*imap.MailboxInfo { resCh := make(chan *imap.MailboxInfo) @@ -477,6 +551,27 @@ func clientFetch(client *client.Client, mailbox string) ([]*imap.Message, error) return iterator.Collect(iterator.Chan(resCh)), nil } +func clientFetchSequence(client *client.Client, sequenceSet string) ([]*imap.Message, error) { + seqSet, err := imap.ParseSeqSet(sequenceSet) + if err != nil { + return nil, err + } + + resCh := make(chan *imap.Message) + + go func() { + if err := client.Fetch( + seqSet, + []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid, "BODY.PEEK[]"}, + resCh, + ); err != nil { + panic(err) + } + }() + + return iterator.Collect(iterator.Chan(resCh)), nil +} + func clientCopy(client *client.Client, from, to string, uid ...uint32) error { status, err := client.Select(from, false) if err != nil {