GODT-1779: Remove go-imap

This commit is contained in:
James Houlahan
2022-08-26 17:00:21 +02:00
parent 3b0bc1ca15
commit 39433fe707
593 changed files with 12725 additions and 91626 deletions

View File

@ -0,0 +1,92 @@
Feature: IMAP auth
Scenario: Authenticates successfully
Given there is connected user "user"
When IMAP client authenticates "user"
Then IMAP response is "OK"
Scenario: Authenticates with bad password
Given there is connected user "user"
When IMAP client authenticates "user" with bad password
Then IMAP response is "IMAP error: NO backend/credentials: incorrect password"
@ignore-live-auth
Scenario: Authenticates with disconnected user
Given there is disconnected user "user"
When IMAP client authenticates "user"
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"
@ignore-live-auth
Scenario: Authenticates with connected user that was loaded without internet
Given there is user "user" which just logged in
And there is no internet connection
When bridge starts
And the internet connection is restored
And the event loop of "user" loops once
And IMAP client authenticates "user"
# Problems during IMAP auth could lead to the user being disconnected.
# This could take a few milliseconds because it happens async in separate goroutines.
# We wait enough time for that to happen, then check that it didn't happen (user should remain connected).
And 2 seconds pass
Then "user" is connected
@ignore-live-auth
Scenario: Authenticates with freshly logged-out user
Given there is user "user" which just logged in
When "user" logs out
And IMAP client authenticates "user"
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"
@ignore-live-auth
Scenario: Authenticates user which was re-logged in
Given there is user "user" which just logged in
When "user" logs out
And IMAP client authenticates "user"
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"
When "user" logs in
And IMAP client authenticates "user"
Then IMAP response is "OK"
When IMAP client selects "INBOX"
Then IMAP response is "OK"
Scenario: Authenticates with no user
When IMAP client authenticates with username "user@pm.me" and password "bridgepassword"
Then IMAP response is "IMAP error: NO user user@pm.me not found"
Scenario: Authenticates with capital letter
Given there is connected user "userAddressWithCapitalLetter"
When IMAP client authenticates "userAddressWithCapitalLetter"
Then IMAP response is "OK"
Scenario: Authenticates with more addresses - primary one
Given there is connected user "userMoreAddresses"
When IMAP client authenticates "userMoreAddresses" with address "primary"
Then IMAP response is "OK"
Scenario: Authenticates with more addresses - secondary one
Given there is connected user "userMoreAddresses"
When IMAP client authenticates "userMoreAddresses" with address "secondary"
Then IMAP response is "OK"
Scenario: Authenticates with more addresses - disabled address
Given there is connected user "userMoreAddresses"
When IMAP client authenticates "userMoreAddresses" with address "disabled"
Then IMAP response is "IMAP error: NO user .* not found"
@ignore-live
Scenario: Authenticates with secondary address of account with disabled primary address
Given there is connected user "userDisabledPrimaryAddress"
When IMAP client authenticates "userDisabledPrimaryAddress" with address "secondary"
Then IMAP response is "OK"
Scenario: Authenticates two users
Given there is connected user "user"
And there is connected user "userMoreAddresses"
When IMAP client "imap1" authenticates "user"
Then IMAP response to "imap1" is "OK"
When IMAP client "imap2" authenticates "userMoreAddresses" with address "primary"
Then IMAP response to "imap2" is "OK"
Scenario: Logs out user
Given there is connected user "user"
When IMAP client logs out
Then IMAP response is "OK"

View File

@ -0,0 +1,63 @@
Feature: IMAP IDLE
Background:
Given there is connected user "user"
And there are 10 messages in mailbox "INBOX" for "user"
# Those tests are ignored as currently our IMAP implementation is not responding with updates to all open connections.
@ignore
Scenario Outline: Mark as read
Given there is IMAP client "active" logged in as "user"
And there is IMAP client "active" selected in "INBOX"
And there is IMAP client "idling" logged in as "user"
And there is IMAP client "idling" selected in "INBOX"
When IMAP client "idling" starts IDLE-ing
And IMAP client "active" marks message seq "<seq>" as read
Then IMAP client "idling" receives update marking message seq "<seq>" as read within <seconds> seconds
Then message "<seq>" in "INBOX" for "user" is marked as read
Examples:
| seq | seconds |
| 1 | 2 |
| 1:5 | 2 |
| 1:10 | 5 |
@ignore
Scenario Outline: Mark as unread
Given there is IMAP client "active" logged in as "user"
And there is IMAP client "active" selected in "INBOX"
And there is IMAP client "idling" logged in as "user"
And there is IMAP client "idling" selected in "INBOX"
When IMAP client "idling" starts IDLE-ing
And IMAP client "active" marks message seq "<seq>" as unread
Then IMAP client "idling" receives update marking message seq "<seq>" as unread within <seconds> seconds
And message "<seq>" in "INBOX" for "user" is marked as unread
Examples:
| seq | seconds |
| 1 | 2 |
| 1:5 | 2 |
| 1:10 | 5 |
@ignore
Scenario Outline: Three IDLEing
Given there is IMAP client "active" logged in as "user"
And there is IMAP client "active" selected in "INBOX"
And there is IMAP client "idling1" logged in as "user"
And there is IMAP client "idling1" selected in "INBOX"
And there is IMAP client "idling2" logged in as "user"
And there is IMAP client "idling2" selected in "INBOX"
And there is IMAP client "idling3" logged in as "user"
And there is IMAP client "idling3" selected in "INBOX"
When IMAP client "idling1" starts IDLE-ing
And IMAP client "idling2" starts IDLE-ing
And IMAP client "idling3" starts IDLE-ing
And IMAP client "active" marks message seq "<seq>" as read
Then IMAP client "idling1" receives update marking message seq "<seq>" as read within <seconds> seconds
Then IMAP client "idling2" receives update marking message seq "<seq>" as read within <seconds> seconds
Then IMAP client "idling3" receives update marking message seq "<seq>" as read within <seconds> seconds
Examples:
| seq | seconds |
| 1 | 2 |
| 1:5 | 2 |
| 1:10 | 5 |

View File

@ -0,0 +1,27 @@
Feature: IMAP IDLE with two users
Scenario: IDLE statements are not leaked to other account
Given there is connected user "user"
And there are 10 messages in mailbox "INBOX" for "user"
And there is connected user "userMoreAddresses"
And there is IMAP client "active" logged in as "user"
And there is IMAP client "active" selected in "INBOX"
And there is IMAP client "idling" logged in as "userMoreAddresses"
And there is IMAP client "idling" selected in "INBOX"
When IMAP client "idling" starts IDLE-ing
And IMAP client "active" marks message seq "1" as read
Then IMAP client "idling" does not receive update for message seq "1" within 5 seconds
Scenario: IDLE statements are not leaked to other alias
Given there is connected user "userMoreAddresses"
And there is "userMoreAddresses" in "combined" address mode
And there are messages in mailbox "INBOX" for "userMoreAddresses"
| from | to | subject |
| john.doe@mail.com | [primary] | foo |
| jane.doe@mail.com | [secondary] | bar |
And there is IMAP client "active" logged in as "userMoreAddresses" with address "primary"
And there is IMAP client "active" selected in "INBOX"
And there is IMAP client "idling" logged in as "userMoreAddresses" with address "secondary"
And there is IMAP client "idling" selected in "INBOX"
When IMAP client "idling" starts IDLE-ing
And IMAP client "active" marks message seq "1" as read
Then IMAP client "idling" does not receive update for message seq "1" within 5 seconds

View File

@ -0,0 +1,50 @@
Feature: IMAP create mailbox
Background:
Given there is connected user "user"
And there is IMAP client logged in as "user"
And "user" does not have mailbox "Folders/mbox"
And "user" does not have mailbox "Labels/mbox"
Scenario: Create folder
When IMAP client creates mailbox "Folders/mbox"
Then IMAP response is "OK"
And "user" has mailbox "Folders/mbox"
And "user" does not have mailbox "Labels/mbox"
Scenario: Create label
When IMAP client creates mailbox "Labels/mbox"
Then IMAP response is "OK"
And "user" does not have mailbox "Folders/mbox"
And "user" has mailbox "Labels/mbox"
Scenario: Creating label with existing name is not possible
Given there is "user" with mailbox "Folders/mbox"
When IMAP client creates mailbox "Labels/mbox"
Then IMAP response is "IMAP error: NO A label or folder with this name already exists"
And "user" has mailbox "Folders/mbox"
And "user" does not have mailbox "Labels/mbox"
Scenario: Creating folder with existing name is not possible
Given there is "user" with mailbox "Labels/mbox"
When IMAP client creates mailbox "Folders/mbox"
Then IMAP response is "IMAP error: NO A label or folder with this name already exists"
And "user" has mailbox "Labels/mbox"
And "user" does not have mailbox "Folders/mbox"
Scenario: Creating system mailbox is not possible
When IMAP client creates mailbox "INBOX"
Then IMAP response is "IMAP error: NO mailbox INBOX already exists"
When IMAP client creates mailbox "Folders/INBOX"
Then IMAP response is "IMAP error: NO Invalid name"
# API allows you to create custom folder with naem `All Mail`
#When IMAP client creates mailbox "Folders/All mail"
#Then IMAP response is "IMAP error: NO mailbox All Mail already exists"
Scenario: Creating mailbox without prefix is not possible
When IMAP client creates mailbox "mbox"
Then IMAP response is "OK"
And "user" does not have mailbox "mbox"
When All mail mailbox is hidden
And IMAP client creates mailbox "All mail"
Then IMAP response is "OK"
And "user" does not have mailbox "All mail"

View File

@ -0,0 +1,29 @@
Feature: IMAP delete mailbox
Background:
Given there is connected user "user"
Scenario: Delete folder
Given there is "user" with mailbox "Folders/mbox"
And there is IMAP client logged in as "user"
When IMAP client deletes mailbox "Folders/mbox"
Then IMAP response is "OK"
And "user" does not have mailbox "Folders/mbox"
Scenario: Delete label
Given there is "user" with mailbox "Labels/mbox"
And there is IMAP client logged in as "user"
When IMAP client deletes mailbox "Labels/mbox"
Then IMAP response is "OK"
And "user" does not have mailbox "Labels/mbox"
Scenario: Empty Trash by deleting it
Given there are 10 messages in mailbox "Trash" for "user"
And there is IMAP client logged in as "user"
When IMAP client deletes mailbox "Trash"
Then IMAP response is "OK"
And mailbox "Trash" for "user" has 0 messages
Scenario: Deleting system mailbox is not possible
Given there is IMAP client logged in as "user"
When IMAP client deletes mailbox "INBOX"
Then IMAP response is "IMAP error: NO cannot empty mailbox 0"

View File

@ -0,0 +1,15 @@
Feature: IMAP get mailbox info
Background:
Given there is connected user "user"
And there are messages in mailbox "INBOX" for "user"
| from | to | subject | body | read | starred |
| john.doe@mail.com | user@pm.me | foo | hello | false | false |
| jane.doe@mail.com | name@pm.me | bar | world | true | true |
And there is IMAP client logged in as "user"
Scenario: Mailbox info contains mailbox name
When IMAP client gets info of "INBOX"
Then IMAP response contains "2 EXISTS"
And IMAP response contains "UNSEEN 1"
And IMAP response contains "UIDNEXT 3"
And IMAP response contains "UIDVALIDITY"

View File

@ -0,0 +1,143 @@
Feature: IMAP list mailboxes
Background:
Given there is connected user "user"
Scenario: List mailboxes
Given there is "user" with mailbox "Folders/mbox1"
And there is "user" with mailbox "Labels/mbox2"
And there is IMAP client logged in as "user"
When IMAP client lists mailboxes
Then IMAP response contains "INBOX"
Then IMAP response contains "Sent"
Then IMAP response contains "Archive"
Then IMAP response contains "Trash"
Then IMAP response contains "All Mail"
Then IMAP response contains "Folders/mbox1"
Then IMAP response contains "Labels/mbox2"
Scenario: List mailboxes without All Mail
Given there is IMAP client logged in as "user"
When IMAP client lists mailboxes
Then IMAP response contains "INBOX"
Then IMAP response contains "Sent"
Then IMAP response contains "Archive"
Then IMAP response contains "Trash"
Then IMAP response contains "All Mail"
When All mail mailbox is hidden
And IMAP client lists mailboxes
Then IMAP response contains "INBOX"
Then IMAP response contains "Sent"
Then IMAP response contains "Archive"
Then IMAP response contains "Trash"
Then IMAP response doesn't contain "All Mail"
When All mail mailbox is visible
And IMAP client lists mailboxes
Then IMAP response contains "INBOX"
Then IMAP response contains "Sent"
Then IMAP response contains "Archive"
Then IMAP response contains "Trash"
Then IMAP response contains "All Mail"
Scenario: List multiple times in parallel without crash
Given there is "user" with mailboxes
| Folders/mbox1 |
| Folders/mbox2 |
| Folders/mbox3 |
| Folders/mbox4 |
| Folders/mbox5 |
| Folders/mbox6 |
| Folders/mbox7 |
| Folders/mbox8 |
| Folders/mbox9 |
| Folders/mbox10 |
| Folders/mbox11 |
| Folders/mbox12 |
| Folders/mbox13 |
| Folders/mbox14 |
| Folders/mbox15 |
| Folders/mbox16 |
| Folders/mbox17 |
| Folders/mbox18 |
| Folders/mbox19 |
| Folders/mbox20 |
| Labels/lab1 |
| Labels/lab2 |
| Labels/lab3 |
| Labels/lab4 |
| Labels/lab5 |
| Labels/lab6 |
| Labels/lab7 |
| Labels/lab8 |
| Labels/lab9 |
| Labels/lab10 |
| Labels/lab11 |
| Labels/lab12 |
| Labels/lab13 |
| Labels/lab14 |
| Labels/lab15 |
| Labels/lab16 |
| Labels/lab17 |
| Labels/lab18 |
| Labels/lab19 |
| Labels/lab20 |
| Labels/lab1.1 |
| Labels/lab1.2 |
| Labels/lab1.3 |
| Labels/lab1.4 |
| Labels/lab1.5 |
| Labels/lab1.6 |
| Labels/lab1.7 |
| Labels/lab1.8 |
| Labels/lab1.9 |
| Labels/lab1.10 |
| Labels/lab1.11 |
| Labels/lab1.12 |
| Labels/lab1.13 |
| Labels/lab1.14 |
| Labels/lab1.15 |
| Labels/lab1.16 |
| Labels/lab1.17 |
| Labels/lab1.18 |
| Labels/lab1.19 |
| Labels/lab1.20 |
| Labels/lab2.1 |
| Labels/lab2.2 |
| Labels/lab2.3 |
| Labels/lab2.4 |
| Labels/lab2.5 |
| Labels/lab2.6 |
| Labels/lab2.7 |
| Labels/lab2.8 |
| Labels/lab2.9 |
| Labels/lab2.10 |
| Labels/lab2.11 |
| Labels/lab2.12 |
| Labels/lab2.13 |
| Labels/lab2.14 |
| Labels/lab2.15 |
| Labels/lab2.16 |
| Labels/lab2.17 |
| Labels/lab2.18 |
| Labels/lab2.19 |
| Labels/lab2.20 |
And there is IMAP client "A" logged in as "user"
And there is IMAP client "B" logged in as "user"
When IMAP client "A" lists mailboxes
And IMAP client "B" lists mailboxes
Then IMAP response to "A" is "OK"
And IMAP response to "A" contains "mbox1"
And IMAP response to "A" contains "mbox10"
And IMAP response to "A" contains "mbox20"
@ignore-live
Scenario: List mailboxes with subfolders
# Escaped slash in the name contains slash in the name.
# Not-escaped slash in the name means tree structure.
# We keep escaping in an IMAP communication so each mailbox is unique and
# both mailboxes are accessible. The slash is visible in the IMAP client.
Given there is "user" with mailbox "Folders/a\/b"
And there is "user" with mailbox "Folders/a/b"
And there is IMAP client logged in as "user"
When IMAP client lists mailboxes
Then IMAP response contains "Folders/a\/b"
Then IMAP response contains "Folders/a/b"

View File

@ -0,0 +1,30 @@
Feature: IMAP mailbox rename
Background:
Given there is connected user "user"
Scenario: Rename folder
Given there is "user" with mailbox "Folders/mbox"
And there is IMAP client logged in as "user"
When IMAP client renames mailbox "Folders/mbox" to "Folders/mbox2"
Then IMAP response is "OK"
And "user" does not have mailbox "Folders/mbox"
And "user" has mailbox "Folders/mbox2"
Scenario: Rename label
Given there is "user" with mailbox "Labels/mbox"
And there is IMAP client logged in as "user"
When IMAP client renames mailbox "Labels/mbox" to "Labels/mbox2"
Then IMAP response is "OK"
And "user" does not have mailbox "Labels/mbox"
And "user" has mailbox "Labels/mbox2"
Scenario: Renaming folder to label is not possible
Given there is "user" with mailbox "Folders/mbox"
And there is IMAP client logged in as "user"
When IMAP client renames mailbox "Folders/mbox" to "Labels/mbox"
Then IMAP response is "IMAP error: NO cannot rename folder to non-folder"
Scenario: Renaming system folder is not possible
Given there is IMAP client logged in as "user"
When IMAP client renames mailbox "INBOX" to "Folders/mbox"
Then IMAP response is "IMAP error: NO cannot rename system mailboxes"

View File

@ -0,0 +1,17 @@
Feature: IMAP select into mailbox
Background:
Given there is connected user "user"
And there is "user" with mailbox "Folders/mbox"
And there is IMAP client logged in as "user"
Scenario: Select into inbox
When IMAP client selects "INBOX"
Then IMAP response is "OK"
Scenario: Select into custom mailbox
When IMAP client selects "Folders/mbox"
Then IMAP response is "OK"
Scenario: Select into non-existing mailbox
When IMAP client selects "qwerty"
Then IMAP response is "IMAP error: NO mailbox qwerty does not exist"

View File

@ -0,0 +1,20 @@
Feature: IMAP get mailbox status
Background:
Given there is connected user "user"
And there are messages in mailbox "INBOX" for "user"
| from | to | subject | body | read | starred |
| john.doe@mail.com | user@pm.me | foo | hello | false | false |
| jane.doe@mail.com | name@pm.me | bar | world | true | true |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
Scenario: Mailbox status contains mailbox name
When IMAP client gets status of "INBOX"
Then IMAP response contains "INBOX"
Scenario: Mailbox status contains counts and UIDs
When IMAP client gets status of "INBOX"
And IMAP response contains "MESSAGES 2"
And IMAP response contains "UNSEEN 1"
And IMAP response contains "UIDNEXT 3"
And IMAP response contains "UIDVALIDITY"

View File

@ -0,0 +1,89 @@
Feature: IMAP copy messages
Background:
Given there is connected user "user"
And there is "user" with mailbox "Folders/mbox"
And there is "user" with mailbox "Labels/label"
And there are messages in mailbox "INBOX" for "user"
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
| jane.doe@mail.com | name@pm.me | bar | world | false | true |
And there are messages in mailbox "Sent" for "user"
| from | to | subject | body |
| john.doe@mail.com | user@pm.me | response | hello |
And there is IMAP client logged in as "user"
Scenario: Copy message to label
Given there is IMAP client selected in "INBOX"
When IMAP client copies message seq "1" to "Labels/label"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
| jane.doe@mail.com | name@pm.me | bar | world | false | true |
And mailbox "Labels/label" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
Scenario: Copy all messages to label
Given there is IMAP client selected in "INBOX"
When IMAP client copies message seq "1:*" to "Labels/label"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
| jane.doe@mail.com | name@pm.me | bar | world | false | true |
And mailbox "Labels/label" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
| jane.doe@mail.com | name@pm.me | bar | world | false | true |
Scenario: Copy message to folder does move
Given there is IMAP client selected in "INBOX"
When IMAP client copies message seq "1" to "Folders/mbox"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has messages
| from | to | subject | body | read | deleted |
| jane.doe@mail.com | name@pm.me | bar | world | false | true |
And mailbox "Folders/mbox" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
Scenario: Copy message from All mail moves from the original location
Given there is IMAP client selected in "All Mail"
When IMAP client copies message seq "1" to "Folders/mbox"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 2 messages
And mailbox "INBOX" for "user" has messages
| from | to | subject | body | read | deleted |
| jane.doe@mail.com | name@pm.me | bar | world | false | true |
| john.doe@mail.com | user@pm.me | response | hello | true | false |
And mailbox "All Mail" for "user" has 3 messages
And mailbox "All Mail" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
| jane.doe@mail.com | name@pm.me | bar | world | false | false |
| john.doe@mail.com | user@pm.me | response | hello | true | false |
And mailbox "Folders/mbox" for "user" has 1 messages
And mailbox "Folders/mbox" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
Scenario: Copy all messages to folder does move
Given there is IMAP client selected in "INBOX"
When IMAP client copies message seq "1:*" to "Folders/mbox"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 0 messages
And mailbox "Folders/mbox" for "user" has messages
| from | to | subject | body | read | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | true | false |
| jane.doe@mail.com | name@pm.me | bar | world | false | true |
Scenario: Copy message from Inbox to Sent is not possible
Given there is IMAP client selected in "INBOX"
When IMAP client copies message seq "1" to "Sent"
Then IMAP response is "move from Inbox to Sent is not allowed"
Scenario: Copy message from Sent to Inbox is not possible
Given there is IMAP client selected in "Sent"
When IMAP client copies message seq "1" to "INBOX"
Then IMAP response is "move from Sent to Inbox is not allowed"

