diff --git a/go.mod b/go.mod index 10e7b1dc..d88a0d8f 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.0 github.com/urfave/cli/v2 v2.16.3 - gitlab.protontech.ch/go/liteapi v0.32.1-0.20221004092920-6b728aed0d4d + gitlab.protontech.ch/go/liteapi v0.32.1-0.20221004164551-596cce482fb0 golang.org/x/exp v0.0.0-20220921164117-439092de6870 golang.org/x/net v0.1.0 golang.org/x/sys v0.1.0 @@ -131,4 +131,5 @@ replace ( github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe + gitlab.protontech.ch/go/liteapi => ../liteapi ) diff --git a/go.sum b/go.sum index 16c634db..5d070cfc 100644 --- a/go.sum +++ b/go.sum @@ -463,8 +463,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= -gitlab.protontech.ch/go/liteapi v0.32.1-0.20221004092920-6b728aed0d4d h1:2CB6po0yWmgb0bVCylvQlQph6a6Hk/Uziq5eHg0ZCfo= -gitlab.protontech.ch/go/liteapi v0.32.1-0.20221004092920-6b728aed0d4d/go.mod h1:SVxEeF4uYYYpSlfeAj2ZqluVEP95pbZ8LyoieSxU0pM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= diff --git a/tests/diff.go b/tests/diff.go index da2613ab..985d231d 100644 --- a/tests/diff.go +++ b/tests/diff.go @@ -1,11 +1,27 @@ package tests import ( + "encoding/json" "reflect" "github.com/bradenaw/juniper/xslices" ) +func ToAny(v any) any { + b, err := json.Marshal(v) + if err != nil { + panic(err) + } + + var a any + + if err := json.Unmarshal(b, &a); err != nil { + panic(err) + } + + return a +} + func IsSub(outer, inner any) bool { if outer == nil && inner != nil { return IsSub(reflect.Zero(reflect.TypeOf(inner)).Interface(), inner) @@ -30,10 +46,6 @@ func IsSub(outer, inner any) bool { return false } - if len(inner) != len(outer) { - return false - } - return isSubSlice(outer, inner) default: @@ -69,6 +81,10 @@ func isSubMap(outer, inner map[string]any) bool { } func isSubSlice(outer, inner []any) bool { + if len(inner) != len(outer) { + return false + } + for _, v := range inner { if xslices.IndexFunc(outer, func(outer any) bool { return IsSub(outer, v) diff --git a/tests/features/imap/mailbox/info.feature b/tests/features/imap/mailbox/info.feature index fb16caa4..6ab1b700 100644 --- a/tests/features/imap/mailbox/info.feature +++ b/tests/features/imap/mailbox/info.feature @@ -5,7 +5,7 @@ Feature: IMAP get mailbox info | name | type | | one | folder | And the address "user@pm.me" of account "user@pm.me" has the following messages in "one": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | And bridge starts diff --git a/tests/features/imap/message/copy.feature b/tests/features/imap/message/copy.feature index 9130b8bc..b6eeff4a 100644 --- a/tests/features/imap/message/copy.feature +++ b/tests/features/imap/message/copy.feature @@ -6,7 +6,7 @@ Feature: IMAP copy messages | mbox | folder | | label | label | And the address "user@pm.me" of account "user@pm.me" has the following messages in "Inbox": - | sender | recipient | subject | unread | + | from | to | subject | unread | | john.doe@mail.com | user@pm.me | foo | false | | jane.doe@mail.com | name@pm.me | bar | true | And bridge starts @@ -17,37 +17,37 @@ Feature: IMAP copy messages Scenario: Copy message to label When IMAP client "1" copies the message with subject "foo" from "INBOX" to "Labels/label" Then IMAP client "1" sees the following messages in "INBOX": - | sender | recipient | subject | unread | + | from | to | subject | unread | | john.doe@mail.com | user@pm.me | foo | false | | jane.doe@mail.com | name@pm.me | bar | true | And IMAP client "1" sees the following messages in "Labels/label": - | sender | recipient | subject | unread | + | from | to | subject | unread | | john.doe@mail.com | user@pm.me | foo | false | Scenario: Copy all messages to label When IMAP client "1" copies all messages from "INBOX" to "Labels/label" Then IMAP client "1" sees the following messages in "INBOX": - | sender | recipient | subject | unread | + | from | to | subject | unread | | john.doe@mail.com | user@pm.me | foo | false | | jane.doe@mail.com | name@pm.me | bar | true | And IMAP client "1" sees the following messages in "Labels/label": - | sender | recipient | subject | unread | + | from | to | subject | unread | | john.doe@mail.com | user@pm.me | foo | false | | jane.doe@mail.com | name@pm.me | bar | true | Scenario: Copy message to folder does move When IMAP client "1" copies the message with subject "foo" from "INBOX" to "Folders/mbox" Then IMAP client "1" eventually sees the following messages in "INBOX": - | sender | recipient | subject | unread | + | from | to | subject | unread | | jane.doe@mail.com | name@pm.me | bar | true | And IMAP client "1" sees the following messages in "Folders/mbox": - | sender | recipient | subject | unread | + | from | to | subject | unread | | john.doe@mail.com | user@pm.me | foo | false | Scenario: Copy all messages to folder does move When IMAP client "1" copies all messages from "INBOX" to "Folders/mbox" Then IMAP client "1" sees the following messages in "Folders/mbox": - | sender | recipient | subject | unread | + | from | to | subject | unread | | john.doe@mail.com | user@pm.me | foo | false | | jane.doe@mail.com | name@pm.me | bar | true | And IMAP client "1" eventually sees 0 messages in "INBOX" @@ -55,7 +55,7 @@ Feature: IMAP copy messages Scenario: Copy message from Inbox to Sent is not possible When IMAP client "1" copies the message with subject "foo" from "INBOX" to "Sent" Then IMAP client "1" eventually sees the following messages in "INBOX": - | sender | recipient | subject | unread | + | from | to | subject | unread | | john.doe@mail.com | user@pm.me | foo | false | | jane.doe@mail.com | name@pm.me | bar | true | And IMAP client "1" eventually sees 0 messages in "Sent" \ No newline at end of file diff --git a/tests/features/smtp/send/html.feature b/tests/features/smtp/send/html.feature new file mode 100644 index 00000000..75483d2e --- /dev/null +++ b/tests/features/smtp/send/html.feature @@ -0,0 +1,344 @@ +Feature: SMTP sending of plain messages + Background: + Given there exists an account with username "user@pm.me" and password "password" + And there exists an account with username "bridgetest@protonmail.com" and password "password" + And there exists an account with username "bridgetest2@protonmail.com" and password "password" + And bridge starts + And the user logs in with username "user@pm.me" and password "password" + And user "user@pm.me" connects and authenticates SMTP client "1" + + Scenario: HTML message to external account + When SMTP client "1" sends the following message from "user@pm.me" to "pm.bridge.qa@gmail.com": + """ + From: Bridge Test + To: External Bridge + Subject: HTML text external + Content-Disposition: inline + Content-Transfer-Encoding: quoted-printable + Content-Type: text/html; charset=utf-8 + In-Reply-To: + + This is body of HTML mail without attachment + + """ + Then it succeeds + 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 | + | user@pm.me | pm.bridge.qa@gmail.com | HTML text external | + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "HTML text external", + "Sender": { + "Name": "Bridge Test" + }, + "ToList": [ + { + "Address": "pm.bridge.qa@gmail.com", + "Name": "External Bridge" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/html" + } + } + """ + + Scenario: HTML message with inline image to external account + When SMTP client "1" sends the following message from "user@pm.me" to "pm.bridge.qa@gmail.com": + """ + From: Bridge Test + To: External Bridge + Subject: Html Inline External + Content-Disposition: inline + User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.5.0 + MIME-Version: 1.0 + Content-Language: en-US + Content-Type: multipart/related; boundary="------------61FA22A41A3F46E8E90EF528" + + This is a multi-part message in MIME format. + --------------61FA22A41A3F46E8E90EF528 + Content-Type: text/html; charset=utf-8 + Content-Transfer-Encoding: 7bit + + + + + + +


+

+

Behold! An inline
+

+ + + + --------------61FA22A41A3F46E8E90EF528 + Content-Type: image/gif; name="email-action-left.gif" + Content-Transfer-Encoding: base64 + Content-ID: + Content-Disposition: inline; filename="email-action-left.gif" + + R0lGODlhGAAYANUAACcsKOHs4kppTH6tgYWxiIq0jTVENpG5lDI/M7bRuEaJSkqOTk2RUU+P + U16lYl+lY2iva262cXS6d3rDfYLNhWeeamKTZGSVZkNbRGqhbOPt4////+7u7qioqFZWVlNT + UyIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAGAAYAAAG + /8CNcLjRJAqVRqNSSGiI0GFgoKhar4NAdHioMhyRCYUyiTgY1cOWUH1ILgIDAGAQXCSPKgHa + XUAyGCCCg4IYGRALCmpCAVUQFgiEkiAIFhBVWhtUDxmRk5IIGXkDRQoMEoGfHpIYEmhGCg4X + nyAdHB+SFw4KRwoRArQdG7eEAhEKSAoTBoIdzs/Cw7iCBhMKSQoUAIJbQ8QgABQKStnbIN1C + 3+HjFcrMtdDO6dMg1dcFvsCfwt+CxsgJYs3a10+QLl4aTKGitYpQq1eaFHDyREtQqFGMHEGq + SMkSJi4K/ACiZQiRIihsJL6JM6fOnTwK9kTpYgqMGDJm0JzsNuWKTw0FWdANMYJECRMnW4IA + ADs= + --------------61FA22A41A3F46E8E90EF528-- + + """ + Then it succeeds + 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 | + | user@pm.me | pm.bridge.qa@gmail.com | Html Inline External | + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "Html Inline External", + "Sender": { + "Name": "Bridge Test" + }, + "ToList": [ + { + "Address": "pm.bridge.qa@gmail.com", + "Name": "External Bridge" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/html" + } + } + """ + + Scenario: HTML message with alternative inline to internal account + When SMTP client "1" sends the following message from "user@pm.me" to "bridgetest@protonmail.com": + """ + From: Bridge Test + To: Internal Bridge + Subject: Html Inline Alternative Internal + Content-Disposition: inline + User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.5.0 + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary="------------5A259F4DE164B5ADA313F644" + Content-Language: en-US + + This is a multi-part message in MIME format. + --------------5A259F4DE164B5ADA313F644 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: 7bit + + + Behold! An inline + + + --------------5A259F4DE164B5ADA313F644 + Content-Type: multipart/related; boundary="------------61FA22A41A3F46E8E90EF528" + + + --------------61FA22A41A3F46E8E90EF528 + Content-Type: text/html; charset=utf-8 + Content-Transfer-Encoding: 7bit + + + + + + +


+

+

Behold! An inline
+

+ + + + --------------61FA22A41A3F46E8E90EF528 + Content-Type: image/gif; name="email-action-left.gif" + Content-Transfer-Encoding: base64 + Content-ID: + Content-Disposition: inline; filename="email-action-left.gif" + + R0lGODlhGAAYANUAACcsKOHs4kppTH6tgYWxiIq0jTVENpG5lDI/M7bRuEaJSkqOTk2RUU+P + U16lYl+lY2iva262cXS6d3rDfYLNhWeeamKTZGSVZkNbRGqhbOPt4////+7u7qioqFZWVlNT + UyIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAGAAYAAAG + /8CNcLjRJAqVRqNSSGiI0GFgoKhar4NAdHioMhyRCYUyiTgY1cOWUH1ILgIDAGAQXCSPKgHa + XUAyGCCCg4IYGRALCmpCAVUQFgiEkiAIFhBVWhtUDxmRk5IIGXkDRQoMEoGfHpIYEmhGCg4X + nyAdHB+SFw4KRwoRArQdG7eEAhEKSAoTBoIdzs/Cw7iCBhMKSQoUAIJbQ8QgABQKStnbIN1C + 3+HjFcrMtdDO6dMg1dcFvsCfwt+CxsgJYs3a10+QLl4aTKGitYpQq1eaFHDyREtQqFGMHEGq + SMkSJi4K/ACiZQiRIihsJL6JM6fOnTwK9kTpYgqMGDJm0JzsNuWKTw0FWdANMYJECRMnW4IA + ADs= + --------------61FA22A41A3F46E8E90EF528-- + + --------------5A259F4DE164B5ADA313F644-- + + """ + Then it succeeds + 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 | + | user@pm.me | bridgetest@protonmail.com | Html Inline Alternative Internal | + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "Html Inline Alternative Internal", + "Sender": { + "Name": "Bridge Test" + }, + "ToList": [ + { + "Address": "bridgetest@protonmail.com", + "Name": "Internal Bridge" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/html" + } + } + """ + + Scenario: HTML message with alternative inline to external account + When SMTP client "1" sends the following message from "user@pm.me" to "pm.bridge.qa@gmail.com": + """ + From: Bridge Test + To: External Bridge + Subject: Html Inline Alternative External + Content-Disposition: inline + User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.5.0 + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary="------------5A259F4DE164B5ADA313F644" + Content-Language: en-US + + This is a multi-part message in MIME format. + --------------5A259F4DE164B5ADA313F644 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: 7bit + + + Behold! An inline + + + --------------5A259F4DE164B5ADA313F644 + Content-Type: multipart/related; boundary="------------61FA22A41A3F46E8E90EF528" + + + --------------61FA22A41A3F46E8E90EF528 + Content-Type: text/html; charset=utf-8 + Content-Transfer-Encoding: 7bit + + + + + + +


+

+

Behold! An inline
+

+ + + + --------------61FA22A41A3F46E8E90EF528 + Content-Type: image/gif; name="email-action-left.gif" + Content-Transfer-Encoding: base64 + Content-ID: + Content-Disposition: inline; filename="email-action-left.gif" + + R0lGODlhGAAYANUAACcsKOHs4kppTH6tgYWxiIq0jTVENpG5lDI/M7bRuEaJSkqOTk2RUU+P + U16lYl+lY2iva262cXS6d3rDfYLNhWeeamKTZGSVZkNbRGqhbOPt4////+7u7qioqFZWVlNT + UyIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAGAAYAAAG + /8CNcLjRJAqVRqNSSGiI0GFgoKhar4NAdHioMhyRCYUyiTgY1cOWUH1ILgIDAGAQXCSPKgHa + XUAyGCCCg4IYGRALCmpCAVUQFgiEkiAIFhBVWhtUDxmRk5IIGXkDRQoMEoGfHpIYEmhGCg4X + nyAdHB+SFw4KRwoRArQdG7eEAhEKSAoTBoIdzs/Cw7iCBhMKSQoUAIJbQ8QgABQKStnbIN1C + 3+HjFcrMtdDO6dMg1dcFvsCfwt+CxsgJYs3a10+QLl4aTKGitYpQq1eaFHDyREtQqFGMHEGq + SMkSJi4K/ACiZQiRIihsJL6JM6fOnTwK9kTpYgqMGDJm0JzsNuWKTw0FWdANMYJECRMnW4IA + ADs= + --------------61FA22A41A3F46E8E90EF528-- + + --------------5A259F4DE164B5ADA313F644-- + + """ + Then it succeeds + 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 | + | user@pm.me | pm.bridge.qa@gmail.com | Html Inline Alternative External | + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "Html Inline Alternative External", + "Sender": { + "Name": "Bridge Test" + }, + "ToList": [ + { + "Address": "pm.bridge.qa@gmail.com", + "Name": "External Bridge" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/html" + } + } + """ + + Scenario: HTML message with extremely long line (greater than default 2000 line limit) to external account + When SMTP client "1" sends the following message from "user@pm.me" to "pm.bridge.qa@gmail.com": + """ + From: Bridge Test + To: External Bridge + Subject: HTML text external + Content-Disposition: inline + Content-Transfer-Encoding: quoted-printable + Content-Type: text/html; charset=utf-8 + In-Reply-To: + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + """ + Then it succeeds + 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 | + | user@pm.me | pm.bridge.qa@gmail.com | HTML text external | + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "HTML text external", + "Sender": { + "Name": "Bridge Test" + }, + "ToList": [ + { + "Address": "pm.bridge.qa@gmail.com", + "Name": "External Bridge" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/html" + } + } + """ diff --git a/tests/features/smtp/send/plain.feature b/tests/features/smtp/send/plain.feature index 5ce521cb..bcc4f59f 100644 --- a/tests/features/smtp/send/plain.feature +++ b/tests/features/smtp/send/plain.feature @@ -19,8 +19,8 @@ Feature: SMTP sending of plain messages Then it succeeds When user "user@pm.me" connects and authenticates IMAP client "1" Then IMAP client "1" eventually sees the following messages in "Sent": - | sender | recipient | subject | unread | - | user@pm.me | bridgetest@protonmail.com | | false | + | from | to | subject | + | user@pm.me | bridgetest@protonmail.com | | And the body in the "POST" request to "/mail/v4/messages" is: """ { @@ -54,8 +54,8 @@ Feature: SMTP sending of plain messages Then it succeeds When user "user@pm.me" connects and authenticates IMAP client "1" Then IMAP client "1" eventually sees the following messages in "Sent": - | sender | recipient | subject | unread | - | user@pm.me | pm.bridge.qa@gmail.com | | false | + | from | to | subject | + | user@pm.me | pm.bridge.qa@gmail.com | | And the body in the "POST" request to "/mail/v4/messages" is: """ { @@ -92,8 +92,8 @@ Feature: SMTP sending of plain messages Then it succeeds When user "user@pm.me" connects and authenticates IMAP client "1" Then IMAP client "1" eventually sees the following messages in "Sent": - | sender | recipient | subject | unread | - | user@pm.me | bridgetest@protonmail.com | Plain text internal | false | + | from | to | subject | + | user@pm.me | bridgetest@protonmail.com | Plain text internal | And the body in the "POST" request to "/mail/v4/messages" is: """ { @@ -130,8 +130,8 @@ Feature: SMTP sending of plain messages Then it succeeds When user "user@pm.me" connects and authenticates IMAP client "1" Then IMAP client "1" eventually sees the following messages in "Sent": - | sender | recipient | subject | unread | - | user@pm.me | pm.bridge.qa@gmail.com | Plain text external | false | + | from | to | subject | + | user@pm.me | pm.bridge.qa@gmail.com | Plain text external | And the body in the "POST" request to "/mail/v4/messages" is: """ { @@ -168,8 +168,8 @@ Feature: SMTP sending of plain messages Then it succeeds When user "user@pm.me" connects and authenticates IMAP client "1" Then IMAP client "1" eventually sees the following messages in "Sent": - | sender | recipient | subject | unread | - | user@pm.me | pm.bridge.qa@gmail.com | Plain text no charset external | false | + | from | to | subject | + | user@pm.me | pm.bridge.qa@gmail.com | Plain text no charset external | And the body in the "POST" request to "/mail/v4/messages" is: """ { @@ -209,8 +209,8 @@ Feature: SMTP sending of plain messages Then it succeeds When user "user@pm.me" connects and authenticates IMAP client "1" Then IMAP client "1" eventually sees the following messages in "Sent": - | sender | recipient | subject | unread | - | user@pm.me | pm.bridge.qa@gmail.com | Plain text no charset external | false | + | from | to | subject | + | user@pm.me | pm.bridge.qa@gmail.com | Plain text no charset external | And the body in the "POST" request to "/mail/v4/messages" is: """ { @@ -245,8 +245,8 @@ Feature: SMTP sending of plain messages Then it succeeds When user "user@pm.me" connects and authenticates IMAP client "1" Then IMAP client "1" eventually sees the following messages in "Sent": - | sender | recipient | subject | unread | - | user@pm.me | pm.bridge.qa@gmail.com | Plain, no charset, no content, external | false | + | from | to | subject | + | user@pm.me | pm.bridge.qa@gmail.com | Plain, no charset, no content, external | And the body in the "POST" request to "/mail/v4/messages" is: """ { @@ -285,8 +285,8 @@ Feature: SMTP sending of plain messages Then it succeeds When user "user@pm.me" connects and authenticates IMAP client "1" Then IMAP client "1" eventually sees the following messages in "Sent": - | sender | recipient | cc | subject | unread | - | user@pm.me | bridgetest@protonmail.com | bridgetest2@protonmail.com | RCPT-CC test | false | + | from | to | cc | subject | + | user@pm.me | bridgetest@protonmail.com | bridgetest2@protonmail.com | RCPT-CC test | And the body in the "POST" request to "/mail/v4/messages" is: """ { diff --git a/tests/features/user/addressmode.feature b/tests/features/user/addressmode.feature index d0659773..3e51e37d 100644 --- a/tests/features/user/addressmode.feature +++ b/tests/features/user/addressmode.feature @@ -7,11 +7,11 @@ Feature: Address mode | one | folder | | two | folder | And the address "user@pm.me" of account "user@pm.me" has the following messages in "one": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | And the address "alias@pm.me" of account "user@pm.me" has the following messages in "two": - | sender | recipient | subject | unread | + | from | to | subject | unread | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | And bridge starts @@ -21,30 +21,30 @@ Feature: Address mode Scenario: The user is in combined mode When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me" Then IMAP client "1" sees the following messages in "Folders/one": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | And IMAP client "1" sees the following messages in "Folders/two": - | sender | recipient | subject | unread | + | from | to | subject | unread | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | And IMAP client "1" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me" Then IMAP client "2" sees the following messages in "Folders/one": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | And IMAP client "2" sees the following messages in "Folders/two": - | sender | recipient | subject | unread | + | from | to | subject | unread | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | And IMAP client "2" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | @@ -55,22 +55,22 @@ Feature: Address mode And user "user@pm.me" finishes syncing When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me" Then IMAP client "1" sees the following messages in "Folders/one": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | And IMAP client "1" sees 0 messages in "Folders/two" And IMAP client "1" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me" Then IMAP client "2" sees 0 messages in "Folders/one" And IMAP client "2" sees the following messages in "Folders/two": - | sender | recipient | subject | unread | + | from | to | subject | unread | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | And IMAP client "2" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | @@ -81,14 +81,14 @@ Feature: Address mode And user "user@pm.me" finishes syncing When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me" Then IMAP client "1" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me" Then IMAP client "2" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | @@ -97,14 +97,14 @@ Feature: Address mode Scenario: The user adds an address while in combined mode When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me" Then IMAP client "1" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me" Then IMAP client "2" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | @@ -113,7 +113,7 @@ Feature: Address mode And bridge sends an address created event for user "user@pm.me" When user "user@pm.me" connects and authenticates IMAP client "3" with address "other@pm.me" Then IMAP client "3" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | @@ -124,12 +124,12 @@ Feature: Address mode And user "user@pm.me" finishes syncing When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me" And IMAP client "1" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me" And IMAP client "2" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | Given the account "user@pm.me" has additional address "other@pm.me" @@ -140,14 +140,14 @@ Feature: Address mode Scenario: The user deletes an address while in combined mode When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me" Then IMAP client "1" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me" Then IMAP client "2" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | | c@pm.me | c@pm.me | three | true | @@ -162,12 +162,12 @@ Feature: Address mode And user "user@pm.me" finishes syncing When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me" And IMAP client "1" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me" And IMAP client "2" sees the following messages in "All Mail": - | sender | recipient | subject | unread | + | from | to | subject | unread | | c@pm.me | c@pm.me | three | true | | d@pm.me | d@pm.me | four | false | Given the account "user@pm.me" no longer has additional address "alias@pm.me" diff --git a/tests/features/user/sync.feature b/tests/features/user/sync.feature index 3dce33b6..e27d9efb 100644 --- a/tests/features/user/sync.feature +++ b/tests/features/user/sync.feature @@ -7,11 +7,11 @@ Feature: Bridge can fully sync an account | two | folder | | three | label | And the address "user@pm.me" of account "user@pm.me" has the following messages in "one": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | And the address "user@pm.me" of account "user@pm.me" has the following messages in "two": - | sender | recipient | subject | unread | + | from | to | subject | unread | | a@pm.me | a@pm.me | one | true | | b@pm.me | b@pm.me | two | false | And bridge starts diff --git a/tests/imap_test.go b/tests/imap_test.go index 302a59b2..eb3a4fb7 100644 --- a/tests/imap_test.go +++ b/tests/imap_test.go @@ -127,15 +127,16 @@ func (s *scenario) imapClientSeesTheFollowingMailboxInfo(clientID string, table return err } - haveMailboxes := xslices.Map(status, func(info *imap.MailboxStatus) Mailbox { - return Mailbox{ - Name: info.Name, - Total: int(info.Messages), - Unread: int(info.Unseen), - } + haveMailboxes := xslices.Map(status, func(status *imap.MailboxStatus) Mailbox { + return newMailboxFromIMAP(status) }) - return matchMailboxes(haveMailboxes, table) + wantMailboxes, err := unmarshalTable[Mailbox](table) + if err != nil { + return err + } + + return matchMailboxes(haveMailboxes, wantMailboxes) } func (s *scenario) imapClientEventuallySeesTheFollowingMailboxInfo(clientID string, table *godog.Table) error { @@ -159,14 +160,15 @@ func (s *scenario) imapClientSeesTheFollowingMailboxInfoForMailbox(clientID, mai }) haveMailboxes := xslices.Map(status, func(info *imap.MailboxStatus) Mailbox { - return Mailbox{ - Name: info.Name, - Total: int(info.Messages), - Unread: int(info.Unseen), - } + return newMailboxFromIMAP(info) }) - return matchMailboxes(haveMailboxes, table) + wantMailboxes, err := unmarshalTable[Mailbox](table) + if err != nil { + return err + } + + return matchMailboxes(haveMailboxes, wantMailboxes) } func (s *scenario) imapClientSeesTheFollowingMailboxes(clientID string, table *godog.Table) error { @@ -280,31 +282,15 @@ func (s *scenario) imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox } haveMessages := xslices.Map(fetch, func(msg *imap.Message) Message { - message := Message{ - Subject: msg.Envelope.Subject, - Unread: slices.Contains(msg.Flags, imap.SeenFlag), - } - - if len(msg.Envelope.From) > 0 { - message.From = msg.Envelope.From[0].Address() - } - - if len(msg.Envelope.To) > 0 { - message.To = msg.Envelope.To[0].Address() - } - - if len(msg.Envelope.Cc) > 0 { - message.CC = msg.Envelope.Cc[0].Address() - } - - if len(msg.Envelope.Bcc) > 0 { - message.BCC = msg.Envelope.Bcc[0].Address() - } - - return message + return newMessageFromIMAP(msg) }) - return matchMessages(haveMessages, table) + wantMessages, err := unmarshalTable[Message](table) + if err != nil { + return err + } + + return matchMessages(haveMessages, wantMessages) } func (s *scenario) imapClientEventuallySeesTheFollowingMessagesInMailbox(clientID, mailbox string, table *godog.Table) error { diff --git a/tests/types_test.go b/tests/types_test.go index 24ecdf3d..c9142a1e 100644 --- a/tests/types_test.go +++ b/tests/types_test.go @@ -6,82 +6,85 @@ import ( "strconv" "time" - "github.com/bradenaw/juniper/xslices" - "github.com/cucumber/godog" "github.com/cucumber/messages-go/v16" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" + "github.com/emersion/go-imap" + "golang.org/x/exp/slices" ) type Message struct { Subject string `bdd:"subject"` - From string `bdd:"sender"` - To string `bdd:"recipient"` + From string `bdd:"from"` + To string `bdd:"to"` CC string `bdd:"cc"` BCC string `bdd:"bcc"` Unread bool `bdd:"unread"` } -func newMessageFromRow(header, row *messages.PickleTableRow) Message { - var msg Message - - if err := unmarshalRow(header, row, &msg); err != nil { - panic(err) +func newMessageFromIMAP(msg *imap.Message) Message { + message := Message{ + Subject: msg.Envelope.Subject, + Unread: slices.Contains(msg.Flags, imap.SeenFlag), } - return msg + if len(msg.Envelope.From) > 0 { + message.From = msg.Envelope.From[0].Address() + } + + if len(msg.Envelope.To) > 0 { + message.To = msg.Envelope.To[0].Address() + } + + if len(msg.Envelope.Cc) > 0 { + message.CC = msg.Envelope.Cc[0].Address() + } + + if len(msg.Envelope.Bcc) > 0 { + message.BCC = msg.Envelope.Bcc[0].Address() + } + + return message } -func matchMessages(have []Message, want *godog.Table) error { - if want := parseMessages(want); !cmp.Equal(want, have, cmpopts.SortSlices(func(a, b Message) bool { return a.Subject < b.Subject })) { - return fmt.Errorf("want: %v, have: %v", want, have) +func matchMessages(have, want []Message) error { + if !IsSub(ToAny(have), ToAny(want)) { + return fmt.Errorf("missing messages: %v", want) } return nil } -func parseMessages(table *godog.Table) []Message { - header := table.Rows[0] - - return xslices.Map(table.Rows[1:], func(row *messages.PickleTableRow) Message { - return newMessageFromRow(header, row) - }) -} - type Mailbox struct { Name string `bdd:"name"` Total int `bdd:"total"` Unread int `bdd:"unread"` } -func newMailboxFromRow(header, row *messages.PickleTableRow) Mailbox { - var mbox Mailbox - - if err := unmarshalRow(header, row, &mbox); err != nil { - panic(err) +func newMailboxFromIMAP(status *imap.MailboxStatus) Mailbox { + return Mailbox{ + Name: status.Name, + Total: int(status.Messages), + Unread: int(status.Unseen), } - - return mbox } -func matchMailboxes(have []Mailbox, want *godog.Table) error { - if want := parseMailboxes(want); !cmp.Equal(want, have, cmpopts.SortSlices(func(a, b Mailbox) bool { return a.Name < b.Name })) { - return fmt.Errorf("want: %v, have: %v", want, have) +func matchMailboxes(have, want []Mailbox) error { + slices.SortFunc(have, func(a, b Mailbox) bool { + return a.Name < b.Name + }) + + slices.SortFunc(want, func(a, b Mailbox) bool { + return a.Name < b.Name + }) + + if !IsSub(want, have) { + return fmt.Errorf("missing messages: %v", want) } return nil } -func parseMailboxes(table *godog.Table) []Mailbox { - header := table.Rows[0] - - return xslices.Map(table.Rows[1:], func(row *messages.PickleTableRow) Mailbox { - return newMailboxFromRow(header, row) - }) -} - func eventually(condition func() error, waitFor, tick time.Duration) error { ch := make(chan error, 1) @@ -111,14 +114,24 @@ func eventually(condition func() error, waitFor, tick time.Duration) error { } } -func getCellValue(header, row *messages.PickleTableRow, name string) (string, bool) { - for idx, cell := range header.Cells { - if cell.Value == name { - return row.Cells[idx].Value, true - } +func unmarshalTable[T any](table *messages.PickleTable) ([]T, error) { + if len(table.Rows) == 0 { + return nil, fmt.Errorf("empty table") } - return "", false + var res []T + + for _, row := range table.Rows[1:] { + var v T + + if err := unmarshalRow(table.Rows[0], row, &v); err != nil { + return nil, err + } + + res = append(res, v) + } + + return res, nil } func unmarshalRow(header, row *messages.PickleTableRow, v any) error { @@ -152,6 +165,16 @@ func unmarshalRow(header, row *messages.PickleTableRow, v any) error { return nil } +func getCellValue(header, row *messages.PickleTableRow, name string) (string, bool) { + for idx, cell := range header.Cells { + if cell.Value == name { + return row.Cells[idx].Value, true + } + } + + return "", false +} + func mustParseInt(s string) int { i, err := strconv.Atoi(s) if err != nil { diff --git a/tests/user_test.go b/tests/user_test.go index 941c6e1c..335f36fa 100644 --- a/tests/user_test.go +++ b/tests/user_test.go @@ -8,9 +8,7 @@ import ( "time" "github.com/ProtonMail/gluon/rfc822" - "github.com/bradenaw/juniper/xslices" "github.com/cucumber/godog" - "github.com/cucumber/messages-go/v16" "github.com/google/uuid" "gitlab.protontech.ch/go/liteapi" "golang.org/x/exp/slices" @@ -82,29 +80,28 @@ func (s *scenario) theAccountHasCustomLabels(username string, count int) error { } func (s *scenario) theAccountHasTheFollowingCustomMailboxes(username string, table *godog.Table) error { - type mailbox struct { - name string - typ liteapi.LabelType + type CustomMailbox struct { + Name string `bdd:"name"` + Type string `bdd:"type"` } - wantMailboxes := xslices.Map(table.Rows[1:], func(row *messages.PickleTableRow) mailbox { - var mailboxType liteapi.LabelType - - switch row.Cells[1].Value { - case "folder": - mailboxType = liteapi.LabelTypeFolder - case "label": - mailboxType = liteapi.LabelTypeLabel - } - - return mailbox{ - name: row.Cells[0].Value, - typ: mailboxType, - } - }) + wantMailboxes, err := unmarshalTable[CustomMailbox](table) + if err != nil { + return err + } for _, wantMailbox := range wantMailboxes { - if _, err := s.t.api.CreateLabel(s.t.getUserID(username), wantMailbox.name, wantMailbox.typ); err != nil { + var labelType liteapi.LabelType + + switch wantMailbox.Type { + case "folder": + labelType = liteapi.LabelTypeFolder + + case "label": + labelType = liteapi.LabelTypeLabel + } + + if _, err := s.t.api.CreateLabel(s.t.getUserID(username), wantMailbox.Name, labelType); err != nil { return err } } @@ -117,7 +114,12 @@ func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address, addrID := s.t.getUserAddrID(userID, address) mboxID := s.t.getMBoxID(userID, mailbox) - for _, wantMessage := range parseMessages(table) { + wantMessages, err := unmarshalTable[Message](table) + if err != nil { + return err + } + + for _, wantMessage := range wantMessages { if _, err := s.t.api.CreateMessage( userID, addrID, @@ -125,8 +127,8 @@ func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address, wantMessage.Subject, &mail.Address{Address: wantMessage.From}, []*mail.Address{{Address: wantMessage.To}}, - []*mail.Address{}, - []*mail.Address{}, + []*mail.Address{{Address: wantMessage.CC}}, + []*mail.Address{{Address: wantMessage.BCC}}, "some body goes here", rfc822.TextPlain, wantMessage.Unread,