forked from Silverfish/proton-bridge
GODT-1779: Remove go-imap
This commit is contained in:
92
tests/_features/imap/auth.feature
Normal file
92
tests/_features/imap/auth.feature
Normal 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"
|
||||
63
tests/_features/imap/idle/basic.feature
Normal file
63
tests/_features/imap/idle/basic.feature
Normal 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 |
|
||||
27
tests/_features/imap/idle/two_users.feature
Normal file
27
tests/_features/imap/idle/two_users.feature
Normal 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
|
||||
50
tests/_features/imap/mailbox/create.feature
Normal file
50
tests/_features/imap/mailbox/create.feature
Normal 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"
|
||||
29
tests/_features/imap/mailbox/delete.feature
Normal file
29
tests/_features/imap/mailbox/delete.feature
Normal 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"
|
||||
15
tests/_features/imap/mailbox/info.feature
Normal file
15
tests/_features/imap/mailbox/info.feature
Normal 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"
|
||||
143
tests/_features/imap/mailbox/list.feature
Normal file
143
tests/_features/imap/mailbox/list.feature
Normal 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"
|
||||
30
tests/_features/imap/mailbox/rename.feature
Normal file
30
tests/_features/imap/mailbox/rename.feature
Normal 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"
|
||||
17
tests/_features/imap/mailbox/select.feature
Normal file
17
tests/_features/imap/mailbox/select.feature
Normal 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"
|
||||
20
tests/_features/imap/mailbox/status.feature
Normal file
20
tests/_features/imap/mailbox/status.feature
Normal 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"
|
||||
89
tests/_features/imap/message/copy.feature
Normal file
89
tests/_features/imap/message/copy.feature
Normal 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"
|
||||
78
tests/_features/imap/message/create.feature
Normal file
78
tests/_features/imap/message/create.feature
Normal 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
|
||||
127
tests/_features/imap/message/delete.feature
Normal file
127
tests/_features/imap/message/delete.feature
Normal 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
|
||||
51
tests/_features/imap/message/delete_from_trash.feature
Normal file
51
tests/_features/imap/message/delete_from_trash.feature
Normal 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 |
|
||||
47
tests/_features/imap/message/drafts.feature
Normal file
47
tests/_features/imap/message/drafts.feature
Normal 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"
|
||||
|
||||
191
tests/_features/imap/message/fetch.feature
Normal file
191
tests/_features/imap/message/fetch.feature
Normal 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
|
||||
41
tests/_features/imap/message/fetch_header.feature
Normal file
41
tests/_features/imap/message/fetch_header.feature
Normal file
@ -0,0 +1,41 @@
|
||||
Feature: IMAP fetch header of message
|
||||
Background: Fetch header deterministic content type and boundary
|
||||
Given there is connected user "user"
|
||||
And there are messages in mailbox "INBOX" for "user"
|
||||
| id | from | to | subject | n attachments | content type | body |
|
||||
| 1 | f@m.co | t@pm.me | A message with attachment | 2 | html | body |
|
||||
| 2 | f@m.co | t@pm.me | A simple html message | 0 | html | body |
|
||||
| 3 | f@m.co | t@pm.me | A simple plain message | 0 | plain | body |
|
||||
# | 4 | f@m.co | t@pm.me | An externally encrypted message | 0 | mixed | body |
|
||||
# | 5 | f@m.co | t@pm.me | A simple plain message in latin1 | 0 | plain-latin1 | body |
|
||||
|
||||
And there is IMAP client logged in as "user"
|
||||
And there is IMAP client selected in "INBOX"
|
||||
|
||||
@ignore-live
|
||||
Scenario Outline: Fetch header deterministic content type and boundary
|
||||
Given header is not cached for message "<id>" in "INBOX" for "user"
|
||||
# First time need to download and cache
|
||||
When IMAP client fetches header of "<id>"
|
||||
Then IMAP response is "OK"
|
||||
And IMAP response contains "Content-Type: <contentType>"
|
||||
And IMAP response contains "<parameter>"
|
||||
And header is cached for message "<id>" in "INBOX" for "user"
|
||||
# Second time it's taken from imap cache
|
||||
When IMAP client fetches body "<id>"
|
||||
Then IMAP response is "OK"
|
||||
And IMAP response contains "Content-Type: <contentType>"
|
||||
And IMAP response contains "<parameter>"
|
||||
# Third time header taken from DB
|
||||
When IMAP client fetches header of "<id>"
|
||||
Then IMAP response is "OK"
|
||||
And IMAP response contains "Content-Type: <contentType>"
|
||||
And IMAP response contains "<parameter>"
|
||||
|
||||
Examples:
|
||||
| id | contentType | parameter |
|
||||
| 1 | multipart/mixed | boundary=4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce |
|
||||
| 2 | text/html | charset=utf-8 |
|
||||
| 3 | text/plain | charset=utf-8 |
|
||||
|
||||
|
||||
220
tests/_features/imap/message/import.feature
Normal file
220
tests/_features/imap/message/import.feature
Normal 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
|
||||
98
tests/_features/imap/message/move.feature
Normal file
98
tests/_features/imap/message/move.feature
Normal 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"
|
||||
80
tests/_features/imap/message/move_local_folder.feature
Normal file
80
tests/_features/imap/message/move_local_folder.feature
Normal 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 |
|
||||
77
tests/_features/imap/message/move_without_support.feature
Normal file
77
tests/_features/imap/message/move_without_support.feature
Normal 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 |
|
||||
90
tests/_features/imap/message/search.feature
Normal file
90
tests/_features/imap/message/search.feature
Normal 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]*$"
|
||||
81
tests/_features/imap/message/update.feature
Normal file
81
tests/_features/imap/message/update.feature
Normal 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
|
||||
|
||||
62
tests/_features/imap/message/update_spam.feature
Normal file
62
tests/_features/imap/message/update_spam.feature
Normal 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 |
|
||||
|
||||
|
||||
26
tests/_features/imap/user_agent.feature
Normal file
26
tests/_features/imap/user_agent.feature
Normal 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])"
|
||||
65
tests/_features/smtp/auth.feature
Normal file
65
tests/_features/smtp/auth.feature
Normal 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"
|
||||
90
tests/_features/smtp/init.feature
Normal file
90
tests/_features/smtp/init.feature
Normal 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"
|
||||
69
tests/_features/smtp/send/bcc.feature
Normal file
69
tests/_features/smtp/send/bcc.feature
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"""
|
||||
38
tests/_features/smtp/send/embedded_message.feature
Normal file
38
tests/_features/smtp/send/embedded_message.feature
Normal 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 |
|
||||
52
tests/_features/smtp/send/failures.feature
Normal file
52
tests/_features/smtp/send/failures.feature
Normal 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"
|
||||
335
tests/_features/smtp/send/html.feature
Normal file
335
tests/_features/smtp/send/html.feature
Normal 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"
|
||||
}
|
||||
}
|
||||
"""
|
||||
210
tests/_features/smtp/send/html_att.feature
Normal file
210
tests/_features/smtp/send/html_att.feature
Normal 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"
|
||||
}
|
||||
}
|
||||
"""
|
||||
38
tests/_features/smtp/send/mixed_case.feature
Normal file
38
tests/_features/smtp/send/mixed_case.feature
Normal 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"
|
||||
}
|
||||
}
|
||||
"""
|
||||
324
tests/_features/smtp/send/plain.feature
Normal file
324
tests/_features/smtp/send/plain.feature
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
187
tests/_features/smtp/send/plain_att.feature
Normal file
187
tests/_features/smtp/send/plain_att.feature
Normal 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"
|
||||
}
|
||||
}
|
||||
"""
|
||||
45
tests/_features/smtp/send/same_message.feature
Normal file
45
tests/_features/smtp/send/same_message.feature
Normal 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. |
|
||||
50
tests/_features/smtp/send/send_append.feature
Normal file
50
tests/_features/smtp/send/send_append.feature
Normal 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"
|
||||
71
tests/_features/smtp/send/two_messages.feature
Normal file
71
tests/_features/smtp/send/two_messages.feature
Normal 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"
|
||||
90
tests/_features/start.feature
Normal file
90
tests/_features/start.feature
Normal 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"
|
||||
76
tests/_features/users/addressmode.feature
Normal file
76
tests/_features/users/addressmode.feature
Normal 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 |
|
||||
45
tests/_features/users/delete.feature
Normal file
45
tests/_features/users/delete.feature
Normal 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"
|
||||
78
tests/_features/users/login.feature
Normal file
78
tests/_features/users/login.feature
Normal 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
|
||||
41
tests/_features/users/relogin.feature
Normal file
41
tests/_features/users/relogin.feature
Normal 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
|
||||
17
tests/_features/users/revoked_session.feature
Normal file
17
tests/_features/users/revoked_session.feature
Normal 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
|
||||
|
||||
60
tests/_features/users/sync.feature
Normal file
60
tests/_features/users/sync.feature
Normal 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
36
tests/api_test.go
Normal 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
170
tests/bdd_test.go
Normal 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
222
tests/bridge_test.go
Normal 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
78
tests/ctx_bridge_test.go
Normal 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
30
tests/ctx_imap_test.go
Normal 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
30
tests/ctx_smtp_test.go
Normal 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
210
tests/ctx_test.go
Normal 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
66
tests/environment_test.go
Normal 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
|
||||
}
|
||||
22
tests/features/imap/auth.feature
Normal file
22
tests/features/imap/auth.feature
Normal 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
|
||||
15
tests/features/imap/mailbox/create.feature
Normal file
15
tests/features/imap/mailbox/create.feature
Normal 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"
|
||||
29
tests/features/imap/mailbox/delete.feature
Normal file
29
tests/features/imap/mailbox/delete.feature
Normal 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"
|
||||
19
tests/features/imap/mailbox/info.feature
Normal file
19
tests/features/imap/mailbox/info.feature
Normal 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 |
|
||||
39
tests/features/imap/mailbox/list.feature
Normal file
39
tests/features/imap/mailbox/list.feature
Normal 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"
|
||||
29
tests/features/imap/mailbox/rename.feature
Normal file
29
tests/features/imap/mailbox/rename.feature
Normal 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
|
||||
27
tests/features/imap/mailbox/select.feature
Normal file
27
tests/features/imap/mailbox/select.feature
Normal 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
|
||||
61
tests/features/imap/message/copy.feature
Normal file
61
tests/features/imap/message/copy.feature
Normal 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"
|
||||
39
tests/features/imap/message/delete.feature
Normal file
39
tests/features/imap/message/delete.feature
Normal 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"
|
||||
17
tests/features/imap/migration.feature
Normal file
17
tests/features/imap/migration.feature
Normal 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"
|
||||
10
tests/features/imap/ports.feature
Normal file
10
tests/features/imap/ports.feature
Normal 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
|
||||
20
tests/features/imap/user_agent.feature
Normal file
20
tests/features/imap/user_agent.feature
Normal 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])"
|
||||
22
tests/features/smtp/auth.feature
Normal file
22
tests/features/smtp/auth.feature
Normal 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
|
||||
48
tests/features/smtp/init.feature
Normal file
48
tests/features/smtp/init.feature
Normal 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"
|
||||
10
tests/features/smtp/ports.feature
Normal file
10
tests/features/smtp/ports.feature
Normal 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
|
||||
29
tests/features/updates.feature
Normal file
29
tests/features/updates.feature
Normal 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
|
||||
14
tests/features/user/delete.feature
Normal file
14
tests/features/user/delete.feature
Normal 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
|
||||
0
tests/features/user/events.feature
Normal file
0
tests/features/user/events.feature
Normal file
28
tests/features/user/login.feature
Normal file
28
tests/features/user/login.feature
Normal 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
|
||||
15
tests/features/user/relogin.feature
Normal file
15
tests/features/user/relogin.feature
Normal 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
|
||||
16
tests/features/user/revoke.feature
Normal file
16
tests/features/user/revoke.feature
Normal 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
|
||||
38
tests/features/user/sync.feature
Normal file
38
tests/features/user/sync.feature
Normal 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
473
tests/imap_test.go
Normal 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
39
tests/init_test.go
Normal 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
107
tests/smtp_test.go
Normal 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
110
tests/types_test.go
Normal 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
212
tests/user_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user