View File

@ -0,0 +1,78 @@
Feature: IMAP create messages
Background:
Given there is connected user "userMoreAddresses"
And there is IMAP client logged in as "userMoreAddresses"
Scenario: Creates message to user's primary address
Given there is IMAP client selected in "INBOX"
When IMAP client creates message "foo" from "john.doe@email.com" to address "primary" of "userMoreAddresses" with body "hello world" in "INBOX"
Then IMAP response is "OK"
And mailbox "INBOX" for "userMoreAddresses" has messages
| from | to | subject | read |
| john.doe@email.com | [primary] | foo | true |
Scenario: Creates draft
When IMAP client creates message "foo" from address "primary" of "userMoreAddresses" to "john.doe@email.com" with body "hello world" in "Drafts"
Then IMAP response is "OK"
And mailbox "Drafts" for "userMoreAddresses" has messages
| from | to | subject | read |
| [primary] | john.doe@email.com | foo | true |
Scenario: Creates message sent from user's primary address
Given there is IMAP client selected in "Sent"
When IMAP client creates message "foo" from address "primary" of "userMoreAddresses" to "john.doe@email.com" with body "hello world" in "Sent"
Then IMAP response is "OK"
When the event loop of "userMoreAddresses" loops once
Then mailbox "Sent" for "userMoreAddresses" has messages
| from | to | subject | read |
| [primary] | john.doe@email.com | foo | true |
And mailbox "INBOX" for "userMoreAddresses" has no messages
Scenario: Creates message sent from user's secondary address
Given there is IMAP client selected in "Sent"
When IMAP client creates message "foo" from address "secondary" of "userMoreAddresses" to "john.doe@email.com" with body "hello world" in "Sent"
Then IMAP response is "OK"
When the event loop of "userMoreAddresses" loops once
Then mailbox "Sent" for "userMoreAddresses" has messages
| from | to | subject | read |
| [secondary] | john.doe@email.com | foo | true |
And mailbox "INBOX" for "userMoreAddresses" has no messages
Scenario: Imports an unrelated message to inbox
Given there is IMAP client selected in "INBOX"
When IMAP client creates message "foo" from "john.doe@email.com" to "john.doe2@email.com" with body "hello world" in "INBOX"
Then IMAP response is "OK"
And mailbox "INBOX" for "userMoreAddresses" has messages
| from | to | subject | read |
| john.doe@email.com | john.doe2@email.com | foo | true |
Scenario: Imports an unrelated message to sent
Given there is IMAP client selected in "Sent"
When IMAP client creates message "foo" from "notuser@gmail.com" to "alsonotuser@gmail.com" with body "hello world" in "Sent"
Then IMAP response is "OK"
When the event loop of "userMoreAddresses" loops once
Then mailbox "Sent" for "userMoreAddresses" has messages
| from | to | subject | read |
| notuser@gmail.com | alsonotuser@gmail.com | foo | true |
And mailbox "INBOX" for "userMoreAddresses" has no messages
# Importing duplicate messages when messageID cannot be found in Sent already.
#
# Previously, we discarded messages for which sender matches account address to
# avoid duplicates, but this led to discarding messages imported through mail client.
#
# NOTE: We need to introduce cooldown here in order to detect duplicates
# properly. Once mail is imported to API the Sphinx indices for duplicate
# detection are updated every 10s. Therefore it is good to leave at least 15
# second gap after import in order to be able to correctly handle the case
# when we try to detect duplicate imports
Scenario: Imports a similar (duplicate) message to sent
Given there are messages in mailbox "Sent" for "userMoreAddresses"
| from | to | subject | body |
| [primary] | chosen@one.com | Meet the Twins | Hello, Mr. Anderson |
And wait for Sphinx to create duplication indices
And there is IMAP client selected in "Sent"
Then mailbox "Sent" for "userMoreAddresses" has 1 messages
When IMAP client creates message "Meet the Twins" from address "primary" of "userMoreAddresses" to "chosen@one.com" with body "Hello, Mr. Anderson" in "Sent"
Then IMAP response is "OK \[APPENDUID 4 2\] APPEND completed"
And mailbox "Sent" for "userMoreAddresses" has 2 messages

View File

@ -0,0 +1,127 @@
Feature: IMAP remove messages from mailbox
Background:
Given there is connected user "user"
And there is "user" with mailbox "Folders/mbox"
And there is "user" with mailbox "Labels/label"
Scenario Outline: Mark message as deleted and EXPUNGE
Given there are 10 messages in mailbox "<mailbox>" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "<mailbox>"
When IMAP client marks message seq "2" as deleted
Then IMAP response is "OK"
And mailbox "<mailbox>" for "user" has 10 messages
And message "2" in "<mailbox>" for "user" is marked as deleted
And IMAP response contains "\* 2 FETCH[ (]*FLAGS \([^)]*\\Deleted"
When IMAP client sends expunge
Then IMAP response is "OK"
And IMAP response contains "\* 2 EXPUNGE"
And mailbox "<mailbox>" for "user" has 9 messages
Examples:
| mailbox |
| INBOX |
| Folders/mbox |
| Labels/label |
| Spam |
| Trash |
Scenario Outline: Mark all messages as deleted and EXPUNGE
Given there are 5 messages in mailbox "<mailbox>" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "<mailbox>"
When IMAP client marks message seq "1:*" as deleted
Then IMAP response is "OK"
# Use UID version to not be sensitive about the order from API. Event loop
# could return it in different order and delete first message with seq 1,
# which would produce EXPUNGE sequence as 1 4 3 2 1, for example.
When IMAP client sends expunge by UID "1:5"
Then IMAP response is "OK"
And IMAP response contains "\* 1 EXPUNGE"
And IMAP response contains "\* 2 EXPUNGE"
And IMAP response contains "\* 3 EXPUNGE"
And IMAP response contains "\* 4 EXPUNGE"
And IMAP response contains "\* 5 EXPUNGE"
And mailbox "<mailbox>" for "user" has 0 messages
Examples:
| mailbox |
| INBOX |
| Folders/mbox |
| Labels/label |
| Spam |
| Trash |
Scenario Outline: Mark messages as undeleted and EXPUNGE
Given there are 5 messages in mailbox "<mailbox>" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "<mailbox>"
When IMAP client marks message seq "1:*" as deleted
Then IMAP response is "OK"
When IMAP client marks message seq "1:3" as undeleted
Then IMAP response is "OK"
When IMAP client sends expunge
Then IMAP response is "OK"
And IMAP response contains "\* 4 EXPUNGE"
And IMAP response contains "\* 5 EXPUNGE"
And mailbox "<mailbox>" for "user" has 3 messages
Examples:
| mailbox |
| INBOX |
| Folders/mbox |
| Labels/label |
| Spam |
| Trash |
Scenario Outline: Mark message as deleted and leave mailbox
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client marks message seq "2" as deleted
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 10 messages
And message "2" in "INBOX" for "user" is marked as deleted
When IMAP client sends command "<leave>"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has <n> messages
Examples:
| leave | n |
| CLOSE | 9 |
| SELECT INBOX | 9 |
| SELECT Trash | 9 |
| EXAMINE INBOX | 9 |
| EXAMINE Trash | 9 |
| LOGOUT | 9 |
| UNSELECT | 10 |
Scenario: Not possible to delete from All Mail and expunge does nothing
Given there are messages in mailbox "INBOX" for "user"
| id | from | to | subject | body |
| 1 | john.doe@mail.com | user@pm.me | subj1 | body1 |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "All Mail"
When IMAP client marks message seq "1" as deleted
Then IMAP response is "IMAP error: NO operation not allowed for 'All Mail' folder"
And mailbox "All Mail" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | subj1 |
When IMAP client sends expunge
Then IMAP response is "OK"
And mailbox "All Mail" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | subj1 |
Scenario: Expunge specific message only
Given there are 5 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client marks message seq "1" as deleted
Then IMAP response is "OK"
When IMAP client marks message seq "2" as deleted
Then IMAP response is "OK"
When IMAP client sends command "UID EXPUNGE 1"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 4 messages
And message "2" in "INBOX" for "user" is marked as deleted

View File

@ -0,0 +1,51 @@
Feature: IMAP remove messages from Trash
Background:
Given there is connected user "user"
And there is "user" with mailbox "Folders/mbox"
And there is "user" with mailbox "Labels/label"
Scenario Outline: Message in Trash/Spam and some other label is not permanently deleted
Given there are messages in mailbox "<mailbox>" for "user"
| id | from | to | subject | body |
| 1 | john.doe@mail.com | user@pm.me | foo | hello |
| 2 | jane.doe@mail.com | name@pm.me | bar | world |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "<mailbox>"
When IMAP client copies message seq "2" to "Labels/label"
Then IMAP response is "OK"
When IMAP client marks message seq "2" as deleted
Then IMAP response is "OK"
And mailbox "<mailbox>" for "user" has 2 messages
And mailbox "All Mail" for "user" has 2 messages
And mailbox "Labels/label" for "user" has 1 messages
When IMAP client sends expunge
Then IMAP response is "OK"
And mailbox "<mailbox>" for "user" has 1 messages
And mailbox "All Mail" for "user" has 2 messages
And mailbox "Labels/label" for "user" has 1 messages
Examples:
| mailbox |
| Spam |
| Trash |
Scenario Outline: Message in Trash/Spam only is permanently deleted
Given there are messages in mailbox "<mailbox>" for "user"
| id | from | to | subject | body |
| 1 | john.doe@mail.com | user@pm.me | foo | hello |
| 2 | jane.doe@mail.com | name@pm.me | bar | world |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "<mailbox>"
When IMAP client marks message seq "2" as deleted
Then IMAP response is "OK"
And mailbox "<mailbox>" for "user" has 2 messages
And mailbox "All Mail" for "user" has 2 messages
When IMAP client sends expunge
Then IMAP response is "OK"
And mailbox "<mailbox>" for "user" has 1 messages
And mailbox "All Mail" for "user" has 1 messages
Examples:
| mailbox |
| Spam |
| Trash |

View File

@ -0,0 +1,47 @@
Feature: IMAP operations with Drafts
Background:
Given there is connected user "user"
And there are messages in mailbox "Drafts" for "user"
| id | from | subject | body |
| msg1 | Lionel Richie <lionel@richie.com> | RE: Hello, is it me you looking for? | Nope |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Drafts"
Scenario: Draft subject updated locally
Scenario: Draft recipient updated locally
Scenario: Draft body updated locally
@ignore-live
Scenario: Draft subject updated on server side
@ignore-live
Scenario: Draft recipient updated on server side
@ignore-live
Scenario: Draft body and size updated on server side
When IMAP client fetches body of UID "1"
Then IMAP response is "OK"
Then IMAP response contains "Nope"
When IMAP client sends command "UID FETCH 1 RFC822.SIZE"
Then IMAP response is "OK"
Then IMAP response contains "538"
When IMAP client sends command "UID FETCH 1 BODYSTRUCTURE"
Then IMAP response is "OK"
Then IMAP response contains "4 14"
Given the body of draft "msg1" for "user" has changed to "Yes I am"
And the event loop of "user" loops once
And mailbox "Drafts" for "user" has 1 messages
When IMAP client fetches body of UID "2"
Then IMAP response is "OK"
Then IMAP response contains "Yes I am"
Then IMAP response does not contain "Nope"
When IMAP client sends command "UID FETCH 2 RFC822.SIZE"
Then IMAP response is "OK"
Then IMAP response contains "542"
When IMAP client sends command "UID FETCH 2 BODYSTRUCTURE"
Then IMAP response is "OK"
Then IMAP response contains "8 14"

View File

@ -0,0 +1,191 @@
Feature: IMAP fetch messages
Background:
Given there is connected user "user"
And there is "user" with mailbox "Folders/mbox"
Scenario: Fetch of inbox
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches "1:*"
Then IMAP response is "OK"
And IMAP response has 10 messages
Scenario: Fetch of inbox by UID
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches by UID "1:*"
Then IMAP response is "OK"
And IMAP response has 10 messages
Scenario: Fetch first few messages of inbox
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches "1:5"
Then IMAP response is "OK"
And IMAP response has 5 messages
Scenario: Fetch first few messages of inbox by UID
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches by UID "1:5"
Then IMAP response is "OK"
And IMAP response has 5 messages
Scenario: Fetch last few messages of inbox using wildcard
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches "6:*"
Then IMAP response is "OK"
And IMAP response has 5 messages
Scenario: Fetch last few messages of inbox using wildcard by UID
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches by UID "6:*"
Then IMAP response is "OK"
And IMAP response has 5 messages
Scenario: Fetch last message of inbox using wildcard
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches "*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch last message of inbox using wildcard by UID
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches by UID "*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch backwards range using wildcard
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches "*:1"
Then IMAP response is "OK"
And IMAP response has 10 messages
Scenario: Fetch backwards range using wildcard by UID
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches by UID "*:1"
Then IMAP response is "OK"
And IMAP response has 10 messages
Scenario: Fetch overshot range using wildcard returns last message
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches "20:*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch overshot range using wildcard by UID returns last message
Given there are 10 messages in mailbox "INBOX" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches by UID "20:*"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Fetch of custom mailbox
Given there are 10 messages in mailbox "Folders/mbox" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Folders/mbox"
When IMAP client fetches "1:*"
Then IMAP response is "OK"
And IMAP response has 10 messages
# This test is wrong! RFC says it should return "BAD" (GODT-1153).
Scenario Outline: Fetch range of empty mailbox
Given there is IMAP client logged in as "user"
And there is IMAP client selected in "Folders/mbox"
When IMAP client fetches "<range>"
Then IMAP response is "OK"
And IMAP response has 0 messages
When IMAP client fetches by UID "<range>"
Then IMAP response is "OK"
And IMAP response has 0 messages
Examples:
| range |
| 1 |
| 1,5,6 |
| 1:* |
| * |
@ignore-live
Scenario: Fetch of big mailbox
Given there are 100 messages in mailbox "Folders/mbox" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Folders/mbox"
When IMAP client fetches "1:*"
Then IMAP response is "OK"
And IMAP response has 100 messages
Scenario: Fetch of big mailbox by UID
Given there are 100 messages in mailbox "Folders/mbox" for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Folders/mbox"
When IMAP client fetches by UID "1:*"
Then IMAP response is "OK"
And IMAP response has 100 messages
Scenario: Fetch returns also messages that are marked as deleted
Given there are messages in mailbox "Folders/mbox" for "user"
| from | to | subject | body | read | starred | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | false | false | false |
| jane.doe@mail.com | name@pm.me | bar | world | true | true | true |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Folders/mbox"
When IMAP client fetches "1:*"
Then IMAP response is "OK"
And IMAP response has 2 message
Scenario: Fetch by UID returns also messages that are marked as deleted
Given there are messages in mailbox "Folders/mbox" for "user"
| from | to | subject | body | read | starred | deleted |
| john.doe@mail.com | user@pm.me | foo | hello | false | false | false |
| jane.doe@mail.com | name@pm.me | bar | world | true | true | true |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Folders/mbox"
When IMAP client fetches by UID "1:*"
Then IMAP response is "OK"
And IMAP response has 2 message
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 | 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"
Then IMAP response is "OK"
And IMAP response contains "\nDate: Fri, 13 Aug 1982"
And IMAP response contains "\nX-Pm-Date: Thu, 01 Jan 1970"
And IMAP response contains "\nX-Original-Date: Sun, 20 Jul 1969"
# We had bug to incorectly set empty date, so let's make sure
# there is no reference anywhere in the response.
And IMAP response does not contain "\nDate: Thu, 01 Jan 1970"
Scenario: Fetch of message which was deleted without event processed
Given there are 10 messages in mailbox "INBOX" for "user"
And message "5" was deleted forever without event processed for "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
When IMAP client fetches bodies "1:*"
Then IMAP response is "NO"
When IMAP client fetches bodies "1:*"
Then IMAP response is "OK"
And IMAP response has 9 messages

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

@ -0,0 +1,220 @@
Feature: IMAP import messages
Background:
Given there is connected user "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
Scenario: Import message with double charset in content type
When IMAP client imports message to "INBOX"
"""
From: Bridge Test <bridgetest@pm.test>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Message with double charset in content type
Content-Type: text/plain; charset=utf-8; charset=utf-8
Content-Disposition: inline
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
Hello
"""
Then IMAP response is "OK"
# I could not find any RFC why this is not valid. But for now our parser is not able to process it.
@ignore
Scenario: Import message with attachment name encoded by RFC 2047 without quoting
When IMAP client imports message to "INBOX"
"""
From: Bridge Test <bridgetest@pm.test>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Message with attachment name encoded by RFC 2047 without quoting
Content-type: multipart/mixed; boundary="boundary"
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
--boundary
Content-Type: text/plain
Hello
--boundary
Content-Type: application/pdf; name==?US-ASCII?Q?filename?=
Content-Disposition: attachment; filename==?US-ASCII?Q?filename?=
somebytes
--boundary--
"""
Then IMAP response is "OK"
Scenario: Import message as latin1 without content type
When IMAP client imports message to "INBOX" with encoding "latin1"
"""
From: Bridge Test <bridgetest@pm.test>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Message in latin1 without content type
Content-Disposition: inline
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
Hello íááá
"""
Then IMAP response is "OK"
Scenario: Import message as latin1 with content type
When IMAP client imports message to "INBOX" with encoding "latin1"
"""
From: Bridge Test <bridgetest@pm.test>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Message in latin1 with content type
Content-Disposition: inline
Content-Type: text/plain; charset=latin1
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
Hello íááá
"""
Then IMAP response is "OK"
Scenario: Import message as latin1 with wrong content type
When IMAP client imports message to "INBOX" with encoding "latin1"
"""
From: Bridge Test <bridgetest@pm.test>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Message in latin1 with wrong content type
Content-Disposition: inline
Content-Type: text/plain; charset=KOI8R
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
Hello íááá
"""
Then IMAP response is "OK"
Scenario: Import received message to Sent
When IMAP client imports message to "Sent"
"""
From: Foo <foo@example.com>
To: Bridge Test <bridgetest@pm.test>
Subject: Hello
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
Hello
"""
Then IMAP response is "OK"
And API mailbox "Sent" for "user" has 0 message
And API mailbox "INBOX" for "user" has 1 message
Scenario: Import non-received message to Inbox
When IMAP client imports message to "INBOX"
"""
From: Foo <foo@example.com>
To: Bridge Test <bridgetest@pm.test>
Subject: Hello
Hello
"""
Then IMAP response is "OK"
And API mailbox "INBOX" for "user" has 0 message
And API mailbox "Sent" for "user" has 1 message
Scenario Outline: Import message without sender
When IMAP client imports message to "<mailbox>"
"""
To: Lionel Richie <lionel@richie.com>
Subject: RE: Hello, is it me you looking for?
Nope.
"""
Then IMAP response is "OK"
And API mailbox "<mailbox>" for "user" has 1 message
Examples:
| mailbox |
| Drafts |
| Archive |
| Sent |
Scenario: Import embedded message
When IMAP client imports message to "INBOX"
"""
From: Foo <foo@example.com>
To: Bridge Test <bridgetest@pm.test>
Subject: Embedded message
Content-Type: multipart/mixed; boundary="boundary"
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
This is a multi-part message in MIME format.
--boundary
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
--boundary
Content-Type: message/rfc822; name="embedded.eml"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="embedded.eml"
From: Bar <bar@example.com>
To: Bridge Test <bridgetest@pm.test>
Subject: (No Subject)
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
hello
--boundary--
"""
Then IMAP response is "OK"
# We cannot control internal IDs on live server.
@ignore-live
Scenario: Import existing message
When IMAP client imports message to "INBOX"
"""
From: Foo <foo@example.com>
To: Bridge Test <bridgetest@pm.test>
Subject: Hello
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
X-Pm-Internal-Id: 1
Hello
"""
Then IMAP response is "OK \[APPENDUID \d 1\] APPEND completed"
When IMAP client imports message to "INBOX"
"""
From: Foo <foo@example.com>
To: Bridge Test <bridgetest@pm.test>
Subject: Hello
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
X-Pm-Internal-Id: 1
Hello
"""
Then IMAP response is "OK \[APPENDUID \d 1\] APPEND completed"
Scenario: Import message to All Mail
When IMAP client imports message to "All Mail"
"""
From: Foo <from1@pm.me>
To: Bridge Test <to1@pm.me>
Subject: subj1
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
body1
"""
Then IMAP response is "OK \[APPENDUID \d 1\] APPEND completed"
Then mailbox "Archive" for "user" has messages
| from | to | subject | body
| from1@pm.me | to1@pm.me | subj1 | body1
And API mailbox "Archive" for "user" has 1 message
And mailbox "All Mail" for "user" has messages
| from | to | subject | body
| from1@pm.me | to1@pm.me | subj1 | body1
And API mailbox "All Mail" for "user" has 1 message

View File

@ -0,0 +1,98 @@
Feature: IMAP move messages
Background:
Given there is connected user "user"
And there is "user" with mailbox "Folders/folder"
And there is "user" with mailbox "Labels/label"
And there is "user" with mailbox "Labels/label2"
And there are messages in mailbox "INBOX" for "user"
| from | to | subject | body |
| john.doe@mail.com | user@pm.me | foo | hello |
| jane.doe@mail.com | name@pm.me | bar | world |
And there are messages in mailbox "Sent" for "user"
| from | to | subject | body |
| john.doe@mail.com | user@pm.me | response | hello |
And there are messages in mailbox "Labels/label2" for "user"
| from | to | subject | body |
| john.doe@mail.com | user@pm.me | baz | hello |
And there is IMAP client logged in as "user"
Scenario: Move message from inbox (folder) to folder
Given there is IMAP client selected in "INBOX"
When IMAP client moves message seq "1" to "Folders/folder"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has messages
| from | to | subject |
| jane.doe@mail.com | name@pm.me | bar |
And mailbox "Folders/folder" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
And API endpoint "PUT /mail/v4/messages/label" is called
And API endpoint "PUT /mail/v4/messages/unlabel" is not called
Scenario: Move all messages from inbox to folder
Given there is IMAP client selected in "INBOX"
When IMAP client moves message seq "1:*" to "Folders/folder"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 0 messages
And mailbox "Folders/folder" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
| jane.doe@mail.com | name@pm.me | bar |
And API endpoint "PUT /mail/v4/messages/label" is called
And API endpoint "PUT /mail/v4/messages/unlabel" is not called
Scenario: Move message from folder to label (keeps in folder)
Given there is IMAP client selected in "INBOX"
When IMAP client moves message seq "1" to "Labels/label"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
| jane.doe@mail.com | name@pm.me | bar |
And mailbox "Labels/label" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
And API endpoint "PUT /mail/v4/messages/label" is called
And API endpoint "PUT /mail/v4/messages/unlabel" is not called
Scenario: Move message from label to folder
Given there is IMAP client selected in "Labels/label2"
When IMAP client moves message seq "1" to "Folders/folder"
Then IMAP response is "OK"
And mailbox "Labels/label2" for "user" has 0 messages
And mailbox "Folders/folder" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | baz |
And API endpoint "PUT /mail/v4/messages/label" is called
And API endpoint "PUT /mail/v4/messages/unlabel" is called
Scenario: Move message from label to label
Given there is IMAP client selected in "Labels/label2"
When IMAP client moves message seq "1" to "Labels/label"
Then IMAP response is "OK"
And mailbox "Labels/label2" for "user" has 0 messages
And mailbox "Labels/label" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | baz |
And API endpoint "PUT /mail/v4/messages/label" is called
And API endpoint "PUT /mail/v4/messages/unlabel" is called
Scenario: Move message from All Mail is not possible
Given there is IMAP client selected in "All Mail"
When IMAP client moves message seq "1" to "Folders/folder"
Then IMAP response is "NO move from All Mail is not allowed"
And mailbox "All Mail" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
| jane.doe@mail.com | name@pm.me | bar |
And mailbox "Folders/folder" for "user" has 0 messages
Scenario: Move message from Inbox to Sent is not possible
Given there is IMAP client selected in "INBOX"
When IMAP client moves message seq "1" to "Sent"
Then IMAP response is "move from Inbox to Sent is not allowed"
Scenario: Move message from Sent to Inbox is not possible
Given there is IMAP client selected in "Sent"
When IMAP client moves message seq "1" to "INBOX"
Then IMAP response is "move from Sent to Inbox is not allowed"

View File

@ -0,0 +1,80 @@
# IMAP clients can move message to local folder (setting \Deleted flag)
# and then move it back (IMAP client does not remember the message,
# so instead removing the flag it imports duplicate message).
# Regular IMAP server would keep the message twice and later EXPUNGE would
# not delete the message (EXPUNGE would delete the original message and
# the new duplicate one would stay). Both Bridge and API detects duplicates;
# therefore we need to remove \Deleted flag if IMAP client re-imports.
Feature: IMAP move message out to and back from local folder
Background:
Given there is connected user "user"
Given there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
Scenario: Mark message as deleted and re-append again
When IMAP client imports message to "INBOX"
"""
From: <john.doe@mail.com>
To: <user@pm.me>
Subject: foo
Date: Mon, 02 Jan 2006 15:04:05 +0000
Message-Id: <msgID>
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
hello
"""
Then IMAP response is "OK"
When IMAP client marks message seq "1" as deleted
Then IMAP response is "OK"
When IMAP client imports message to "INBOX"
"""
From: <john.doe@mail.com>
To: <user@pm.me>
Subject: foo
Date: Mon, 02 Jan 2006 15:04:05 +0000
Message-Id: <msgID>
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
hello
"""
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 1 message
And mailbox "INBOX" for "user" has messages
| from | to | subject | deleted |
| john.doe@mail.com | user@pm.me | foo | false |
# We cannot control ID generation on API.
@ignore-live
Scenario: Mark internal message as deleted and re-append again
# Each message has different subject so if the ID generations on fake API
# changes, test will fail because not even external ID mechanism will work.
When IMAP client imports message to "INBOX"
"""
From: <john.doe@mail.com>
To: <user@pm.me>
Subject: foo
Date: Mon, 02 Jan 2006 15:04:05 +0000
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
hello
"""
Then IMAP response is "OK"
When IMAP client marks message seq "1" as deleted
Then IMAP response is "OK"
# Fake API generates for the first message simple ID 1.
When IMAP client imports message to "INBOX"
"""
From: <john.doe@mail.com>
To: <user@pm.me>
Subject: bar
Date: Mon, 02 Jan 2006 15:04:05 +0000
X-Pm-Internal-Id: 1
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
hello
"""
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 1 message
And mailbox "INBOX" for "user" has messages
| from | to | subject | deleted |
| john.doe@mail.com | user@pm.me | foo | false |

View File

@ -0,0 +1,77 @@
Feature: IMAP move messages by append and delete (without MOVE support, e.g., Outlook)
Background:
Given there is connected user "user"
And there is "user" with mailbox "Folders/mbox"
And there is IMAP client "source" logged in as "user"
And there is IMAP client "target" logged in as "user"
Scenario Outline: Move message from <srcMailbox> to <dstMailbox> by <order>
Given there are messages in mailbox "<srcMailbox>" for "user"
| id | from | to | subject | body |
| 1 | sndr1@pm.me | rcvr1@pm.me | subj1 | body1 |
| 2 | sndr2@pm.me | rcvr2@pm.me | subj2 | body2 |
And there is IMAP client "source" selected in "<srcMailbox>"
And there is IMAP client "target" selected in "<dstMailbox>"
When IMAP clients "source" and "target" move message seq "2" of "user" to "<dstMailbox>" by <order>
Then IMAP response to "source" is "OK"
Then IMAP response to "target" is "OK"
And mailbox "<dstMailbox>" for "user" has 1 messages
And mailbox "<dstMailbox>" for "user" has messages
| from | to | subject |
| sndr2@pm.me | rcvr2@pm.me | subj2 |
And mailbox "<srcMailbox>" for "user" has 1 messages
And mailbox "<srcMailbox>" for "user" has messages
| from | to | subject |
| sndr1@pm.me | rcvr1@pm.me | subj1 |
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 |
Scenario Outline: Move message from <mailbox> to All Mail by <order>
Given there are messages in mailbox "<mailbox>" for "user"
| id | from | to | subject | body |
| 1 | john.doe@mail.com | user@pm.me | subj1 | body1 |
| 2 | john.doe@mail.com | name@pm.me | subj2 | body2 |
And there is IMAP client "source" selected in "<mailbox>"
And there is IMAP client "target" selected in "All Mail"
When IMAP clients "source" and "target" move message seq "2" of "user" to "All Mail" by <order>
Then IMAP response to "source" is "OK"
Then IMAP response to "target" is "OK"
And mailbox "<mailbox>" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | subj1 |
And mailbox "All Mail" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | subj1 |
| john.doe@mail.com | name@pm.me | subj2 |
Examples:
| mailbox | order |
| INBOX | APPEND DELETE EXPUNGE |
| Archive | APPEND DELETE EXPUNGE |
| Trash | APPEND DELETE EXPUNGE |
| Spam | APPEND DELETE EXPUNGE |
| INBOX | DELETE APPEND EXPUNGE |
| Archive | DELETE APPEND EXPUNGE |
| Trash | DELETE APPEND EXPUNGE |
| Spam | DELETE APPEND EXPUNGE |
| INBOX | DELETE EXPUNGE APPEND |
| Archive | DELETE EXPUNGE APPEND |
| Trash | DELETE EXPUNGE APPEND |
| Spam | DELETE EXPUNGE APPEND |

View File

@ -0,0 +1,90 @@
Feature: IMAP search messages
Background:
Given there is connected user "user"
Given there are messages in mailbox "INBOX" for "user"
| from | to | cc | subject | read | starred | deleted | body |
| john.doe@email.com | user@pm.me | | foo | false | false | false | hello |
| jane.doe@email.com | user@pm.me | name@pm.me | bar | true | true | false | world |
| jane.doe@email.com | name@pm.me | | baz | true | false | true | bye |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
Scenario: Search by Sequence numbers
When IMAP client searches for "1"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1[^0-9]*$"
Scenario: Search by UID
When IMAP client searches for "UID 2"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 2[^0-9]*$"
Scenario: Search by Sequence numbers and UID
When IMAP client searches for "1 UID 1"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1[^0-9]*$"
Scenario: Search by Sequence numbers and UID without match
When IMAP client searches for "1 UID 2"
Then IMAP response is "OK"
And IMAP response contains "SEARCH[^0-9]*$"
Scenario: Search by Subject
When IMAP client searches for "SUBJECT foo"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1[^0-9]*$"
Scenario: Search by From
When IMAP client searches for "FROM jane.doe@email.com"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 2 3[^0-9]*$"
Scenario: Search by To
When IMAP client searches for "TO user@pm.me"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1 2[^0-9]*$"
Scenario: Search by CC
When IMAP client searches for "CC name@pm.me"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 2[^0-9]*$"
Scenario: Search flagged messages
When IMAP client searches for "FLAGGED"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 2[^0-9]*$"
Scenario: Search not flagged messages
When IMAP client searches for "UNFLAGGED"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1 3[^0-9]*$"
Scenario: Search seen messages
When IMAP client searches for "SEEN"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 2 3[^0-9]*$"
Scenario: Search unseen messages
When IMAP client searches for "UNSEEN"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1[^0-9]*$"
Scenario: Search deleted messages
When IMAP client searches for "DELETED"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 3[^0-9]*$"
Scenario: Search undeleted messages
When IMAP client searches for "UNDELETED"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1 2[^0-9]*$"
Scenario: Search recent messages
When IMAP client searches for "RECENT"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1 2 3[^0-9]*$"
Scenario: Search by more criterias
When IMAP client searches for "SUBJECT baz TO name@pm.me SEEN UNFLAGGED"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 3[^0-9]*$"

View File

@ -0,0 +1,81 @@
Feature: IMAP update messages
Background:
Given there is connected user "user"
And there are messages in mailbox "INBOX" for "user"
| id | from | to | subject | body | read | starred | deleted |
| 1 | john.doe@mail.com | user@pm.me | foo | hello | false | false | false |
| 2 | jane.doe@mail.com | name@pm.me | bar | world | true | true | false |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
Scenario: Mark message as read
When IMAP client marks message seq "1" as read
Then IMAP response is "OK"
And message "1" in "INBOX" for "user" is marked as read
And message "1" in "INBOX" for "user" is marked as unstarred
Scenario: Mark message as unread
When IMAP client marks message seq "2" as unread
Then IMAP response is "OK"
And message "2" in "INBOX" for "user" is marked as unread
And message "2" in "INBOX" for "user" is marked as starred
Scenario: Mark message as starred
Then message "1" in "INBOX" for "user" is marked as unread
And message "1" in "INBOX" for "user" is marked as unstarred
When IMAP client marks message seq "1" as starred
Then IMAP response is "OK"
And message "1" in "INBOX" for "user" is marked as unread
And message "1" in "INBOX" for "user" is marked as starred
Scenario: Mark message as unstarred
When IMAP client marks message seq "2" as unstarred
Then IMAP response is "OK"
And message "2" in "INBOX" for "user" is marked as read
And message "2" in "INBOX" for "user" is marked as unstarred
Scenario: Mark message as read and starred
When IMAP client marks message seq "1" with "\Seen \Flagged"
Then IMAP response is "OK"
And message "1" in "INBOX" for "user" is marked as read
And message "1" in "INBOX" for "user" is marked as starred
Scenario: Mark message as read only
When IMAP client marks message seq "2" with "\Seen"
Then IMAP response is "OK"
And message "2" in "INBOX" for "user" is marked as read
# Unstarred because we set flags without \Starred.
And message "2" in "INBOX" for "user" is marked as unstarred
Scenario: Mark message as spam only
When IMAP client marks message seq "2" with "Junk"
Then IMAP response is "OK"
# Unread and unstarred because we set flags without \Seen and \Starred.
And message "1" in "Spam" for "user" is marked as unread
And message "1" in "Spam" for "user" is marked as unstarred
Scenario: Mark message as deleted
# Mark message as Starred so we can check that mark as Deleted is not
# tempering with Starred flag
When IMAP client marks message seq "2" as starred
Then IMAP response is "OK"
When IMAP client marks message seq "2" as deleted
Then IMAP response is "OK"
And message "2" in "INBOX" for "user" is marked as read
And message "2" in "INBOX" for "user" is marked as starred
And message "2" in "INBOX" for "user" is marked as deleted
Scenario: Mark message as undeleted
When IMAP client marks message seq "2" as undeleted
Then IMAP response is "OK"
And message "2" in "INBOX" for "user" is marked as read
And message "2" in "INBOX" for "user" is marked as starred
And message "2" in "INBOX" for "user" is marked as undeleted
Scenario: Mark message as deleted only
When IMAP client marks message seq "2" with "\Deleted"
Then IMAP response is "OK"
And message "2" in "INBOX" for "user" is marked as unread
And message "2" in "INBOX" for "user" is marked as unstarred
And message "2" in "INBOX" for "user" is marked as deleted

View File

@ -0,0 +1,62 @@
Feature: IMAP update messages in Spam folder
Background:
Given there is connected user "user"
# Messages are inserted in opposite way to keep increasing ID.
# Sequence numbers are then opposite than listed above.
And there are messages in mailbox "Spam" for "user"
| id | from | to | subject | body | read | starred | deleted |
| 1 | john.doe@mail.com | user@pm.me | foo | hello | false | false | false |
| 2 | jane.doe@mail.com | name@pm.me | bar | world | true | true | false |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Spam"
Scenario: Mark message as read only
When IMAP client marks message seq "1" with "\Seen"
Then IMAP response is "OK"
And message "1" in "Spam" for "user" is marked as read
And message "1" in "Spam" for "user" is marked as unstarred
And API mailbox "Spam" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
| jane.doe@mail.com | name@pm.me | bar |
Scenario Outline: Move from Spam to INBOX when client <operation> <flag>
When IMAP client <operation> flags "<flag>" <suffix> message seq "1"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 1 messages
And mailbox "INBOX" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | foo |
And mailbox "Spam" for "user" has 1 messages
And mailbox "Spam" for "user" has messages
| from | to | subject |
| jane.doe@mail.com | name@pm.me | bar |
Examples:
| operation | suffix | flag |
| adds | to | nojunk |
| adds | to | NoJunk |
| removes | from | junk |
| removes | from | Junk |
| removes | from | $Junk |
Scenario Outline: Do not move from Archive to INBOX when client <operation> <flag>
Given there are messages in mailbox "Archive" for "user"
| id | from | to | subject | body | read | starred | deleted |
| 1 | john.doe@mail.com | user@pm.me | Archived | hello | false | false | false |
And there is IMAP client selected in "Archive"
When IMAP client <operation> flags "<flag>" <suffix> message seq "1"
Then IMAP response is "OK"
And mailbox "INBOX" for "user" has 0 messages
And mailbox "Archive" for "user" has 1 messages
And mailbox "Archive" for "user" has messages
| from | to | subject |
| john.doe@mail.com | user@pm.me | Archived |
Examples:
| operation | suffix | flag |
| adds | to | nojunk |
| adds | to | NoJunk |
| removes | from | junk |
| removes | from | Junk |
| removes | from | $Junk |

View File

@ -0,0 +1,26 @@
Feature: User agent
Background:
Given there is connected user "user"
Scenario: Get user agent
Given there is IMAP client logged in as "user"
Then API user-agent is "UnknownClient/0.0.1 ([GOOS])"
When IMAP client sends ID with argument:
"""
"name" "Foo" "version" "1.4.0"
"""
Then API user-agent is "Foo/1.4.0 ([GOOS])"
Scenario: Update user agent
Given there is IMAP client logged in as "user"
Then API user-agent is "UnknownClient/0.0.1 ([GOOS])"
When IMAP client sends ID with argument:
"""
"name" "Foo" "version" "1.4.0"
"""
Then API user-agent is "Foo/1.4.0 ([GOOS])"
When IMAP client sends ID with argument:
"""
"name" "Bar" "version" "4.2.0"
"""
Then API user-agent is "Bar/4.2.0 ([GOOS])"

View File

@ -0,0 +1,65 @@
Feature: SMTP auth
Scenario: Ask EHLO
Given there is connected user "user"
When SMTP client sends "EHLO example.com"
Then SMTP response is "OK"
Scenario: Authenticates successfully and EHLO successfully
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "EHLO example.com"
Then SMTP response is "OK"
Scenario: Authenticates with bad password
Given there is connected user "user"
When SMTP client authenticates "user" with bad password
Then SMTP response is "SMTP error: 454 4.7.0 backend/credentials: incorrect password"
Scenario: Authenticates with disconnected user
Given there is disconnected user "user"
When SMTP client authenticates "user"
Then SMTP response is "SMTP error: 454 4.7.0 account is logged out, use the app to login again"
Scenario: Authenticates with no user
When SMTP client authenticates with username "user@pm.me" and password "bridgepassword"
Then SMTP response is "SMTP error: 454 4.7.0 user user@pm.me not found"
Scenario: Authenticates with capital letter
Given there is connected user "userAddressWithCapitalLetter"
When SMTP client authenticates "userAddressWithCapitalLetter"
Then SMTP response is "OK"
Scenario: Authenticates with more addresses - primary one
Given there is connected user "userMoreAddresses"
When SMTP client authenticates "userMoreAddresses" with address "primary"
Then SMTP response is "OK"
Scenario: Authenticates with more addresses - secondary one
Given there is connected user "userMoreAddresses"
When SMTP client authenticates "userMoreAddresses" with address "secondary"
Then SMTP response is "OK"
Scenario: Authenticates with more addresses - disabled address
Given there is connected user "userMoreAddresses"
When SMTP client authenticates "userMoreAddresses" with address "disabled"
Then SMTP response is "SMTP error: 454 4.7.0 user .* not found"
@ignore-live
Scenario: Authenticates with secondary address of account with disabled primary address
Given there is connected user "userDisabledPrimaryAddress"
When SMTP client authenticates "userDisabledPrimaryAddress" with address "secondary"
Then SMTP response is "OK"
Scenario: Authenticates two users
Given there is connected user "user"
And there is connected user "userMoreAddresses"
When SMTP client "smtp1" authenticates "user"
Then SMTP response to "smtp1" is "OK"
When SMTP client "smtp2" authenticates "userMoreAddresses" with address "primary"
Then SMTP response to "smtp2" is "OK"
Scenario: Logs out user
Given there is connected user "user"
When SMTP client logs out
Then SMTP response is "OK"

View File

@ -0,0 +1,90 @@
Feature: SMTP initiation
Scenario: Empty FROM
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "MAIL FROM:<>"
Then SMTP response is "OK"
Scenario: Send without FROM and TO
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "DATA"
Then SMTP response is "SMTP error: 502 5.5.1 Missing RCPT TO command."
Scenario: Reset is the same as without FROM and TO
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "MAIL FROM:<[userAddress]>"
Then SMTP response is "OK"
When SMTP client sends "RCPT TO:<user@pm.me>"
Then SMTP response is "OK"
When SMTP client sends "RSET"
Then SMTP response is "OK"
When SMTP client sends "DATA"
Then SMTP response is "SMTP error: 502 5.5.1 Missing RCPT TO command"
Scenario: Send without FROM
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "RCPT TO:<user@pm.me>"
Then SMTP response is "SMTP error: 502 5.5.1 Missing MAIL FROM command."
Scenario: Send without TO
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "MAIL FROM:<[userAddress]>"
Then SMTP response is "OK"
When SMTP client sends "DATA"
Then SMTP response is "SMTP error: 502 5.5.1 Missing RCPT TO command."
Scenario: Send with empty FROM
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "MAIL FROM:<>"
Then SMTP response is "OK"
When SMTP client sends "RCPT TO:<user@pm.me>"
Then SMTP response is "OK"
When SMTP client sends "DATA"
Then SMTP response is "OK"
When SMTP client sends "hello\r\n."
Then SMTP response is "SMTP error: 554 5.0.0 Error: transaction failed, blame it on the weather: missing return path"
Scenario: Send with empty TO
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "MAIL FROM:<[userAddress]>"
Then SMTP response is "OK"
When SMTP client sends "RCPT TO:<>"
Then SMTP response is "OK"
When SMTP client sends "DATA"
Then SMTP response is "OK"
When SMTP client sends "hello\r\n."
Then SMTP response is "SMTP error: 554 5.0.0 Error: transaction failed, blame it on the weather: missing recipient"
Scenario: Allow BODY parameter of MAIL FROM command
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "MAIL FROM:<[userAddress]> BODY=7BIT"
Then SMTP response is "OK"
Scenario: Allow AUTH parameter of MAIL FROM command
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "MAIL FROM:<[userAddress]> AUTH=<>"
Then SMTP response is "OK"
Scenario: FROM not owned by user
Given there is connected user "user"
When SMTP client authenticates "user"
Then SMTP response is "OK"
When SMTP client sends "MAIL FROM:<user@pm.test>"
Then SMTP response is "SMTP error: 451 4.0.0 backend: invalid return path: not owned by user"

View File

@ -0,0 +1,69 @@
Feature: SMTP with bcc
Background:
Given there is connected user "user"
And there is SMTP client logged in as "user"
Scenario: Send message to address in to and bcc
When SMTP client sends message with bcc "bridgetest2@protonmail.com"
"""
Subject: hello
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
hello
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | hello |
And message is sent with API call
"""
{
"Message": {
"Subject": "hello",
"ToList": [
{
"Address": "bridgetest@protonmail.com",
"Name": "Internal Bridge"
}
],
"CCList": [],
"BCCList": [
{
"Address": "bridgetest2@protonmail.com"
}
]
}
}
"""
Scenario: Send message only to bcc
When SMTP client sends message with bcc "bridgetest@protonmail.com"
"""
Subject: hello
From: Bridge Test <[userAddress]>
hello
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | | hello |
And message is sent with API call
"""
{
"Message": {
"Subject": "hello",
"ToList": [],
"CCList": [],
"BCCList": [
{
"Address": "bridgetest@protonmail.com"
}
]
}
}
"""

View File

@ -0,0 +1,38 @@
Feature: SMTP sending embedded message
Scenario: Send it
Given there is connected user "user"
And there is SMTP client logged in as "user"
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Embedded message
Content-Type: multipart/mixed; boundary="boundary"
This is a multi-part message in MIME format.
--boundary
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
--boundary
Content-Type: message/rfc822; name="embedded.eml"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="embedded.eml"
From: Bar <bar@example.com>
To: Bridge Test <bridgetest@pm.test>
Subject: (No Subject)
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
hello
--boundary--
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Embedded message |

View File

@ -0,0 +1,52 @@
Feature: SMTP wrong messages
Background:
Given there is connected user "user"
And there is SMTP client logged in as "user"
Scenario: Message with attachment and wrong boundaries
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: With attachment (wrong boundaries)
Content-Type: multipart/related; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=utf-8
This is body of mail with attachment
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: attachment; filename=outline-light-instagram-48.png
Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com>
Content-Transfer-Encoding: base64
Content-Type: image/png
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
"""
Then SMTP response is "SMTP error: 554 5.0.0 Error: transaction failed, blame it on the weather: failed to create new parser: unexpected EOF"
Scenario: Invalid from
When SMTP client sends message
"""
From: Bridge Test <bridgetest@pm.test>
To: Internal Bridge <bridgetest@protonmail.com>
hello
"""
Then SMTP response is "SMTP error: 554 5.0.0 Error: transaction failed, blame it on the weather: backend: invalid email address: not owned by user"

View File

@ -0,0 +1,335 @@
Feature: SMTP sending of HTML messages
Background:
Given there is connected user "user"
And there is SMTP client logged in as "user"
Scenario: HTML message to external account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: HTML text external
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=utf-8
In-Reply-To: <base64hashOfSomeMessage@protonmail.internalid>
<html><body>This is body of <b>HTML mail</b> without attachment<body></html>
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | HTML text external |
And message is sent with API call
"""
{
"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 sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
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
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body text="#000000" bgcolor="#FFFFFF">
<p><br>
</p>
<p>Behold! An inline <img moz-do-not-send="false"
src="cid:part1.D96BFAE9.E2E1CAE3@protonmail.com" alt=""
width="24" height="24"><br>
</p>
</body>
</html>
--------------61FA22A41A3F46E8E90EF528
Content-Type: image/gif; name="email-action-left.gif"
Content-Transfer-Encoding: base64
Content-ID: <part1.D96BFAE9.E2E1CAE3@protonmail.com>
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 SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Html Inline External |
And message is sent with API call
"""
{
"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 sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
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
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body text="#000000" bgcolor="#FFFFFF">
<p><br>
</p>
<p>Behold! An inline <img moz-do-not-send="false"
src="cid:part1.D96BFAE9.E2E1CAE3@protonmail.com" alt=""
width="24" height="24"><br>
</p>
</body>
</html>
--------------61FA22A41A3F46E8E90EF528
Content-Type: image/gif; name="email-action-left.gif"
Content-Transfer-Encoding: base64
Content-ID: <part1.D96BFAE9.E2E1CAE3@protonmail.com>
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 SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Html Inline Alternative Internal |
And message is sent with API call
"""
{
"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 sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
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
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body text="#000000" bgcolor="#FFFFFF">
<p><br>
</p>
<p>Behold! An inline <img moz-do-not-send="false"
src="cid:part1.D96BFAE9.E2E1CAE3@protonmail.com" alt=""
width="24" height="24"><br>
</p>
</body>
</html>
--------------61FA22A41A3F46E8E90EF528
Content-Type: image/gif; name="email-action-left.gif"
Content-Transfer-Encoding: base64
Content-ID: <part1.D96BFAE9.E2E1CAE3@protonmail.com>
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 SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Html Inline Alternative External |
And message is sent with API call
"""
{
"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 sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: HTML text external
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=utf-8
In-Reply-To: <base64hashOfSomeMessage@protonmail.internalid>
<html><body>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<body></html>
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | HTML text external |
And message is sent with API call
"""
{
"Message": {
"Subject": "HTML text external",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/html"
}
}
"""

View File

@ -0,0 +1,210 @@
Feature: SMTP sending of HTML messages with attachments
Background:
Given there is connected user "user"
And there is SMTP client logged in as "user"
Scenario: HTML message with attachment to internal account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: HTML with attachment internal
Content-Type: multipart/related; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=utf-8
<html><body>This is body of <b>HTML mail</b> with attachment<body></html>
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: attachment; filename=outline-light-instagram-48.png
Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com>
Content-Transfer-Encoding: base64
Content-Type: image/png
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | HTML with attachment internal |
And message is sent with API call
"""
{
"Message": {
"Subject": "HTML with attachment internal",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "bridgetest@protonmail.com",
"Name": "Internal Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/html"
}
}
"""
Scenario: HTML message with attachment to external account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: HTML with attachment external PGP
Content-Type: multipart/mixed; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=utf-8
<html><body>This is body of <b>HTML mail</b> with attachment<body></html>
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: attachment; filename=outline-light-instagram-48.png
Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com>
Content-Transfer-Encoding: base64
Content-Type: image/png
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | HTML with attachment external PGP |
And message is sent with API call
"""
{
"Message": {
"Subject": "HTML with attachment external PGP",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/html"
}
}
"""
Scenario: Alternative plain and HTML message with rfc822 attachment
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: Alternative plain and HTML with rfc822 attachment
Content-Type: multipart/mixed; boundary=main-parts
This is a multipart message in MIME format
--main-parts
Content-Type: multipart/alternative; boundary=alternatives
--alternatives
Content-Type: text/plain
There is an attachment
--alternatives
Content-Type: text/html
<html><body>There <b>is</b> an attachment<body></html>
--alternatives--
--main-parts
Content-Type: message/rfc822
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment
Received: from mx1.opensuse.org (mx1.infra.opensuse.org [192.168.47.95]) by
mailman3.infra.opensuse.org (Postfix) with ESMTP id 38BE2AC3 for
<factory@lists.opensuse.org>; Sun, 11 Jul 2021 19:50:34 +0000 (UTC)
From: "Bob " <Bob@something.net>
Sender: "Bob" <Bob@gmail.com>
To: "opensuse-factory" <opensuse-factory@opensuse.org>
Cc: "Bob" <Bob@something.net>
References: <y6ZUV5yEyOVQHETZRmi1GFe-Xumzct7QcLpGoSsi1MefGaoovfrUqdkmQ5gM6uySZ7JPIJhDkPJFDqHS1fb_mQ==@protonmail.internalid>
Subject: VirtualBox problems with kernel 5.13
Date: Sun, 11 Jul 2021 21:50:25 +0200
Message-ID: <71672e5f-24a2-c79f-03cc-4c923eb1790b@lwfinger.net>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
X-Mailer: Microsoft Outlook 16.0
List-Unsubscribe: <mailto:factory-leave@lists.opensuse.org>
Content-Language: en-us
List-Help: <mailto:factory-request@lists.opensuse.org?subject=help>
List-Subscribe: <mailto:factory-join@lists.opensuse.org>
Thread-Index: AQFWvbNSAqFOch49YPlLU4eJWPObaQK2iKDq
I am writing this message as openSUSE's maintainer of VirtualBox.
Nearly every update of the Linux kernel to a new 5.X version breaks =
VirtualBox.
Bob
--main-parts--
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Alternative plain and HTML with rfc822 attachment |
And message is sent with API call
"""
{
"Message": {
"Subject": "Alternative plain and HTML with rfc822 attachment",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/html"
}
}
"""

View File

@ -0,0 +1,38 @@
Feature: SMTP sending with mixed case address
Background:
Given there is connected user "user"
And there is SMTP client logged in as "user"
Scenario: Mixed sender case in sender address
When SMTP client sends message
"""
From: Bridge Test <[userAddress|capitalize]>
To: Internal Bridge <bridgetest@protonmail.com>
hello
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | |
And message is sent with API call
"""
{
"Message": {
"Subject": "",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "bridgetest@protonmail.com",
"Name": "Internal Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""

View File

@ -0,0 +1,324 @@
Feature: SMTP sending of plain messages
Background:
Given there is connected user "user"
And there is SMTP client logged in as "user"
Scenario: Only from and to headers to internal account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
hello
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | |
And message is sent with API call
"""
{
"Message": {
"Subject": "",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "bridgetest@protonmail.com",
"Name": "Internal Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: Only from and to headers to external account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
hello
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | |
And message is sent with API call
"""
{
"Message": {
"Subject": "",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: Basic message to internal account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Plain text internal
Content-Disposition: inline
Content-Type: text/plain; charset=utf-8
This is body of mail 👋
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Plain text internal |
And message is sent with API call
"""
{
"Message": {
"Subject": "Plain text internal",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "bridgetest@protonmail.com",
"Name": "Internal Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: Basic message to external account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: Plain text external
Content-Disposition: inline
Content-Type: text/plain; charset=utf-8
This is body of mail 👋
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain text external |
And message is sent with API call
"""
{
"Message": {
"Subject": "Plain text external",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: Message without charset is utf8
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: Plain text no charset external
Content-Disposition: inline
Content-Type: text/plain;
This is body of mail without charset. Please assume utf8
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain text no charset external |
And message is sent with API call
"""
{
"Message": {
"Subject": "Plain text no charset external",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: Message without charset is base64-encoded latin1
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: Plain text no charset external
Content-Disposition: inline
Content-Type: text/plain;
Content-Transfer-Encoding: base64
dGhpcyBpcyBpbiBsYXRpbjEgYW5kIHRoZXJlIGFyZSBsb3RzIG9mIGVzIHdpdGggYWNjZW50czog
6enp6enp6enp6enp6enp
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain text no charset external |
And message is sent with API call
"""
{
"Message": {
"Subject": "Plain text no charset external",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: Message without charset and content is detected as HTML
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: Plain, no charset, no content, external
Content-Disposition: inline
Content-Type: text/plain;
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain, no charset, no content, external |
And message is sent with API call
"""
{
"Message": {
"Subject": "Plain, no charset, no content, external",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: RCPT does not contain all CC
When SMTP client sends "MAIL FROM:<[userAddress]>"
Then SMTP response is "OK"
When SMTP client sends "RCPT TO:<bridgetest@protonmail.com>"
Then SMTP response is "OK"
When SMTP client sends "DATA"
Then SMTP response is "OK"
When SMTP client sends
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
CC: Internal Bridge 2 <bridgetest2@protonmail.com>
Content-Type: text/plain
Subject: RCPT-CC test
This is CC missing in RCPT test. Have a nice day!
.
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | cc | subject |
| [userAddress] | bridgetest@protonmail.com | bridgetest2@protonmail.com | RCPT-CC test |
And message is sent with API call
"""
{
"Message": {
"Subject": "RCPT-CC test",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "bridgetest@protonmail.com",
"Name": "Internal Bridge"
}
],
"CCList": [
{
"Address": "bridgetest2@protonmail.com",
"Name": "Internal Bridge 2"
}
],
"BCCList": []
}
}
"""
And packages are sent with API call
"""
{
"Packages":[
{
"Addresses":{
"bridgetest@protonmail.com":{
"Type":1
},
"bridgetest2@protonmail.com":{
"Type":1
}
},
"Type":1,
"MIMEType":"text/plain"
}
]
}
"""

View File

@ -0,0 +1,187 @@
Feature: SMTP sending of plain messages with attachments
Background:
Given there is connected user "user"
And there is SMTP client logged in as "user"
Scenario: Basic message with attachment to internal account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Plain with attachment
Content-Type: multipart/related; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=utf-8
This is body of mail with attachment
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: attachment; filename=outline-light-instagram-48.png
Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com>
Content-Transfer-Encoding: base64
Content-Type: image/png
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Plain with attachment |
And message is sent with API call
"""
{
"Message": {
"Subject": "Plain with attachment",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "bridgetest@protonmail.com",
"Name": "Internal Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: Plain message with attachment to external account
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: Plain with attachment external
Content-Type: multipart/related; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=utf-8
This is body of mail with attachment
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: attachment; filename=outline-light-instagram-48.png
Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com>
Content-Transfer-Encoding: base64
Content-Type: image/png
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain with attachment external |
And message is sent with API call
"""
{
"Message": {
"Subject": "Plain with attachment external",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""
Scenario: Plain message with attachment to two external accounts
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge 1 <pm.bridge.qa@gmail.com>
CC: External Bridge 2 <bridgeqa@seznam.cz>
Subject: Plain with attachment external PGP and external CC
Content-Type: multipart/mixed; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=utf-8
This is body of mail with attachment
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Disposition: attachment; filename=outline-light-instagram-48.png
Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com>
Content-Transfer-Encoding: base64
Content-Type: image/png
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | cc | subject |
| [userAddress] | pm.bridge.qa@gmail.com | bridgeqa@seznam.cz | Plain with attachment external PGP and external CC |
And message is sent with API call
"""
{
"Message": {
"Subject": "Plain with attachment external PGP and external CC",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge 1"
}
],
"CCList": [
{
"Address": "bridgeqa@seznam.cz",
"Name": "External Bridge 2"
}
],
"BCCList": [],
"MIMEType": "text/plain"
}
}
"""

View File

@ -0,0 +1,45 @@
Feature: SMTP sending the same message twice
Background:
Given there is connected user "user"
And there is SMTP client logged in as "user"
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Hello
World
"""
Then SMTP response is "OK"
Scenario: The exact same message is not sent twice
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Hello
World
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Hello |
Scenario: Slight change means different message and is sent twice
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Hello.
World
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Hello |
| [userAddress] | bridgetest@protonmail.com | Hello. |

View File

@ -0,0 +1,50 @@
Feature: SMTP sending with APPENDing to Sent
Background:
Given there is connected user "user"
And there is IMAP client logged in as "user"
And there is IMAP client selected in "Sent"
And there is SMTP client logged in as "user"
Scenario: Send message and append to Sent
# First do sending.
When SMTP client sends message
"""
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Manual send and append
Message-ID: bridgemessage42
hello
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has 1 messages
And mailbox "Sent" for "user" has messages
| externalid | from | to | subject |
| bridgemessage42 | [userAddress] | bridgetest@protonmail.com | Manual send and append |
And message is sent with API call
"""
{
"Message": {
"Subject": "Manual send and append",
"ExternalID": "bridgemessage42"
}
}
"""
# Then simulate manual append to Sent mailbox - message should be detected as a duplicate.
When IMAP client imports message to "Sent"
"""
To: Internal Bridge <bridgetest@protonmail.com>
Subject: Manual send and append
Message-ID: bridgemessage42
hello
"""
Then IMAP response is "OK"
And mailbox "Sent" for "user" has 1 messages
# Check that the external ID was not lost in the process.
When IMAP client sends command "FETCH 1 body.peek[header]"
Then IMAP response is "OK"
And IMAP response contains "bridgemessage42"

View File

@ -0,0 +1,71 @@
Feature: SMTP sending two messages
Scenario: Send two messages in one connection
Given there is connected user "user"
And there is SMTP client logged in as "user"
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
hello
"""
Then SMTP response is "OK"
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
world
"""
Then SMTP response is "OK"
Scenario: Send to two addresses
Given there is connected user "userMoreAddresses"
And there is "userMoreAddresses" in "split" address mode
And there is SMTP client "smtp1" logged in as "userMoreAddresses" with address "primary"
And there is SMTP client "smtp2" logged in as "userMoreAddresses" with address "secondary"
When SMTP client "smtp1" sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
hello
"""
Then SMTP response to "smtp1" is "OK"
When SMTP client "smtp2" sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
world
"""
Then SMTP response to "smtp2" is "OK"
Scenario: Send to two users
Given there is connected user "user"
And there is connected user "userMoreAddresses"
And there is SMTP client "smtp1" logged in as "user"
And there is SMTP client "smtp2" logged in as "userMoreAddresses"
When SMTP client "smtp1" sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
hello
"""
Then SMTP response to "smtp1" is "OK"
When SMTP client "smtp2" sends message
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
world
"""
Then SMTP response to "smtp2" is "OK"

View File

@ -0,0 +1,90 @@
Feature: Start bridge
Scenario: Start with connected user, database file and internet connection
Given there is user "user" which just logged in
And there is database file for "user"
When bridge starts
Then "user" is connected
And "user" has loaded store
And "user" has running event loop
And "user" has non-zero space
Scenario: Start with connected user, database file and no internet connection
Given there is user "user" which just logged in
And there is database file for "user"
And there is no internet connection
When bridge starts
Then "user" is connected
And "user" has loaded store
And "user" has running event loop
And "user" has zero space
Scenario: Start with connected user, no database file and internet connection
Given there is user "user" which just logged in
And there is no database file for "user"
When bridge starts
Then "user" is connected
And "user" has loaded store
And "user" has running event loop
And "user" has non-zero space
Scenario: Start with connected user, no database file and no internet connection
Given there is user "user" which just logged in
And there is no database file for "user"
And there is no internet connection
When bridge starts
Then "user" is connected
And "user" does not have loaded store
And "user" does not have running event loop
And the internet connection is restored
And 5 seconds pass
Then "user" is connected
And "user" has loaded store
And "user" has running event loop
And "user" has non-zero space
Scenario: Start with disconnected user, database file and internet connection
Given there is disconnected user "user"
And there is database file for "user"
When bridge starts
Then "user" is disconnected
And "user" has loaded store
And "user" does not have running event loop
And "user" has zero space
Scenario: Start with disconnected user, database file and no internet connection
Given there is disconnected user "user"
And there is database file for "user"
And there is no internet connection
When bridge starts
Then "user" is disconnected
And "user" has loaded store
And "user" does not have running event loop
And "user" has zero space
Scenario: Start with disconnected user, no database file and internet connection
Given there is disconnected user "user"
And there is no database file for "user"
When bridge starts
Then "user" is disconnected
And "user" does not have loaded store
And "user" does not have running event loop
And "user" has zero space
Scenario: Start with disconnected user, no database file and no internet connection
Given there is disconnected user "user"
And there is no database file for "user"
And there is no internet connection
When bridge starts
Then "user" is disconnected
And "user" does not have loaded store
And "user" does not have running event loop
And "user" has zero space
Scenario: Start with connected user, database file and internet connection, but no write access to credentials
Given there is user "user" which just logged in
And credentials are locked
And there is database file for "user"
When bridge starts
Then "user" is connected
When IMAP client authenticates "user"
Then IMAP response is "NO"

View File

@ -0,0 +1,76 @@
Feature: Address mode
Background:
Given there is connected user "userMoreAddresses"
And there is "userMoreAddresses" with mailbox "Folders/mbox"
And there are messages in mailbox "Folders/mbox" for "userMoreAddresses"
| from | to | subject |
| john.doe@mail.com | [primary] | foo |
| jane.doe@mail.com | [secondary] | bar |
Scenario: All messages in one mailbox with combined mode
Given there is "userMoreAddresses" in "combined" address mode
Then mailbox "Folders/mbox" for address "primary" of "userMoreAddresses" has messages
| from | to | subject |
| john.doe@mail.com | [primary] | foo |
| jane.doe@mail.com | [secondary] | bar |
Scenario: Messages separated in more mailboxes with split mode
Given there is "userMoreAddresses" in "split" address mode
Then mailbox "Folders/mbox" for address "primary" of "userMoreAddresses" has messages
| from | to | subject |
| john.doe@mail.com | [primary] | foo |
And mailbox "Folders/mbox" for address "secondary" of "userMoreAddresses" has messages
| from | to | subject |
| jane.doe@mail.com | [secondary] | bar |
Scenario: Switch address mode from combined to split mode
Given there is "userMoreAddresses" in "combined" address mode
When "userMoreAddresses" changes the address mode
Then last response is "OK"
And "userMoreAddresses" has address mode in "split" mode
And mailbox "Folders/mbox" for address "primary" of "userMoreAddresses" has messages
| from | to | subject |
| john.doe@mail.com | [primary] | foo |
And mailbox "Folders/mbox" for address "secondary" of "userMoreAddresses" has messages
| from | to | subject |
| jane.doe@mail.com | [secondary] | bar |
Scenario: Switch address mode from split to combined mode
Given there is "userMoreAddresses" in "split" address mode
When "userMoreAddresses" changes the address mode
Then last response is "OK"
And "userMoreAddresses" has address mode in "combined" mode
And mailbox "Folders/mbox" for address "primary" of "userMoreAddresses" has messages
| from | to | subject |
| john.doe@mail.com | [primary] | foo |
| jane.doe@mail.com | [secondary] | bar |
Scenario: Make secondary address primary in combined mode
Given there is "userMoreAddresses" in "combined" address mode
Then mailbox "Folders/mbox" for address "primary" of "userMoreAddresses" has messages
| from | to | subject |
| john.doe@mail.com | [primary] | foo |
| jane.doe@mail.com | [secondary] | bar |
When "userMoreAddresses" swaps address "primary" with address "secondary"
And "userMoreAddresses" receives an address event
Then mailbox "Folders/mbox" for address "primary" of "userMoreAddresses" has messages
| from | to | subject |
| john.doe@mail.com | [secondary] | foo |
| jane.doe@mail.com | [primary] | bar |
Scenario: Make secondary address primary in split mode
Given there is "userMoreAddresses" in "split" address mode
Then mailbox "Folders/mbox" for address "primary" of "userMoreAddresses" has messages
| from | to | subject |
| john.doe@mail.com | [primary] | foo |
And mailbox "Folders/mbox" for address "secondary" of "userMoreAddresses" has messages
| from | to | subject |
| jane.doe@mail.com | [secondary] | bar |
When "userMoreAddresses" swaps address "primary" with address "secondary"
And "userMoreAddresses" receives an address event
Then mailbox "Folders/mbox" for address "primary" of "userMoreAddresses" has messages
| from | to | subject |
| jane.doe@mail.com | [primary] | bar |
And mailbox "Folders/mbox" for address "secondary" of "userMoreAddresses" has messages
| from | to | subject |
| john.doe@mail.com | [secondary] | foo |

View File

@ -0,0 +1,45 @@
Feature: Delete user
@ignore-live-auth
Scenario: Deleting connected user
Given there is user "user" which just logged in
When user deletes "user"
Then last response is "OK"
And "user" has database file
@ignore-live-auth
Scenario: Deleting connected user with cache
Given there is user "user" which just logged in
When user deletes "user" with cache
Then last response is "OK"
And "user" does not have database file
@ignore-live-auth
Scenario: Deleting connected user without database file
Given there is user "user" which just logged in
And there is no database file for "user"
When user deletes "user" with cache
Then last response is "OK"
# THIS IS BLOCKED BY ANTI-ABUSE
@ignore-live
Scenario: Deleting disconnected user
Given there is disconnected user "user"
When user deletes "user"
Then last response is "OK"
And "user" has database file
# THIS IS BLOCKED BY ANTI-ABUSE
@ignore-live
Scenario: Deleting disconnected user with cache
Given there is disconnected user "user"
When user deletes "user" with cache
Then last response is "OK"
And "user" does not have database file
# THIS IS BLOCKED BY ANTI-ABUSE
@ignore-live
Scenario: Deleting disconnected user without database file
Given there is disconnected user "user"
And there is no database file for "user"
When user deletes "user" with cache
Then last response is "OK"

View File

@ -0,0 +1,78 @@
Feature: Login for the first time
@ignore-live-auth
Scenario: Normal login
Given there is user "user"
When "user" logs in
Then last response is "OK"
And "user" is connected
And "user" has database file
And "user" has running event loop
And "user" has non-zero space
@ignore-live
Scenario: Login with bad username
When "user" logs in with bad password
Then last response is "failed to login: Incorrect login credentials. Please try again"
@ignore-live
Scenario: Login with bad password
Given there is user "user"
When "user" logs in with bad password
Then last response is "failed to login: Incorrect login credentials. Please try again"
@ignore-live-auth
Scenario: Login without internet connection
Given there is no internet connection
When "user" logs in
Then last response is "failed to login: no internet connection"
@ignore-live
Scenario: Login user with 2FA
Given there is user "user2fa"
When "user2fa" logs in
Then last response is "OK"
And "user2fa" is connected
And "user2fa" has database file
And "user2fa" has running event loop
And "user2fa" has non-zero space
@ignore-live-auth
Scenario: Login user with capital letters in address
Given there is user "userAddressWithCapitalLetter"
When "userAddressWithCapitalLetter" logs in
Then last response is "OK"
And "userAddressWithCapitalLetter" is connected
And "userAddressWithCapitalLetter" has database file
And "userAddressWithCapitalLetter" has running event loop
And "userAddressWithCapitalLetter" has non-zero space
@ignore-live-auth
Scenario: Login user with more addresses
Given there is user "userMoreAddresses"
When "userMoreAddresses" logs in
Then last response is "OK"
And "userMoreAddresses" is connected
And "userMoreAddresses" has database file
And "userMoreAddresses" has running event loop
And "userMoreAddresses" has non-zero space
@ignore-live
Scenario: Login user with disabled primary address
Given there is user "userDisabledPrimaryAddress"
When "userDisabledPrimaryAddress" logs in
Then last response is "OK"
And "userDisabledPrimaryAddress" is connected
And "userDisabledPrimaryAddress" has database file
And "userDisabledPrimaryAddress" has running event loop
And "userDisabledPrimaryAddress" has non-zero space
@ignore-live-auth
Scenario: Login two users
Given there is user "user"
And there is user "userMoreAddresses"
When "user" logs in
Then last response is "OK"
And "user" is connected
When "userMoreAddresses" logs in
Then last response is "OK"
And "userMoreAddresses" is connected

View File

@ -0,0 +1,41 @@
Feature: Re-login
@ignore-live-auth
Scenario: Re-login with connected user and database file
Given there is user "user" which just logged in
And there is database file for "user"
When "user" logs in
Then last response is "failed to finish login: user is already connected"
And "user" is connected
And "user" has running event loop
And "user" has non-zero space
@ignore
Scenario: Re-login with connected user and no database file
Given there is user "user" which just logged in
And there is no database file for "user"
When "user" logs in
Then last response is "failed to finish login: user is already connected"
And "user" is connected
And "user" has database file
And "user" has running event loop
@ignore-live-auth
Scenario: Re-login with disconnected user and database file
Given there is disconnected user "user"
And there is database file for "user"
When "user" logs in
Then last response is "OK"
And "user" is connected
And "user" has running event loop
And "user" has non-zero space
@ignore-live-auth
Scenario: Re-login with disconnected user and no database file
Given there is disconnected user "user"
And there is no database file for "user"
When "user" logs in
Then last response is "OK"
And "user" is connected
And "user" has database file
And "user" has running event loop
And "user" has non-zero space

View File

@ -0,0 +1,17 @@
Feature: Session deleted on API
@ignore-live
Scenario: Session revoked after start
Given there is connected user "user"
When session was revoked for "user"
And the event loop of "user" loops once
Then "user" is disconnected
@ignore-live
Scenario: Starting with revoked session
Given there is user "user" which just logged in
And session was revoked for "user"
When bridge starts
Then "user" is disconnected

View File

@ -0,0 +1,60 @@
Feature: Sync bridge
Background:
Given there is connected user "userMoreAddresses"
And there is "userMoreAddresses" with mailboxes
| Folders/one |
| Folders/two |
| Labels/three |
| Labels/four |
And there are messages for "userMoreAddresses" as follows
| address | mailboxes | messages |
| primary | INBOX,Folders/one | 1 |
| primary | Archive,Labels/three | 2 |
| primary | INBOX | 1000 |
| primary | Archive | 1200 |
| primary | Folders/one | 1400 |
| primary | Folders/two | 1600 |
| primary | Labels/three | 1800 |
| primary | Labels/four | 2000 |
| secondary | INBOX | 100 |
| secondary | Archive | 120 |
| secondary | Folders/one | 140 |
| secondary | Folders/two | 160 |
| secondary | Labels/three | 180 |
| secondary | Labels/four | 200 |
# Too heavy for live.
@ignore-live
Scenario: Sync in combined mode
And there is "userMoreAddresses" in "combined" address mode
When bridge syncs "userMoreAddresses"
Then last response is "OK"
And "userMoreAddresses" has the following messages
| mailboxes | messages |
| INBOX | 1101 |
| Archive | 1322 |
| Folders/one | 1541 |
| Folders/two | 1760 |
| Labels/three | 1982 |
| Labels/four | 2200 |
# Too heavy for live.
@ignore-live
Scenario: Sync in split mode
And there is "userMoreAddresses" in "split" address mode
When bridge syncs "userMoreAddresses"
Then last response is "OK"
And "userMoreAddresses" has the following messages
| address | mailboxes | messages |
| primary | INBOX | 1001 |
| primary | Archive | 1202 |
| primary | Folders/one | 1401 |
| primary | Folders/two | 1600 |
| primary | Labels/three | 1802 |
| primary | Labels/four | 2000 |
| secondary | INBOX | 100 |
| secondary | Archive | 120 |
| secondary | Folders/one | 140 |
| secondary | Folders/two | 160 |
| secondary | Labels/three | 180 |
| secondary | Labels/four | 200 |

36
tests/api_test.go Normal file
View File

@ -0,0 +1,36 @@
package tests
import (
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/rfc822"
"gitlab.protontech.ch/go/liteapi"
"gitlab.protontech.ch/go/liteapi/server"
)
type API interface {
SetMinAppVersion(*semver.Version)
GetHostURL() string
AddCallWatcher(func(server.Call), ...string)
AddUser(username, password, address string) (userID, addrID string, err error)
RevokeUser(userID string) error
GetLabels(userID string) ([]liteapi.Label, error)
AddLabel(userID, name string, labelType liteapi.LabelType) (string, error)
GetMessages(userID string) ([]liteapi.Message, error)
AddMessage(userID, addrID string, labelIDs []string, sender, recipient, subject, body string, mimeType rfc822.MIMEType, read, starred bool) (string, error)
Close()
}
type fakeAPI struct {
*server.Server
}
func newFakeAPI() *fakeAPI {
return &fakeAPI{
Server: server.NewTLS(),
}
}

170
tests/bdd_test.go Normal file
View File

@ -0,0 +1,170 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.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 tests
import (
"context"
"runtime"
"strings"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/v2/internal/user"
"github.com/cucumber/godog"
"github.com/stretchr/testify/require"
)
func init() {
user.DefaultEventPeriod = time.Second
user.DefaultEventJitter = time.Second
}
type scenario struct {
t *testCtx
}
func (s *scenario) reset(tb testing.TB) {
s.t = newTestCtx(tb)
}
func (s *scenario) close(tb testing.TB) {
require.NoError(tb, s.t.close(context.Background()))
}
func TestFeatures(testingT *testing.T) {
suite := godog.TestSuite{
ScenarioInitializer: func(ctx *godog.ScenarioContext) {
var s scenario
ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
s.reset(testingT)
return ctx, nil
})
ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
s.close(testingT)
return ctx, nil
})
ctx.StepContext().Before(func(ctx context.Context, st *godog.Step) (context.Context, error) {
s.t.beforeStep()
st.Text = strings.ReplaceAll(st.Text, "[GOOS]", runtime.GOOS)
return ctx, nil
})
// ==== ENVIRONMENT ====
ctx.Step(`^it succeeds$`, s.itSucceeds)
ctx.Step(`^it fails$`, s.itFails)
ctx.Step(`^it fails with error "([^"]*)"$`, s.itFailsWithError)
ctx.Step(`^the internet is turned off$`, s.internetIsTurnedOff)
ctx.Step(`^the internet is turned on$`, s.internetIsTurnedOn)
ctx.Step(`^the user agent is "([^"]*)"$`, s.theUserAgentIs)
ctx.Step(`^the value of the "([^"]*)" header in the request to "([^"]*)" is "([^"]*)"$`, s.theValueOfTheHeaderInTheRequestToIs)
// ==== BRIDGE ====
ctx.Step(`^bridge starts$`, s.bridgeStarts)
ctx.Step(`^bridge restarts$`, s.bridgeRestarts)
ctx.Step(`^bridge stops$`, s.bridgeStops)
ctx.Step(`^bridge is version "([^"]*)" and the latest available version is "([^"]*)" reachable from "([^"]*)"$`, s.bridgeVersionIsAndTheLatestAvailableVersionIsReachableFrom)
ctx.Step(`^the API requires bridge version at least "([^"]*)"$`, s.theAPIRequiresBridgeVersion)
ctx.Step(`^the user has disabled automatic updates$`, s.theUserHasDisabledAutomaticUpdates)
ctx.Step(`^the user changes the IMAP port to (\d+)$`, s.theUserChangesTheIMAPPortTo)
ctx.Step(`^the user changes the SMTP port to (\d+)$`, s.theUserChangesTheSMTPPortTo)
ctx.Step(`^the user changes the gluon path$`, s.theUserChangesTheGluonPath)
ctx.Step(`^the user reports a bug$`, s.theUserReportsABug)
ctx.Step(`^bridge sends a connection up event$`, s.bridgeSendsAConnectionUpEvent)
ctx.Step(`^bridge sends a connection down event$`, s.bridgeSendsAConnectionDownEvent)
ctx.Step(`^bridge sends a deauth event for user "([^"]*)"$`, s.bridgeSendsADeauthEventForUser)
ctx.Step(`^bridge sends sync started and finished events for user "([^"]*)"$`, s.bridgeSendsSyncStartedAndFinishedEventsForUser)
ctx.Step(`^bridge sends an update available event for version "([^"]*)"$`, s.bridgeSendsAnUpdateAvailableEventForVersion)
ctx.Step(`^bridge sends a manual update event for version "([^"]*)"$`, s.bridgeSendsAManualUpdateEventForVersion)
ctx.Step(`^bridge sends an update installed event for version "([^"]*)"$`, s.bridgeSendsAnUpdateInstalledEventForVersion)
ctx.Step(`^bridge sends an update not available event$`, s.bridgeSendsAnUpdateNotAvailableEvent)
ctx.Step(`^bridge sends a forced update event$`, s.bridgeSendsAForcedUpdateEvent)
// ==== USER ====
ctx.Step(`^there exists an account with username "([^"]*)" and password "([^"]*)"$`, s.thereExistsAnAccountWithUsernameAndPassword)
ctx.Step(`^the account "([^"]*)" has (\d+) custom folders$`, s.theAccountHasCustomFolders)
ctx.Step(`^the account "([^"]*)" has (\d+) custom labels$`, s.theAccountHasCustomLabels)
ctx.Step(`^the account "([^"]*)" has the following custom mailboxes:$`, s.theAccountHasTheFollowingCustomMailboxes)
ctx.Step(`^the account "([^"]*)" has the following messages in "([^"]*)":$`, s.theAccountHasTheFollowingMessagesInMailbox)
ctx.Step(`^the account "([^"]*)" has (\d+) messages in "([^"]*)"$`, s.theAccountHasMessagesInMailbox)
ctx.Step(`^the user logs in with username "([^"]*)" and password "([^"]*)"$`, s.userLogsInWithUsernameAndPassword)
ctx.Step(`^user "([^"]*)" logs out$`, s.userLogsOut)
ctx.Step(`^user "([^"]*)" is deleted$`, s.userIsDeleted)
ctx.Step(`^the auth of user "([^"]*)" is revoked$`, s.theAuthOfUserIsRevoked)
ctx.Step(`^user "([^"]*)" is listed and connected$`, s.userIsListedAndConnected)
ctx.Step(`^user "([^"]*)" is listed but not connected$`, s.userIsListedButNotConnected)
ctx.Step(`^user "([^"]*)" is not listed$`, s.userIsNotListed)
ctx.Step(`^user "([^"]*)" finishes syncing$`, s.userFinishesSyncing)
// ==== IMAP ====
ctx.Step(`^user "([^"]*)" connects IMAP client "([^"]*)"$`, s.userConnectsIMAPClient)
ctx.Step(`^user "([^"]*)" connects IMAP client "([^"]*)" on port (\d+)$`, s.userConnectsIMAPClientOnPort)
ctx.Step(`^user "([^"]*)" connects and authenticates IMAP client "([^"]*)"$`, s.userConnectsAndAuthenticatesIMAPClient)
ctx.Step(`^IMAP client "([^"]*)" can authenticate$`, s.imapClientCanAuthenticate)
ctx.Step(`^IMAP client "([^"]*)" cannot authenticate$`, s.imapClientCannotAuthenticate)
ctx.Step(`^IMAP client "([^"]*)" cannot authenticate with incorrect username$`, s.imapClientCannotAuthenticateWithIncorrectUsername)
ctx.Step(`^IMAP client "([^"]*)" cannot authenticate with incorrect password$`, s.imapClientCannotAuthenticateWithIncorrectPassword)
ctx.Step(`^IMAP client "([^"]*)" announces its ID with name "([^"]*)" and version "([^"]*)"$`, s.imapClientAnnouncesItsIDWithNameAndVersion)
ctx.Step(`^IMAP client "([^"]*)" creates "([^"]*)"$`, s.imapClientCreatesMailbox)
ctx.Step(`^IMAP client "([^"]*)" deletes "([^"]*)"$`, s.imapClientDeletesMailbox)
ctx.Step(`^IMAP client "([^"]*)" renames "([^"]*)" to "([^"]*)"$`, s.imapClientRenamesMailboxTo)
ctx.Step(`^IMAP client "([^"]*)" sees the following mailbox info:$`, s.imapClientSeesTheFollowingMailboxInfo)
ctx.Step(`^IMAP client "([^"]*)" sees the following mailbox info for "([^"]*)":$`, s.imapClientSeesTheFollowingMailboxInfoForMailbox)
ctx.Step(`^IMAP client "([^"]*)" sees the following mailboxes:$`, s.imapClientSeesTheFollowingMailboxes)
ctx.Step(`^IMAP client "([^"]*)" sees "([^"]*)"$`, s.imapClientSeesMailbox)
ctx.Step(`^IMAP client "([^"]*)" does not see "([^"]*)"$`, s.imapClientDoesNotSeeMailbox)
ctx.Step(`^IMAP client "([^"]*)" counts (\d+) mailboxes under "([^"]*)"$`, s.imapClientCountsMailboxesUnder)
ctx.Step(`^IMAP client "([^"]*)" selects "([^"]*)"$`, s.imapClientSelectsMailbox)
ctx.Step(`^IMAP client "([^"]*)" copies the message with subject "([^"]*)" from "([^"]*)" to "([^"]*)"$`, s.imapClientCopiesTheMessageWithSubjectFromTo)
ctx.Step(`^IMAP client "([^"]*)" copies all messages from "([^"]*)" to "([^"]*)"$`, s.imapClientCopiesAllMessagesFromTo)
ctx.Step(`^IMAP client "([^"]*)" sees the following messages in "([^"]*)":$`, s.imapClientSeesTheFollowingMessagesInMailbox)
ctx.Step(`^IMAP client "([^"]*)" eventually sees the following messages in "([^"]*)":$`, s.imapClientEventuallySeesTheFollowingMessagesInMailbox)
ctx.Step(`^IMAP client "([^"]*)" sees (\d+) messages in "([^"]*)"$`, s.imapClientSeesMessagesInMailbox)
ctx.Step(`^IMAP client "([^"]*)" eventually sees (\d+) messages in "([^"]*)"$`, s.imapClientEventuallySeesMessagesInMailbox)
ctx.Step(`^IMAP client "([^"]*)" marks message (\d+) as deleted$`, s.imapClientMarksMessageAsDeleted)
ctx.Step(`^IMAP client "([^"]*)" marks message (\d+) as not deleted$`, s.imapClientMarksMessageAsNotDeleted)
ctx.Step(`^IMAP client "([^"]*)" marks all messages as deleted$`, s.imapClientMarksAllMessagesAsDeleted)
ctx.Step(`^IMAP client "([^"]*)" sees that message (\d+) has the flag "([^"]*)"$`, s.imapClientSeesThatMessageHasTheFlag)
ctx.Step(`^IMAP client "([^"]*)" expunges$`, s.imapClientExpunges)
// ==== SMTP ====
ctx.Step(`^user "([^"]*)" connects SMTP client "([^"]*)"$`, s.userConnectsSMTPClient)
ctx.Step(`^user "([^"]*)" connects SMTP client "([^"]*)" on port (\d+)$`, s.userConnectsSMTPClientOnPort)
ctx.Step(`^user "([^"]*)" connects and authenticates SMTP client "([^"]*)"$`, s.userConnectsAndAuthenticatesSMTPClient)
ctx.Step(`^SMTP client "([^"]*)" can authenticate$`, s.smtpClientCanAuthenticate)
ctx.Step(`^SMTP client "([^"]*)" cannot authenticate$`, s.smtpClientCannotAuthenticate)
ctx.Step(`^SMTP client "([^"]*)" cannot authenticate with incorrect username$`, s.smtpClientCannotAuthenticateWithIncorrectUsername)
ctx.Step(`^SMTP client "([^"]*)" cannot authenticate with incorrect password$`, s.smtpClientCannotAuthenticateWithIncorrectPassword)
ctx.Step(`^SMTP client "([^"]*)" sends MAIL FROM "([^"]*)"$`, s.smtpClientSendsMailFrom)
ctx.Step(`^SMTP client "([^"]*)" sends RCPT TO "([^"]*)"$`, s.smtpClientSendsRcptTo)
ctx.Step(`^SMTP client "([^"]*)" sends DATA "([^"]*)"$`, s.smtpClientSendsData)
ctx.Step(`^SMTP client "([^"]*)" sends RSET$`, s.smtpClientSendsReset)
},
Options: &godog.Options{
Format: "pretty",
Paths: []string{"features"},
TestingT: testingT,
},
}
if suite.Run() != 0 {
testingT.Fatal("non-zero status returned, failed to run feature tests")
}
}

222
tests/bridge_test.go Normal file
View File

@ -0,0 +1,222 @@
package tests
import (
"context"
"errors"
"fmt"
"os"
"time"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"gitlab.protontech.ch/go/liteapi"
)
func (s *scenario) bridgeStarts() error {
return s.t.startBridge()
}
func (s *scenario) bridgeRestarts() error {
if err := s.t.stopBridge(); err != nil {
return err
}
return s.t.startBridge()
}
func (s *scenario) bridgeStops() error {
return s.t.stopBridge()
}
func (s *scenario) bridgeVersionIsAndTheLatestAvailableVersionIsReachableFrom(current, latest, minAuto string) error {
s.t.version = semver.MustParse(current)
s.t.mocks.Updater.SetLatestVersion(semver.MustParse(latest), semver.MustParse(minAuto))
return nil
}
func (s *scenario) theAPIRequiresBridgeVersion(version string) error {
s.t.api.SetMinAppVersion(semver.MustParse(version))
return nil
}
func (s *scenario) theUserChangesTheIMAPPortTo(port int) error {
return s.t.bridge.SetIMAPPort(port)
}
func (s *scenario) theUserChangesTheSMTPPortTo(port int) error {
return s.t.bridge.SetSMTPPort(port)
}
func (s *scenario) theUserChangesTheGluonPath() error {
gluonDir, err := os.MkdirTemp(s.t.dir, "gluon")
if err != nil {
return err
}
return s.t.bridge.SetGluonDir(context.Background(), gluonDir)
}
func (s *scenario) theUserHasDisabledAutomaticUpdates() error {
var started bool
if s.t.bridge == nil {
if err := s.t.startBridge(); err != nil {
return err
}
started = true
}
if err := s.t.bridge.SetAutoUpdate(false); err != nil {
return err
}
if started {
if err := s.t.stopBridge(); err != nil {
return err
}
}
return nil
}
func (s *scenario) theUserReportsABug() error {
return s.t.bridge.ReportBug(context.Background(), "osType", "osVersion", "description", "username", "email", "client", false)
}
func (s *scenario) bridgeSendsAConnectionUpEvent() error {
return try(s.t.connStatusCh, 5*time.Second, func(event events.ConnStatus) error {
if event.Status != liteapi.StatusUp {
return fmt.Errorf("expected connection up event, got %v", event.Status)
}
return nil
})
}
func (s *scenario) bridgeSendsAConnectionDownEvent() error {
return try(s.t.connStatusCh, 5*time.Second, func(event events.ConnStatus) error {
if event.Status != liteapi.StatusDown {
return fmt.Errorf("expected connection down event, got %v", event.Status)
}
return nil
})
}
func (s *scenario) bridgeSendsADeauthEventForUser(username string) error {
return try(s.t.userDeauthCh, 5*time.Second, func(event events.UserDeauth) error {
if wantUserID := s.t.getUserID(username); wantUserID != event.UserID {
return fmt.Errorf("expected deauth event for user with ID %s, got %s", wantUserID, event.UserID)
}
return nil
})
}
func (s *scenario) bridgeSendsSyncStartedAndFinishedEventsForUser(username string) error {
if err := get(s.t.syncStartedCh, func(event events.SyncStarted) error {
if wantUserID := s.t.getUserID(username); wantUserID != event.UserID {
return fmt.Errorf("expected sync started event for user with ID %s, got %s", wantUserID, event.UserID)
}
return nil
}); err != nil {
return fmt.Errorf("failed to get sync started event: %w", err)
}
if err := get(s.t.syncFinishedCh, func(event events.SyncFinished) error {
if wantUserID := s.t.getUserID(username); wantUserID != event.UserID {
return fmt.Errorf("expected sync finished event for user with ID %s, got %s", wantUserID, event.UserID)
}
return nil
}); err != nil {
return fmt.Errorf("failed to get sync finished event: %w", err)
}
return nil
}
func (s *scenario) bridgeSendsAnUpdateNotAvailableEvent() error {
return try(s.t.updateCh, 5*time.Second, func(event events.Event) error {
if event, ok := event.(events.UpdateNotAvailable); !ok {
return fmt.Errorf("expected update not available event, got %T", event)
}
return nil
})
}
func (s *scenario) bridgeSendsAnUpdateAvailableEventForVersion(version string) error {
return try(s.t.updateCh, 5*time.Second, func(event events.Event) error {
updateEvent, ok := event.(events.UpdateAvailable)
if !ok {
return fmt.Errorf("expected update available event, got %T", event)
}
if !updateEvent.CanInstall {
return errors.New("expected update event to be installable")
}
if !updateEvent.Version.Version.Equal(semver.MustParse(version)) {
return fmt.Errorf("expected update event for version %s, got %s", version, updateEvent.Version.Version)
}
return nil
})
}
func (s *scenario) bridgeSendsAManualUpdateEventForVersion(version string) error {
return try(s.t.updateCh, 5*time.Second, func(event events.Event) error {
updateEvent, ok := event.(events.UpdateAvailable)
if !ok {
return fmt.Errorf("expected manual update event, got %T", event)
}
if updateEvent.CanInstall {
return errors.New("expected update event to not be installable")
}
if !updateEvent.Version.Version.Equal(semver.MustParse(version)) {
return fmt.Errorf("expected update event for version %s, got %s", version, updateEvent.Version.Version)
}
return nil
})
}
func (s *scenario) bridgeSendsAnUpdateInstalledEventForVersion(version string) error {
return try(s.t.updateCh, 5*time.Second, func(event events.Event) error {
updateEvent, ok := event.(events.UpdateInstalled)
if !ok {
return fmt.Errorf("expected update installed event, got %T", event)
}
if !updateEvent.Version.Version.Equal(semver.MustParse(version)) {
return fmt.Errorf("expected update event for version %s, got %s", version, updateEvent.Version.Version)
}
return nil
})
}
func (s *scenario) bridgeSendsAForcedUpdateEvent() error {
return try(s.t.forcedUpdateCh, 5*time.Second, func(event events.UpdateForced) error {
return nil
})
}
func try[T any](inCh <-chan T, wait time.Duration, fn func(T) error) error {
select {
case event := <-inCh:
return fn(event)
case <-time.After(wait):
return errors.New("timeout waiting for event")
}
}
func get[T any](inCh <-chan T, fn func(T) error) error {
return fn(<-inCh)
}

78
tests/ctx_bridge_test.go Normal file
View File

@ -0,0 +1,78 @@
package tests
import (
"context"
"fmt"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/useragent"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
)
func (t *testCtx) startBridge() error {
// Bridge will enable the proxy by default at startup.
t.mocks.ProxyDialer.EXPECT().AllowProxy()
// Get the path to the vault.
vaultDir, err := t.locator.ProvideSettingsPath()
if err != nil {
return err
}
// Get the default gluon path.
gluonDir, err := t.locator.ProvideGluonPath()
if err != nil {
return err
}
// Create the vault.
vault, corrupt, err := vault.New(vaultDir, gluonDir, t.storeKey)
if err != nil {
return err
} else if corrupt {
return fmt.Errorf("vault is corrupt")
}
// Create the bridge.
bridge, err := bridge.New(
t.api.GetHostURL(),
t.locator,
vault,
useragent.New(),
t.mocks.TLSReporter,
t.mocks.ProxyDialer,
t.mocks.Autostarter,
t.mocks.Updater,
t.version,
)
if err != nil {
return err
}
// Save the bridge t.
t.bridge = bridge
// Connect the event channels.
t.connStatusCh = chToType[events.Event, events.ConnStatus](bridge.GetEvents(events.ConnStatus{}))
t.userLoginCh = chToType[events.Event, events.UserLoggedIn](bridge.GetEvents(events.UserLoggedIn{}))
t.userLogoutCh = chToType[events.Event, events.UserLoggedOut](bridge.GetEvents(events.UserLoggedOut{}))
t.userDeletedCh = chToType[events.Event, events.UserDeleted](bridge.GetEvents(events.UserDeleted{}))
t.userDeauthCh = chToType[events.Event, events.UserDeauth](bridge.GetEvents(events.UserDeauth{}))
t.syncStartedCh = chToType[events.Event, events.SyncStarted](bridge.GetEvents(events.SyncStarted{}))
t.syncFinishedCh = chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
t.forcedUpdateCh = chToType[events.Event, events.UpdateForced](bridge.GetEvents(events.UpdateForced{}))
t.updateCh, _ = bridge.GetEvents(events.UpdateAvailable{}, events.UpdateNotAvailable{}, events.UpdateInstalled{}, events.UpdateForced{})
return nil
}
func (t *testCtx) stopBridge() error {
if err := t.bridge.Close(context.Background()); err != nil {
return err
}
t.bridge = nil
return nil
}

30
tests/ctx_imap_test.go Normal file
View File

@ -0,0 +1,30 @@
package tests
import (
"fmt"
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
"github.com/emersion/go-imap/client"
)
func (t *testCtx) newIMAPClient(userID, clientID string) error {
return t.newIMAPClientOnPort(userID, clientID, t.bridge.GetIMAPPort())
}
func (t *testCtx) newIMAPClientOnPort(userID, clientID string, imapPort int) error {
client, err := client.Dial(fmt.Sprintf("%v:%d", constants.Host, imapPort))
if err != nil {
return err
}
t.imapClients[clientID] = &imapClient{
userID: userID,
client: client,
}
return nil
}
func (t *testCtx) getIMAPClient(clientID string) (string, *client.Client) {
return t.imapClients[clientID].userID, t.imapClients[clientID].client
}

30
tests/ctx_smtp_test.go Normal file
View File

@ -0,0 +1,30 @@
package tests
import (
"fmt"
"net/smtp"
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
)
func (t *testCtx) newSMTPClient(userID, clientID string) error {
return t.newSMTPClientOnPort(userID, clientID, t.bridge.GetSMTPPort())
}
func (t *testCtx) newSMTPClientOnPort(userID, clientID string, imapPort int) error {
client, err := smtp.Dial(fmt.Sprintf("%v:%d", constants.Host, imapPort))
if err != nil {
return err
}
t.smtpClients[clientID] = &smtpClient{
userID: userID,
client: client,
}
return nil
}
func (t *testCtx) getSMTPClient(clientID string) (string, *smtp.Client) {
return t.smtpClients[clientID].userID, t.smtpClients[clientID].client
}

210
tests/ctx_test.go Normal file
View File

@ -0,0 +1,210 @@
package tests
import (
"context"
"fmt"
"net/smtp"
"testing"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
"github.com/bradenaw/juniper/xslices"
"github.com/emersion/go-imap/client"
"gitlab.protontech.ch/go/liteapi"
"gitlab.protontech.ch/go/liteapi/server"
)
var defaultVersion = semver.MustParse("1.0.0")
type testCtx struct {
// These are the objects supporting the test.
dir string
api API
locator *locations.Locations
storeKey []byte
version *semver.Version
mocks *bridge.Mocks
// bridge holds the bridge app under test.
bridge *bridge.Bridge
// These channels hold events of various types coming from bridge.
connStatusCh <-chan events.ConnStatus
userLoginCh <-chan events.UserLoggedIn
userLogoutCh <-chan events.UserLoggedOut
userDeletedCh <-chan events.UserDeleted
userDeauthCh <-chan events.UserDeauth
syncStartedCh <-chan events.SyncStarted
syncFinishedCh <-chan events.SyncFinished
forcedUpdateCh <-chan events.UpdateForced
updateCh <-chan events.Event
// These maps hold expected userIDByName, their primary addresses and bridge passwords.
userIDByName map[string]string
userAddrByID map[string]string
userPassByID map[string]string
addrIDByID map[string]string
// These are the IMAP and SMTP clients used to connect to bridge.
imapClients map[string]*imapClient
smtpClients map[string]*smtpClient
// calls holds calls made to the API during each step of the test.
calls [][]server.Call
// errors holds test-related errors encountered while running test steps.
errors [][]error
}
type imapClient struct {
userID string
client *client.Client
}
type smtpClient struct {
userID string
client *smtp.Client
}
func newTestCtx(tb testing.TB) *testCtx {
ctx := &testCtx{
dir: tb.TempDir(),
api: newFakeAPI(),
locator: locations.New(bridge.NewTestLocationsProvider(tb), "config-name"),
storeKey: []byte("super-secret-store-key"),
mocks: bridge.NewMocks(tb, defaultVersion, defaultVersion),
version: defaultVersion,
userIDByName: make(map[string]string),
userAddrByID: make(map[string]string),
userPassByID: make(map[string]string),
addrIDByID: make(map[string]string),
imapClients: make(map[string]*imapClient),
smtpClients: make(map[string]*smtpClient),
}
ctx.api.AddCallWatcher(func(call server.Call) {
ctx.calls[len(ctx.calls)-1] = append(ctx.calls[len(ctx.calls)-1], call)
})
return ctx
}
func (t *testCtx) beforeStep() {
t.calls = append(t.calls, nil)
t.errors = append(t.errors, nil)
}
func (t *testCtx) getUserID(username string) string {
return t.userIDByName[username]
}
func (t *testCtx) setUserID(username, userID string) {
t.userIDByName[username] = userID
}
func (t *testCtx) getUserAddr(userID string) string {
return t.userAddrByID[userID]
}
func (t *testCtx) setUserAddr(userID, addr string) {
t.userAddrByID[userID] = addr
}
func (t *testCtx) getUserPass(userID string) string {
return t.userPassByID[userID]
}
func (t *testCtx) setUserPass(userID, pass string) {
t.userPassByID[userID] = pass
}
func (t *testCtx) getAddrID(userID string) string {
return t.addrIDByID[userID]
}
func (t *testCtx) setAddrID(userID, addrID string) {
t.addrIDByID[userID] = addrID
}
func (t *testCtx) getMBoxID(userID string, name string) string {
labels, err := t.api.GetLabels(userID)
if err != nil {
panic(err)
}
idx := xslices.IndexFunc(labels, func(label liteapi.Label) bool {
return label.Name == name
})
if idx < 0 {
panic(fmt.Errorf("label %q not found", name))
}
return labels[idx].ID
}
func (t *testCtx) getLastCall(path string) (server.Call, error) {
calls := t.calls[len(t.calls)-2]
if len(calls) == 0 {
return server.Call{}, fmt.Errorf("no calls made")
}
for _, call := range calls {
if call.URL.Path == path {
return call, nil
}
}
return calls[len(calls)-1], nil
}
func (t *testCtx) pushError(err error) {
t.errors[len(t.errors)-1] = append(t.errors[len(t.errors)-1], err)
}
func (t *testCtx) getLastError() error {
errors := t.errors[len(t.errors)-2]
if len(errors) == 0 {
return nil
}
return errors[len(errors)-1]
}
func (t *testCtx) close(ctx context.Context) error {
for _, client := range t.imapClients {
if err := client.client.Logout(); err != nil {
return err
}
}
if t.bridge != nil {
if err := t.bridge.Close(ctx); err != nil {
return err
}
}
t.api.Close()
return nil
}
func chToType[In, Out any](inCh <-chan In, done any) <-chan Out {
outCh := make(chan Out)
go func() {
defer close(outCh)
for in := range inCh {
outCh <- any(in).(Out)
}
}()
return outCh
}

66
tests/environment_test.go Normal file
View File

@ -0,0 +1,66 @@
package tests
import (
"fmt"
"strings"
)
func (s *scenario) itSucceeds() error {
if err := s.t.getLastError(); err != nil {
return fmt.Errorf("expected nil, got error %v", err)
}
return nil
}
func (s *scenario) itFails() error {
if err := s.t.getLastError(); err == nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) itFailsWithError(wantErr string) error {
err := s.t.getLastError()
if err == nil {
return fmt.Errorf("expected error, got nil")
}
if haveErr := err.Error(); !strings.Contains(haveErr, wantErr) {
return fmt.Errorf("expected error %q, got %q", wantErr, haveErr)
}
return nil
}
func (s *scenario) internetIsTurnedOff() error {
s.t.mocks.TLSDialer.SetCanDial(false)
return nil
}
func (s *scenario) internetIsTurnedOn() error {
s.t.mocks.TLSDialer.SetCanDial(true)
return nil
}
func (s *scenario) theUserAgentIs(userAgent string) error {
if haveUserAgent := s.t.bridge.GetCurrentUserAgent(); haveUserAgent != userAgent {
return fmt.Errorf("have user agent %q, want %q", haveUserAgent, userAgent)
}
return nil
}
func (s *scenario) theValueOfTheHeaderInTheRequestToIs(key, path, value string) error {
call, err := s.t.getLastCall(path)
if err != nil {
return err
}
if haveKey := call.Request.Header.Get(key); haveKey != value {
return fmt.Errorf("have header %q, want %q", haveKey, value)
}
return nil
}

View File

@ -0,0 +1,22 @@
Feature: A user can authenticate an IMAP client
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
Scenario: IMAP client can authenticate successfully
When user "user@pm.me" connects IMAP client "1"
Then IMAP client "1" can authenticate
Scenario: IMAP client cannot authenticate with bad username
When user "user@pm.me" connects IMAP client "1"
Then IMAP client "1" cannot authenticate with incorrect username
Scenario: IMAP client cannot authenticate with bad password
When user "user@pm.me" connects IMAP client "1"
Then IMAP client "1" cannot authenticate with incorrect password
Scenario: IMAP client cannot authenticate for disconnected user
When user "user@pm.me" logs out
And user "user@pm.me" connects IMAP client "1"
Then IMAP client "1" cannot authenticate

View File

@ -0,0 +1,15 @@
Feature: IMAP create mailbox
Background:
Given there exists an account with username "user@pm.me" and password "password"
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 "1"
Scenario: Create folder
When IMAP client "1" creates "Folders/mbox"
Then IMAP client "1" sees "Folders/mbox"
Scenario: Create label
When IMAP client "1" creates "Labels/mbox"
Then IMAP client "1" sees "Labels/mbox"

View File

@ -0,0 +1,29 @@
Feature: IMAP delete mailbox
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 |
| one | folder |
| two | folder |
| three | label |
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 "1"
Scenario: Delete folder
When IMAP client "1" deletes "Folders/one"
Then IMAP client "1" does not see "Folders/one"
But IMAP client "1" sees "Folders/two"
But IMAP client "1" sees "Labels/three"
Scenario: Delete label
When IMAP client "1" deletes "Labels/three"
Then IMAP client "1" does not see "Labels/three"
But IMAP client "1" sees "Folders/one"
But IMAP client "1" sees "Folders/two"
Scenario: Deleting system mailbox is not possible
When IMAP client "1" deletes "INBOX"
Then it fails
And IMAP client "1" sees "INBOX"

View File

@ -0,0 +1,19 @@
Feature: IMAP get mailbox info
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 |
| one | folder |
And the account "user@pm.me" has the following messages in "one":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
And user "user@pm.me" finishes syncing
Scenario: Mailbox status reports correct name, total and unread
When user "user@pm.me" connects and authenticates IMAP client "1"
Then IMAP client "1" sees the following mailbox info for "Folders/one":
| name | total | unread |
| Folders/one | 2 | 1 |

View File

@ -0,0 +1,39 @@
Feature: IMAP list mailboxes
Scenario: List mailboxes
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 |
| mbox1 | folder |
| mbox2 | label |
When 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 "1"
Then IMAP client "1" sees the following mailbox info:
| name | total | unread |
| INBOX | 0 | 0 |
| Drafts | 0 | 0 |
| Sent | 0 | 0 |
| Starred | 0 | 0 |
| Archive | 0 | 0 |
| Spam | 0 | 0 |
| Trash | 0 | 0 |
| All Mail | 0 | 0 |
| Folders | 0 | 0 |
| Folders/mbox1 | 0 | 0 |
| Labels | 0 | 0 |
| Labels/mbox2 | 0 | 0 |
Scenario: List multiple times in parallel without crash
Given there exists an account with username "user@pm.me" and password "password"
And the account "user@pm.me" has 20 custom folders
And the account "user@pm.me" has 60 custom labels
When bridge starts
And the user logs in with username "user@pm.me" and password "password"
And user "user@pm.me" finishes syncing
When user "user@pm.me" connects and authenticates IMAP client "1"
And user "user@pm.me" connects and authenticates IMAP client "2"
Then IMAP client "1" counts 20 mailboxes under "Folders"
And IMAP client "1" counts 60 mailboxes under "Labels"
Then IMAP client "2" counts 20 mailboxes under "Folders"
And IMAP client "2" counts 60 mailboxes under "Labels"

View File

@ -0,0 +1,29 @@
Feature: IMAP get mailbox info
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 |
| f1 | folder |
| l1 | label |
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 "1"
Scenario: Rename folder
When IMAP client "1" renames "Folders/f1" to "Folders/f2"
Then IMAP client "1" sees "Folders/f2"
And IMAP client "1" does not see "Folders/f1"
Scenario: Rename label
When IMAP client "1" renames "Labels/l1" to "Labels/l2"
Then IMAP client "1" sees "Labels/l2"
And IMAP client "1" does not see "Labels/l1"
Scenario: Renaming folder to label is not possible
When IMAP client "1" renames "Folders/f1" to "Labels/f2"
Then it fails
Scenario: Renaming system folder is not possible
When IMAP client "1" renames "Labels/l1" to "Folders/l2"
Then it fails

View File

@ -0,0 +1,27 @@
Feature: IMAP select mailbox
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 |
| label | label |
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 "1"
Scenario: Select inbox
When IMAP client "1" selects "INBOX"
Then it succeeds
Scenario: Select custom mailbox
When IMAP client "1" selects "Folders/mbox"
Then it succeeds
Scenario: Select custom label
When IMAP client "1" selects "Labels/label"
Then it succeeds
Scenario: Select non-existing mailbox
When IMAP client "1" selects "qwerty"
Then it fails

View File

@ -0,0 +1,61 @@
Feature: IMAP copy messages
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 |
| label | label |
And the account "user@pm.me" has the following messages in "Inbox":
| sender | recipient | subject | unread |
| john.doe@mail.com | user@pm.me | foo | false |
| jane.doe@mail.com | name@pm.me | bar | true |
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 "1"
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 |
| 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 |
| 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 |
| 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 |
| 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 |
| jane.doe@mail.com | name@pm.me | bar | true |
And IMAP client "1" sees the following messages in "Folders/mbox":
| sender | recipient | 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 |
| 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"
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 |
| 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"

View File

@ -0,0 +1,39 @@
Feature: IMAP remove messages from mailbox
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 |
| label | label |
And the account "user@pm.me" has 10 messages in "mbox"
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 "1"
Scenario: Mark message as deleted and EXPUNGE
When IMAP client "1" selects "Folders/mbox"
And IMAP client "1" marks message 2 as deleted
Then IMAP client "1" sees that message 2 has the flag "\Deleted"
When IMAP client "1" expunges
Then IMAP client "1" sees 9 messages in "Folders/mbox"
Scenario: Mark all messages as deleted and EXPUNGE
When IMAP client "1" selects "Folders/mbox"
And IMAP client "1" marks all messages as deleted
And IMAP client "1" expunges
Then IMAP client "1" sees 0 messages in "Folders/mbox"
Scenario: Mark messages as undeleted and EXPUNGE
When IMAP client "1" selects "Folders/mbox"
And IMAP client "1" marks all messages as deleted
But IMAP client "1" marks message 2 as not deleted
And IMAP client "1" marks message 3 as not deleted
When IMAP client "1" expunges
Then IMAP client "1" sees 2 messages in "Folders/mbox"
Scenario: Not possible to delete from All Mail and expunge does nothing
When IMAP client "1" selects "All Mail"
And IMAP client "1" marks message 2 as deleted
And IMAP client "1" expunges
Then IMAP client "1" eventually sees 10 messages in "All Mail"

View File

@ -0,0 +1,17 @@
Feature: Bridge can fully sync an account
Background:
Given there exists an account with username "user@pm.me" and password "password"
And the account "user@pm.me" has 20 custom folders
And the account "user@pm.me" has 60 custom labels
When bridge starts
And the user logs in with username "user@pm.me" and password "password"
And user "user@pm.me" finishes syncing
When user "user@pm.me" connects and authenticates IMAP client "1"
Then IMAP client "1" counts 20 mailboxes under "Folders"
And IMAP client "1" counts 60 mailboxes under "Labels"
Scenario: The user changes the gluon path
When the user changes the gluon path
And user "user@pm.me" connects and authenticates IMAP client "2"
Then IMAP client "2" counts 20 mailboxes under "Folders"
And IMAP client "2" counts 60 mailboxes under "Labels"

View File

@ -0,0 +1,10 @@
Feature: A user can connect an IMAP client to custom ports
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
And the user changes the IMAP port to 1144
Scenario: Authenticates successfully on custom port
When user "user@pm.me" connects IMAP client "1" on port 1144
Then IMAP client "1" can authenticate

View File

@ -0,0 +1,20 @@
Feature: The IMAP ID is propagated to bridge
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
Scenario: Initial user agent before an IMAP client announces its ID
When user "user@pm.me" connects IMAP client "1"
Then the user agent is "UnknownClient/0.0.1 ([GOOS])"
Scenario: User agent after an IMAP client announces its ID
When user "user@pm.me" connects IMAP client "1"
And IMAP client "1" announces its ID with name "name" and version "version"
Then the user agent is "name/version ([GOOS])"
Scenario: User agent is used for API calls
When user "user@pm.me" connects IMAP client "1"
And IMAP client "1" announces its ID with name "name" and version "version"
When the user reports a bug
Then the value of the "User-Agent" header in the request to "/core/v4/reports/bug" is "name/version ([GOOS])"

View File

@ -0,0 +1,22 @@
Feature: A user can authenticate an SMTP client
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
Scenario: SMTP client can authenticate successfully
When user "user@pm.me" connects SMTP client "1"
Then SMTP client "1" can authenticate
Scenario: SMTP client cannot authenticate with wrong username
When user "user@pm.me" connects SMTP client "1"
Then SMTP client "1" cannot authenticate with incorrect username
Scenario: SMTP client cannot authenticate with wrong password
When user "user@pm.me" connects SMTP client "1"
Then SMTP client "1" cannot authenticate with incorrect password
Scenario: SMTP client cannot authenticate for disconnected user
When user "user@pm.me" logs out
And user "user@pm.me" connects SMTP client "1"
Then SMTP client "1" cannot authenticate

View File

@ -0,0 +1,48 @@
Feature: SMTP initiation
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
When user "user@pm.me" connects and authenticates SMTP client "1"
Scenario: Send without first announcing FROM and TO
When SMTP client "1" sends DATA "Subject: test"
Then it fails with error "Missing RCPT TO command"
Scenario: Reset is the same as without FROM and TO
When SMTP client "1" sends MAIL FROM "<user@pm.me>"
Then it succeeds
When SMTP client "1" sends RCPT TO "<user@pm.me>"
Then it succeeds
When SMTP client "1" sends RSET
Then it succeeds
When SMTP client "1" sends DATA "Subject: test"
Then it fails with error "Missing RCPT TO command"
Scenario: Send without FROM
When SMTP client "1" sends RCPT TO "<user@pm.me>"
Then it fails with error "Missing MAIL FROM command"
Scenario: Send without TO
When SMTP client "1" sends MAIL FROM "<user@pm.me>"
Then it succeeds
When SMTP client "1" sends DATA "Subject: test"
Then it fails with error "Missing RCPT TO command"
Scenario: Send with empty FROM
When SMTP client "1" sends MAIL FROM "<>"
Then it fails with error "invalid return path"
Scenario: Send with empty TO
When SMTP client "1" sends MAIL FROM "<user@pm.me>"
Then it succeeds
When SMTP client "1" sends RCPT TO "<>"
Then it fails with error "invalid recipient"
Scenario: Allow BODY parameter of MAIL FROM command
When SMTP client "1" sends MAIL FROM "<user@pm.me> BODY=7BIT"
Then it succeeds
Scenario: FROM not owned by user
When SMTP client "1" sends MAIL FROM "<user@pm.test>"
Then it fails with error "invalid return path"

View File

@ -0,0 +1,10 @@
Feature: A user can connect an SMTP client to custom ports
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
And the user changes the SMTP port to 1144
Scenario: Authenticates successfully on custom port
When user "user@pm.me" connects SMTP client "1" on port 1144
Then SMTP client "1" can authenticate

View File

@ -0,0 +1,29 @@
Feature: Bridge checks for updates
Scenario: Update not available
Given bridge is version "2.3.0" and the latest available version is "2.3.0" reachable from "2.3.0"
When bridge starts
Then bridge sends an update not available event
Scenario: Update available without automatic updates enabled
Given bridge is version "2.3.0" and the latest available version is "2.4.0" reachable from "2.3.0"
And the user has disabled automatic updates
When bridge starts
Then bridge sends an update available event for version "2.4.0"
Scenario: Update available with automatic updates enabled
Given bridge is version "2.3.0" and the latest available version is "2.4.0" reachable from "2.3.0"
When bridge starts
Then bridge sends an update installed event for version "2.4.0"
Scenario: Manual update available with automatic updates enabled
Given bridge is version "2.3.0" and the latest available version is "2.4.0" reachable from "2.4.0"
When bridge starts
Then bridge sends a manual update event for version "2.4.0"
Scenario: Update is required to continue using bridge
Given there exists an account with username "user@pm.me" and password "password"
And bridge is version "2.3.0" and the latest available version is "2.3.0" reachable from "2.3.0"
And the API requires bridge version at least "2.4.0"
When bridge starts
And the user logs in with username "user@pm.me" and password "password"
Then bridge sends a forced update event

View File

@ -0,0 +1,14 @@
Feature: A user can be deleted
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
Scenario: Delete a connected user
When user "user@pm.me" is deleted
Then user "user@pm.me" is not listed
Scenario: Delete a disconnected user
Given user "user@pm.me" logs out
When user "user@pm.me" is deleted
Then user "user@pm.me" is not listed

View File

View File

@ -0,0 +1,28 @@
Feature: A user can login
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
Scenario: Login to account
When the user logs in with username "user@pm.me" and password "password"
Then user "user@pm.me" is listed and connected
Scenario: Login to account with wrong password
When the user logs in with username "user@pm.me" and password "wrong"
Then user "user@pm.me" is not listed
Scenario: Login to nonexistent account
When the user logs in with username "other@pm.me" and password "unknown"
Then user "other@pm.me" is not listed
Scenario: Login to account without internet
Given the internet is turned off
When the user logs in with username "user@pm.me" and password "password"
Then user "user@pm.me" is not listed
Scenario: Login to multiple accounts
Given there exists an account with username "additional@pm.me" and password "other"
When the user logs in with username "user@pm.me" and password "password"
And the user logs in with username "additional@pm.me" and password "other"
Then user "user@pm.me" is listed and connected
And user "additional@pm.me" is listed and connected

View File

@ -0,0 +1,15 @@
Feature: A logged out user can login again
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
Scenario: Login to disconnected account
When user "user@pm.me" logs out
And bridge restarts
And the user logs in with username "user@pm.me" and password "password"
Then user "user@pm.me" is listed and connected
Scenario: Cannot login to removed account
When user "user@pm.me" is deleted
Then user "user@pm.me" is not listed

View File

@ -0,0 +1,16 @@
Feature: A logged in user is logged out when its auth is revoked.
Background:
Given there exists an account with username "user@pm.me" and password "password"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
Scenario: The auth is revoked while bridge is running
When the auth of user "user@pm.me" is revoked
Then bridge sends a deauth event for user "user@pm.me"
And user "user@pm.me" is listed but not connected
Scenario: The auth is revoked while bridge is not running
Given bridge stops
And the auth of user "user@pm.me" is revoked
When bridge starts
Then user "user@pm.me" is listed but not connected

View File

@ -0,0 +1,38 @@
Feature: Bridge can fully sync an account
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 |
| one | folder |
| two | folder |
| three | label |
And the account "user@pm.me" has the following messages in "one":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
And the account "user@pm.me" has the following messages in "two":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
And bridge starts
Scenario: The account is synced when the user logs in and persists across bridge restarts
When the user logs in with username "user@pm.me" and password "password"
Then bridge sends sync started and finished events for user "user@pm.me"
When bridge restarts
And user "user@pm.me" connects and authenticates IMAP client "1"
Then IMAP client "1" sees the following mailbox info:
| name | total | unread |
| INBOX | 0 | 0 |
| Drafts | 0 | 0 |
| Sent | 0 | 0 |
| Starred | 0 | 0 |
| Archive | 0 | 0 |
| Spam | 0 | 0 |
| Trash | 0 | 0 |
| All Mail | 4 | 2 |
| Folders | 0 | 0 |
| Folders/one | 2 | 1 |
| Folders/two | 2 | 1 |
| Labels | 0 | 0 |
| Labels/three | 0 | 0 |

473
tests/imap_test.go Normal file
View File

@ -0,0 +1,473 @@
package tests
import (
"fmt"
"strings"
"time"
"github.com/bradenaw/juniper/iterator"
"github.com/bradenaw/juniper/xslices"
"github.com/cucumber/godog"
"github.com/cucumber/messages-go/v16"
"github.com/emersion/go-imap"
id "github.com/emersion/go-imap-id"
"github.com/emersion/go-imap/client"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"golang.org/x/exp/slices"
)
func (s *scenario) userConnectsIMAPClient(username, clientID string) error {
return s.t.newIMAPClient(s.t.getUserID(username), clientID)
}
func (s *scenario) userConnectsIMAPClientOnPort(username, clientID string, port int) error {
return s.t.newIMAPClientOnPort(s.t.getUserID(username), clientID, port)
}
func (s *scenario) userConnectsAndAuthenticatesIMAPClient(username, clientID string) error {
if err := s.t.newIMAPClient(s.t.getUserID(username), clientID); err != nil {
return err
}
userID, client := s.t.getIMAPClient(clientID)
return client.Login(s.t.getUserAddr(userID), s.t.getUserPass(userID))
}
func (s *scenario) imapClientCanAuthenticate(clientID string) error {
userID, client := s.t.getIMAPClient(clientID)
return client.Login(s.t.getUserAddr(userID), s.t.getUserPass(userID))
}
func (s *scenario) imapClientCannotAuthenticate(clientID string) error {
userID, client := s.t.getIMAPClient(clientID)
if err := client.Login(s.t.getUserAddr(userID), s.t.getUserPass(userID)); err == nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) imapClientCannotAuthenticateWithIncorrectUsername(clientID string) error {
userID, client := s.t.getIMAPClient(clientID)
if err := client.Login(s.t.getUserAddr(userID)+"bad", s.t.getUserPass(userID)); err == nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) imapClientCannotAuthenticateWithIncorrectPassword(clientID string) error {
userID, client := s.t.getIMAPClient(clientID)
if err := client.Login(s.t.getUserAddr(userID), s.t.getUserPass(userID)+"bad"); err == nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) imapClientAnnouncesItsIDWithNameAndVersion(clientID, name, version string) error {
_, client := s.t.getIMAPClient(clientID)
if _, err := id.NewClient(client).ID(id.ID{id.FieldName: name, id.FieldVersion: version}); err != nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) imapClientCreatesMailbox(clientID, mailbox string) error {
_, client := s.t.getIMAPClient(clientID)
s.t.pushError(client.Create(mailbox))
return nil
}
func (s *scenario) imapClientDeletesMailbox(clientID, mailbox string) error {
_, client := s.t.getIMAPClient(clientID)
s.t.pushError(client.Delete(mailbox))
return nil
}
func (s *scenario) imapClientRenamesMailboxTo(clientID, fromName, toName string) error {
_, client := s.t.getIMAPClient(clientID)
s.t.pushError(client.Rename(fromName, toName))
return nil
}
func (s *scenario) imapClientSeesTheFollowingMailboxInfo(clientID string, table *godog.Table) error {
_, client := s.t.getIMAPClient(clientID)
status, err := clientStatus(client)
if err != nil {
return err
}
haveMailboxes := xslices.Map(status, func(info *imap.MailboxStatus) Mailbox {
return Mailbox{
Name: info.Name,
Total: int(info.Messages),
Unread: int(info.Unseen),
}
})
return matchMailboxes(haveMailboxes, table)
}
func (s *scenario) imapClientSeesTheFollowingMailboxInfoForMailbox(clientID, mailbox string, table *godog.Table) error {
_, client := s.t.getIMAPClient(clientID)
status, err := clientStatus(client)
if err != nil {
return err
}
status = xslices.Filter(status, func(status *imap.MailboxStatus) bool {
return status.Name == mailbox
})
haveMailboxes := xslices.Map(status, func(info *imap.MailboxStatus) Mailbox {
return Mailbox{
Name: info.Name,
Total: int(info.Messages),
Unread: int(info.Unseen),
}
})
return matchMailboxes(haveMailboxes, table)
}
func (s *scenario) imapClientSeesTheFollowingMailboxes(clientID string, table *godog.Table) error {
_, client := s.t.getIMAPClient(clientID)
mailboxes, err := clientList(client)
if err != nil {
return err
}
have := xslices.Map(mailboxes, func(info *imap.MailboxInfo) string {
return info.Name
})
want := xslices.Map(table.Rows[1:], func(row *messages.PickleTableRow) string {
return row.Cells[0].Value
})
if !cmp.Equal(want, have, cmpopts.SortSlices(func(a, b string) bool { return a < b })) {
return fmt.Errorf("want %v, have %v", want, have)
}
return nil
}
func (s *scenario) imapClientSeesMailbox(clientID, mailbox string) error {
_, client := s.t.getIMAPClient(clientID)
mailboxes, err := clientList(client)
if err != nil {
return err
}
if !slices.Contains(xslices.Map(mailboxes, func(info *imap.MailboxInfo) string { return info.Name }), mailbox) {
return fmt.Errorf("expected %v to contain %v but it doesn't", mailboxes, mailbox)
}
return nil
}
func (s *scenario) imapClientDoesNotSeeMailbox(clientID, mailbox string) error {
_, client := s.t.getIMAPClient(clientID)
mailboxes, err := clientList(client)
if err != nil {
return err
}
if slices.Contains(xslices.Map(mailboxes, func(info *imap.MailboxInfo) string { return info.Name }), mailbox) {
return fmt.Errorf("expected %v to not contain %v but it does", mailboxes, mailbox)
}
return nil
}
func (s *scenario) imapClientCountsMailboxesUnder(clientID string, count int, parent string) error {
_, client := s.t.getIMAPClient(clientID)
mailboxes, err := clientList(client)
if err != nil {
return err
}
mailboxes = xslices.Filter(mailboxes, func(info *imap.MailboxInfo) bool {
return strings.HasPrefix(info.Name, parent) && info.Name != parent
})
if len(mailboxes) != count {
return fmt.Errorf("expected %v to have %v mailboxes, got %v", parent, count, len(mailboxes))
}
return nil
}
func (s *scenario) imapClientSelectsMailbox(clientID, mailbox string) error {
_, client := s.t.getIMAPClient(clientID)
status, err := client.Select(mailbox, false)
if err != nil {
s.t.pushError(err)
} else if status.Name != mailbox {
return fmt.Errorf("expected mailbox %v, got %v", mailbox, status.Name)
}
return nil
}
func (s *scenario) imapClientCopiesTheMessageWithSubjectFromTo(clientID, subject, from, to string) error {
_, client := s.t.getIMAPClient(clientID)
uid, err := clientGetUIDBySubject(client, from, subject)
if err != nil {
return err
}
return clientCopy(client, from, to, uid)
}
func (s *scenario) imapClientCopiesAllMessagesFromTo(clientID, from, to string) error {
_, client := s.t.getIMAPClient(clientID)
return clientCopy(client, from, to)
}
func (s *scenario) imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox string, table *godog.Table) error {
_, client := s.t.getIMAPClient(clientID)
fetch, err := clientFetch(client, mailbox)
if err != nil {
return err
}
haveMessages := xslices.Map(fetch, func(msg *imap.Message) Message {
return Message{
Sender: msg.Envelope.Sender[0].Address(),
Recipient: msg.Envelope.To[0].Address(),
Subject: msg.Envelope.Subject,
Unread: slices.Contains(msg.Flags, imap.SeenFlag),
}
})
return matchMessages(haveMessages, table)
}
func (s *scenario) imapClientEventuallySeesTheFollowingMessagesInMailbox(clientID, mailbox string, table *godog.Table) error {
return eventually(
func() error { return s.imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox, table) },
5*time.Second,
500*time.Millisecond,
)
}
func (s *scenario) imapClientSeesMessagesInMailbox(clientID string, count int, mailbox string) error {
_, client := s.t.getIMAPClient(clientID)
fetch, err := clientFetch(client, mailbox)
if err != nil {
return err
}
if len(fetch) != count {
return fmt.Errorf("expected mailbox %v to be empty, got %v", mailbox, fetch)
}
return nil
}
func (s *scenario) imapClientEventuallySeesMessagesInMailbox(clientID string, count int, mailbox string) error {
return eventually(
func() error { return s.imapClientSeesMessagesInMailbox(clientID, count, mailbox) },
5*time.Second,
500*time.Millisecond,
)
}
func (s *scenario) imapClientMarksMessageAsDeleted(clientID string, seq int) error {
_, client := s.t.getIMAPClient(clientID)
_, err := clientStore(client, seq, seq, imap.FormatFlagsOp(imap.AddFlags, true), imap.DeletedFlag)
if err != nil {
return err
}
return nil
}
func (s *scenario) imapClientMarksMessageAsNotDeleted(clientID string, seq int) error {
_, client := s.t.getIMAPClient(clientID)
_, err := clientStore(client, seq, seq, imap.FormatFlagsOp(imap.RemoveFlags, true), imap.DeletedFlag)
if err != nil {
return err
}
return nil
}
func (s *scenario) imapClientMarksAllMessagesAsDeleted(clientID string) error {
_, client := s.t.getIMAPClient(clientID)
_, err := clientStore(client, 1, int(client.Mailbox().Messages), imap.FormatFlagsOp(imap.AddFlags, true), imap.DeletedFlag)
if err != nil {
return err
}
return nil
}
func (s *scenario) imapClientSeesThatMessageHasTheFlag(clientID string, seq int, flag string) error {
_, client := s.t.getIMAPClient(clientID)
fetch, err := clientFetch(client, client.Mailbox().Name)
if err != nil {
return err
}
idx := xslices.IndexFunc(fetch, func(msg *imap.Message) bool {
return msg.SeqNum == uint32(seq)
})
if !slices.Contains(fetch[idx].Flags, flag) {
return fmt.Errorf("expected message %v to have flag %v, got %v", seq, flag, fetch[idx].Flags)
}
return nil
}
func (s *scenario) imapClientExpunges(clientID string) error {
_, client := s.t.getIMAPClient(clientID)
return client.Expunge(nil)
}
func clientList(client *client.Client) ([]*imap.MailboxInfo, error) {
resCh := make(chan *imap.MailboxInfo)
go func() {
if err := client.List("", "*", resCh); err != nil {
panic(err)
}
}()
return iterator.Collect(iterator.Chan(resCh)), nil
}
func clientStatus(client *client.Client) ([]*imap.MailboxStatus, error) {
var status []*imap.MailboxStatus
list, err := clientList(client)
if err != nil {
return nil, err
}
for _, info := range list {
res, err := client.Status(info.Name, []imap.StatusItem{imap.StatusMessages, imap.StatusRecent, imap.StatusUidNext, imap.StatusUidValidity, imap.StatusUnseen})
if err != nil {
return nil, err
}
status = append(status, res)
}
return status, nil
}
func clientGetUIDBySubject(client *client.Client, mailbox, subject string) (uint32, error) {
fetch, err := clientFetch(client, mailbox)
if err != nil {
return 0, err
}
for _, msg := range fetch {
if msg.Envelope.Subject == subject {
return msg.Uid, nil
}
}
return 0, fmt.Errorf("could not find message with subject %v", subject)
}
func clientFetch(client *client.Client, mailbox string) ([]*imap.Message, error) {
status, err := client.Select(mailbox, false)
if err != nil {
return nil, err
}
if status.Messages == 0 {
return nil, nil
}
resCh := make(chan *imap.Message)
go func() {
if err := client.Fetch(
&imap.SeqSet{Set: []imap.Seq{{Start: 1, Stop: status.Messages}}},
[]imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid},
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 {
return err
}
if status.Messages == 0 {
return fmt.Errorf("expected %v to have messages, but it doesn't", from)
}
var seqset *imap.SeqSet
if len(uid) == 0 {
seqset = &imap.SeqSet{Set: []imap.Seq{{Start: 1, Stop: status.Messages}}}
} else {
seqset = &imap.SeqSet{}
for _, uid := range uid {
seqset.AddNum(uid)
}
}
return client.UidCopy(seqset, to)
}
func clientStore(client *client.Client, from, to int, item imap.StoreItem, flags ...string) ([]*imap.Message, error) {
resCh := make(chan *imap.Message)
go func() {
if err := client.Store(
&imap.SeqSet{Set: []imap.Seq{{Start: uint32(from), Stop: uint32(to)}}},
item,
xslices.Map(flags, func(flag string) interface{} { return flag }),
resCh,
); err != nil {
panic(err)
}
}()
return iterator.Collect(iterator.Chan(resCh)), nil
}

39
tests/init_test.go Normal file
View File

@ -0,0 +1,39 @@
package tests
import (
"crypto/x509"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
"gitlab.protontech.ch/go/liteapi/server/account"
)
func init() {
key, err := crypto.GenerateKey("name", "email", "rsa", 1024)
if err != nil {
panic(err)
}
account.GenerateKey = func(name, email string, passphrase []byte, keyType string, bits int) (string, error) {
encKey, err := key.Lock(passphrase)
if err != nil {
return "", err
}
return encKey.Armor()
}
template, err := certs.NewTLSTemplate()
if err != nil {
panic(err)
}
certPEM, keyPEM, err := certs.GenerateCert(template)
if err != nil {
panic(err)
}
certs.GenerateCert = func(template *x509.Certificate) ([]byte, []byte, error) {
return certPEM, keyPEM, nil
}
}

107
tests/smtp_test.go Normal file
View File

@ -0,0 +1,107 @@
package tests
import (
"fmt"
"net/smtp"
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
)
func (s *scenario) userConnectsSMTPClient(username, clientID string) error {
return s.t.newSMTPClient(s.t.getUserID(username), clientID)
}
func (s *scenario) userConnectsSMTPClientOnPort(username, clientID string, port int) error {
return s.t.newSMTPClientOnPort(s.t.getUserID(username), clientID, port)
}
func (s *scenario) userConnectsAndAuthenticatesSMTPClient(username, clientID string) error {
if err := s.t.newSMTPClient(s.t.getUserID(username), clientID); err != nil {
return err
}
userID, client := s.t.getSMTPClient(clientID)
s.t.pushError(client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID), s.t.getUserPass(userID), constants.Host)))
return nil
}
func (s *scenario) smtpClientCanAuthenticate(clientID string) error {
userID, client := s.t.getSMTPClient(clientID)
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID), s.t.getUserPass(userID), constants.Host)); err != nil {
return fmt.Errorf("expected no error, got %v", err)
}
return nil
}
func (s *scenario) smtpClientCannotAuthenticate(clientID string) error {
userID, client := s.t.getSMTPClient(clientID)
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID), s.t.getUserPass(userID), constants.Host)); err == nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) smtpClientCannotAuthenticateWithIncorrectUsername(clientID string) error {
userID, client := s.t.getSMTPClient(clientID)
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID)+"bad", s.t.getUserPass(userID), constants.Host)); err == nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) smtpClientCannotAuthenticateWithIncorrectPassword(clientID string) error {
userID, client := s.t.getSMTPClient(clientID)
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID), s.t.getUserPass(userID)+"bad", constants.Host)); err == nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) smtpClientSendsMailFrom(clientID, from string) error {
_, client := s.t.getSMTPClient(clientID)
s.t.pushError(client.Mail(from))
return nil
}
func (s *scenario) smtpClientSendsRcptTo(clientID, to string) error {
_, client := s.t.getSMTPClient(clientID)
s.t.pushError(client.Rcpt(to))
return nil
}
func (s *scenario) smtpClientSendsData(clientID, data string) error {
_, client := s.t.getSMTPClient(clientID)
rc, err := client.Data()
if err != nil {
s.t.pushError(err)
} else if _, err := rc.Write([]byte(data)); err != nil {
s.t.pushError(err)
} else if err := rc.Close(); err != nil {
s.t.pushError(err)
}
return nil
}
func (s *scenario) smtpClientSendsReset(clientID string) error {
_, client := s.t.getSMTPClient(clientID)
s.t.pushError(client.Reset())
return nil
}

110
tests/types_test.go Normal file
View File

@ -0,0 +1,110 @@
package tests
import (
"fmt"
"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"
)
type Message struct {
Sender string
Recipient string
Subject string
Unread bool
}
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)
}
return nil
}
func parseMessages(table *godog.Table) []Message {
return xslices.Map(table.Rows[1:], func(row *messages.PickleTableRow) Message {
return Message{
Sender: row.Cells[0].Value,
Recipient: row.Cells[1].Value,
Subject: row.Cells[2].Value,
Unread: mustParseBool(row.Cells[3].Value),
}
})
}
type Mailbox struct {
Name string
Total int
Unread int
}
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)
}
return nil
}
func parseMailboxes(table *godog.Table) []Mailbox {
mustParseInt := func(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}
return xslices.Map(table.Rows[1:], func(row *messages.PickleTableRow) Mailbox {
return Mailbox{
Name: row.Cells[0].Value,
Total: mustParseInt(row.Cells[1].Value),
Unread: mustParseInt(row.Cells[2].Value),
}
})
}
func mustParseBool(s string) bool {
v, err := strconv.ParseBool(s)
if err != nil {
panic(err)
}
return v
}
func eventually(condition func() error, waitFor, tick time.Duration) error {
ch := make(chan error, 1)
timer := time.NewTimer(waitFor)
defer timer.Stop()
ticker := time.NewTicker(tick)
defer ticker.Stop()
for tick := ticker.C; ; {
select {
case <-timer.C:
return fmt.Errorf("timed out after %v", waitFor)
case <-tick:
tick = nil
go func() { ch <- condition() }()
case err := <-ch:
if err == nil {
return nil
}
tick = ticker.C
}
}
}

212
tests/user_test.go Normal file
View File

@ -0,0 +1,212 @@
package tests
import (
"context"
"errors"
"fmt"
"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"
)
func (s *scenario) thereExistsAnAccountWithUsernameAndPassword(username, password string) error {
userID, addrID, err := s.t.api.AddUser(username, password, username)
if err != nil {
return err
}
// Set the ID of this user.
s.t.setUserID(username, userID)
// Set the address ID of this user.
s.t.setAddrID(userID, addrID)
// Set the address of this user (right now just the same as the username, but let's stay flexible).
s.t.setUserAddr(userID, username)
return nil
}
func (s *scenario) theAccountHasCustomFolders(username string, count int) error {
for idx := 0; idx < count; idx++ {
if _, err := s.t.api.AddLabel(s.t.getUserID(username), uuid.NewString(), liteapi.LabelTypeFolder); err != nil {
return err
}
}
return nil
}
func (s *scenario) theAccountHasCustomLabels(username string, count int) error {
for idx := 0; idx < count; idx++ {
if _, err := s.t.api.AddLabel(s.t.getUserID(username), uuid.NewString(), liteapi.LabelTypeLabel); err != nil {
return err
}
}
return nil
}
func (s *scenario) theAccountHasTheFollowingCustomMailboxes(username string, table *godog.Table) error {
type mailbox struct {
name string
typ liteapi.LabelType
}
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,
}
})
for _, wantMailbox := range wantMailboxes {
if _, err := s.t.api.AddLabel(s.t.getUserID(username), wantMailbox.name, wantMailbox.typ); err != nil {
return err
}
}
return nil
}
func (s *scenario) theAccountHasTheFollowingMessagesInMailbox(username, mailbox string, table *godog.Table) error {
userID := s.t.getUserID(username)
addrID := s.t.getAddrID(userID)
mboxID := s.t.getMBoxID(userID, mailbox)
for _, wantMessage := range parseMessages(table) {
if _, err := s.t.api.AddMessage(
userID,
addrID,
[]string{mboxID},
wantMessage.Sender,
wantMessage.Recipient,
wantMessage.Subject,
"some body goes here",
rfc822.TextPlain,
wantMessage.Unread,
false,
); err != nil {
return err
}
}
return nil
}
func (s *scenario) theAccountHasMessagesInMailbox(username string, count int, mailbox string) error {
userID := s.t.getUserID(username)
addrID := s.t.getAddrID(userID)
mboxID := s.t.getMBoxID(userID, mailbox)
for idx := 0; idx < count; idx++ {
if _, err := s.t.api.AddMessage(
userID,
addrID,
[]string{mboxID},
fmt.Sprintf("sender%v@pm.me", idx),
fmt.Sprintf("recipient%v@pm.me", idx),
fmt.Sprintf("subject %v", idx),
fmt.Sprintf("body %v", idx),
rfc822.TextPlain,
false,
false,
); err != nil {
return err
}
}
return nil
}
func (s *scenario) userLogsInWithUsernameAndPassword(username, password string) error {
userID, err := s.t.bridge.LoginUser(context.Background(), username, password, nil, nil)
if err != nil {
s.t.pushError(err)
} else {
if userID != s.t.getUserID(username) {
return errors.New("user ID mismatch")
}
info, err := s.t.bridge.GetUserInfo(userID)
if err != nil {
return err
}
s.t.setUserPass(userID, info.BridgePass)
}
return nil
}
func (s *scenario) userLogsOut(username string) error {
return s.t.bridge.LogoutUser(context.Background(), s.t.getUserID(username))
}
func (s *scenario) userIsDeleted(username string) error {
return s.t.bridge.DeleteUser(context.Background(), s.t.getUserID(username))
}
func (s *scenario) theAuthOfUserIsRevoked(username string) error {
return s.t.api.RevokeUser(s.t.getUserID(username))
}
func (s *scenario) userIsListedAndConnected(username string) error {
user, err := s.t.bridge.GetUserInfo(s.t.getUserID(username))
if err != nil {
return err
}
if user.Username != username {
return errors.New("user not listed")
}
if !user.Connected {
return errors.New("user not connected")
}
return nil
}
func (s *scenario) userIsListedButNotConnected(username string) error {
user, err := s.t.bridge.GetUserInfo(s.t.getUserID(username))
if err != nil {
return err
}
if user.Username != username {
return errors.New("user not listed")
}
if user.Connected {
return errors.New("user connected")
}
return nil
}
func (s *scenario) userIsNotListed(username string) error {
if slices.Contains(s.t.bridge.GetUserIDs(), s.t.getUserID(username)) {
return errors.New("user listed")
}
return nil
}
func (s *scenario) userFinishesSyncing(username string) error {
return s.bridgeSendsSyncStartedAndFinishedEventsForUser(username)
}