mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2026-02-04 08:18:34 +00:00
Merge branch 'release/congo' into devel
This commit is contained in:
@ -1,25 +1,38 @@
|
||||
.PHONY: check-has-go install-godog test test-live test-debug test-live-debug
|
||||
.PHONY: check-go check-godog install-godog test test-bridge test-ie test-live test-live-bridge test-live-ie test-stage test-debug test-live-debug bench
|
||||
|
||||
export GO111MODULE=on
|
||||
export BRIDGE_VERSION:=1.3.0-integrationtests
|
||||
export VERBOSITY?=fatal
|
||||
export TEST_DATA=testdata
|
||||
export TEST_APP?=bridge
|
||||
|
||||
check-has-go:
|
||||
# Tests do not run in parallel. This will overrule user settings.
|
||||
MAKEFLAGS=-j1
|
||||
|
||||
check-go:
|
||||
@which go || (echo "Install Go-lang!" && exit 1)
|
||||
|
||||
install-godog: check-has-go
|
||||
check-godog:
|
||||
@which godog || $(MAKE) install-godog
|
||||
install-godog: check-go
|
||||
go get github.com/cucumber/godog/cmd/godog@v0.8.1
|
||||
|
||||
test:
|
||||
which godog || $(MAKE) install-godog
|
||||
TEST_ENV=fake TEST_ACCOUNTS=accounts/fake.json godog --tags="~@ignore" $(FEATURES)
|
||||
test: test-bridge test-ie
|
||||
test-bridge: FEATURES ?= features/bridge
|
||||
test-bridge: check-godog
|
||||
TEST_APP=bridge TEST_ENV=fake TEST_ACCOUNTS=accounts/fake.json godog --tags="~@ignore" $(FEATURES)
|
||||
test-ie: FEATURES ?= features/ie
|
||||
test-ie: check-godog
|
||||
TEST_APP=ie TEST_ENV=fake TEST_ACCOUNTS=accounts/fake.json godog --tags="~@ignore" $(FEATURES)
|
||||
|
||||
# Doesn't work in parallel!
|
||||
# Provide TEST_ACCOUNTS with your accounts.
|
||||
test-live:
|
||||
which godog || $(MAKE) install-godog
|
||||
TEST_ENV=live godog --tags="~@ignore && ~@ignore-live" $(FEATURES)
|
||||
test-live: test-live-bridge test-live-ie
|
||||
test-live-bridge: FEATURES ?= features/bridge
|
||||
test-live-bridge: check-godog
|
||||
TEST_APP=bridge TEST_ENV=live godog --tags="~@ignore && ~@ignore-live" $(FEATURES)
|
||||
test-live-ie: FEATURES ?= features/ie
|
||||
test-live-ie: check-godog
|
||||
TEST_APP=ie TEST_ENV=live godog --tags="~@ignore && ~@ignore-live" $(FEATURES)
|
||||
|
||||
# Doesn't work in parallel!
|
||||
# Provide TEST_ACCOUNTS with your accounts.
|
||||
|
||||
45
test/api_actions_test.go
Normal file
45
test/api_actions_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
)
|
||||
|
||||
func APIActionsFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^the internet connection is lost$`, theInternetConnectionIsLost)
|
||||
s.Step(`^the internet connection is restored$`, theInternetConnectionIsRestored)
|
||||
s.Step(`^(\d+) seconds pass$`, secondsPass)
|
||||
}
|
||||
|
||||
func theInternetConnectionIsLost() error {
|
||||
ctx.GetPMAPIController().TurnInternetConnectionOff()
|
||||
return nil
|
||||
}
|
||||
|
||||
func theInternetConnectionIsRestored() error {
|
||||
ctx.GetPMAPIController().TurnInternetConnectionOn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func secondsPass(seconds int) error {
|
||||
time.Sleep(time.Duration(seconds) * time.Second)
|
||||
return nil
|
||||
}
|
||||
@ -29,6 +29,8 @@ import (
|
||||
func APIChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^API endpoint "([^"]*)" is called with:$`, apiIsCalledWith)
|
||||
s.Step(`^message is sent with API call:$`, messageIsSentWithAPICall)
|
||||
s.Step(`^API mailbox "([^"]*)" for "([^"]*)" has messages$`, apiMailboxForUserHasMessages)
|
||||
s.Step(`^API mailbox "([^"]*)" for address "([^"]*)" of "([^"]*)" has messages$`, apiMailboxForAddressOfUserHasMessages)
|
||||
}
|
||||
|
||||
func apiIsCalledWith(endpoint string, data *gherkin.DocString) error {
|
||||
@ -77,3 +79,41 @@ func checkAllRequiredFieldsForSendingMessage(request []byte) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func apiMailboxForUserHasMessages(mailboxName, bddUserID string, messages *gherkin.DataTable) error {
|
||||
return apiMailboxForAddressOfUserHasMessages(mailboxName, "", bddUserID, messages)
|
||||
}
|
||||
|
||||
func apiMailboxForAddressOfUserHasMessages(mailboxName, bddAddressID, bddUserID string, messages *gherkin.DataTable) error {
|
||||
account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
|
||||
labelIDs, err := ctx.GetPMAPIController().GetLabelIDs(account.Username(), []string{mailboxName})
|
||||
if err != nil {
|
||||
return internalError(err, "getting label %s for %s", mailboxName, account.Username())
|
||||
}
|
||||
labelID := labelIDs[0]
|
||||
|
||||
pmapiMessages, err := ctx.GetPMAPIController().GetMessages(account.Username(), labelID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
head := messages.Rows[0].Cells
|
||||
for _, row := range messages.Rows[1:] {
|
||||
found, err := messagesContainsMessageRow(account, pmapiMessages, head, row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
rowMap := map[string]string{}
|
||||
for idx, cell := range row.Cells {
|
||||
rowMap[head[idx].Value] = cell.Value
|
||||
}
|
||||
return fmt.Errorf("message %v not found", rowMap)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
31
test/api_setup_test.go
Normal file
31
test/api_setup_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"github.com/cucumber/godog"
|
||||
)
|
||||
|
||||
func APISetupFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^there is no internet connection$`, thereIsNoInternetConnection)
|
||||
}
|
||||
|
||||
func thereIsNoInternetConnection() error {
|
||||
ctx.GetPMAPIController().TurnInternetConnectionOff()
|
||||
return nil
|
||||
}
|
||||
@ -18,19 +18,27 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/test/context"
|
||||
"github.com/cucumber/godog"
|
||||
)
|
||||
|
||||
const (
|
||||
timeFormat = "2006-01-02T15:04:05"
|
||||
)
|
||||
|
||||
func FeatureContext(s *godog.Suite) {
|
||||
s.BeforeScenario(beforeScenario)
|
||||
s.AfterScenario(afterScenario)
|
||||
|
||||
APIActionsFeatureContext(s)
|
||||
APIChecksFeatureContext(s)
|
||||
APISetupFeatureContext(s)
|
||||
|
||||
BridgeActionsFeatureContext(s)
|
||||
BridgeChecksFeatureContext(s)
|
||||
BridgeSetupFeatureContext(s)
|
||||
|
||||
CommonChecksFeatureContext(s)
|
||||
|
||||
IMAPActionsAuthFeatureContext(s)
|
||||
IMAPActionsMailboxFeatureContext(s)
|
||||
@ -45,18 +53,32 @@ func FeatureContext(s *godog.Suite) {
|
||||
StoreActionsFeatureContext(s)
|
||||
StoreChecksFeatureContext(s)
|
||||
StoreSetupFeatureContext(s)
|
||||
|
||||
TransferActionsFeatureContext(s)
|
||||
TransferChecksFeatureContext(s)
|
||||
TransferSetupFeatureContext(s)
|
||||
|
||||
UsersActionsFeatureContext(s)
|
||||
UsersSetupFeatureContext(s)
|
||||
UsersChecksFeatureContext(s)
|
||||
}
|
||||
|
||||
var ctx *context.TestContext //nolint[gochecknoglobals]
|
||||
|
||||
func beforeScenario(scenario interface{}) {
|
||||
ctx = context.New()
|
||||
// bridge or ie. With godog 0.10.x and later it can be determined from
|
||||
// scenario.Uri and its file location.
|
||||
app := os.Getenv("TEST_APP")
|
||||
ctx = context.New(app)
|
||||
}
|
||||
|
||||
func afterScenario(scenario interface{}, err error) {
|
||||
if err != nil {
|
||||
for _, user := range ctx.GetBridge().GetUsers() {
|
||||
user.GetStore().TestDumpDB(ctx.GetTestingT())
|
||||
for _, user := range ctx.GetUsers().GetUsers() {
|
||||
store := user.GetStore()
|
||||
if store != nil {
|
||||
store.TestDumpDB(ctx.GetTestingT())
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.Cleanup()
|
||||
|
||||
@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
func benchTestContext() (*context.TestContext, *mocks.IMAPClient) {
|
||||
ctx := context.New()
|
||||
ctx := context.New("bridge")
|
||||
|
||||
username := "user"
|
||||
account := ctx.GetTestAccount(username)
|
||||
|
||||
@ -18,28 +18,16 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
)
|
||||
|
||||
func BridgeActionsFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^bridge starts$`, bridgeStarts)
|
||||
s.Step(`^bridge syncs "([^"]*)"$`, bridgeSyncsUser)
|
||||
s.Step(`^"([^"]*)" logs in to bridge$`, userLogsInToBridge)
|
||||
s.Step(`^"([^"]*)" logs in to bridge with bad password$`, userLogsInToBridgeWithBadPassword)
|
||||
s.Step(`^"([^"]*)" logs out from bridge$`, userLogsOutFromBridge)
|
||||
s.Step(`^"([^"]*)" changes the address mode$`, userChangesTheAddressMode)
|
||||
s.Step(`^user deletes "([^"]*)" from bridge$`, userDeletesUserFromBridge)
|
||||
s.Step(`^user deletes "([^"]*)" from bridge with cache$`, userDeletesUserFromBridgeWithCache)
|
||||
s.Step(`^the internet connection is lost$`, theInternetConnectionIsLost)
|
||||
s.Step(`^the internet connection is restored$`, theInternetConnectionIsRestored)
|
||||
s.Step(`^(\d+) seconds pass$`, secondsPass)
|
||||
s.Step(`^"([^"]*)" swaps address "([^"]*)" with address "([^"]*)"$`, swapsAddressWithAddress)
|
||||
}
|
||||
|
||||
func bridgeStarts() error {
|
||||
ctx.SetLastBridgeError(ctx.RestartBridge())
|
||||
ctx.SetLastError(ctx.RestartBridge())
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -51,113 +39,6 @@ func bridgeSyncsUser(bddUserID string) error {
|
||||
if err := ctx.WaitForSync(account.Username()); err != nil {
|
||||
return internalError(err, "waiting for sync")
|
||||
}
|
||||
ctx.SetLastBridgeError(ctx.GetTestingError())
|
||||
ctx.SetLastError(ctx.GetTestingError())
|
||||
return nil
|
||||
}
|
||||
|
||||
func userLogsInToBridge(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
ctx.SetLastBridgeError(ctx.LoginUser(account.Username(), account.Password(), account.MailboxPassword()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func userLogsInToBridgeWithBadPassword(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
ctx.SetLastBridgeError(ctx.LoginUser(account.Username(), "you shall not pass!", "123"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func userLogsOutFromBridge(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
ctx.SetLastBridgeError(ctx.LogoutUser(account.Username()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func userChangesTheAddressMode(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
if err := bridgeUser.SwitchAddressMode(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.EventuallySyncIsFinishedForUsername(account.Username())
|
||||
return nil
|
||||
}
|
||||
|
||||
func userDeletesUserFromBridge(bddUserID string) error {
|
||||
return deleteUserFromBridge(bddUserID, false)
|
||||
}
|
||||
|
||||
func userDeletesUserFromBridgeWithCache(bddUserID string) error {
|
||||
return deleteUserFromBridge(bddUserID, true)
|
||||
}
|
||||
|
||||
func deleteUserFromBridge(bddUserID string, cache bool) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
return ctx.GetBridge().DeleteUser(bridgeUser.ID(), cache)
|
||||
}
|
||||
|
||||
func theInternetConnectionIsLost() error {
|
||||
ctx.GetPMAPIController().TurnInternetConnectionOff()
|
||||
return nil
|
||||
}
|
||||
|
||||
func theInternetConnectionIsRestored() error {
|
||||
ctx.GetPMAPIController().TurnInternetConnectionOn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func secondsPass(seconds int) error {
|
||||
time.Sleep(time.Duration(seconds) * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func swapsAddressWithAddress(bddUserID, bddAddressID1, bddAddressID2 string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
|
||||
address1ID := account.GetAddressID(bddAddressID1)
|
||||
address2ID := account.GetAddressID(bddAddressID2)
|
||||
addressIDs := make([]string, len(*account.Addresses()))
|
||||
|
||||
var address1Index, address2Index int
|
||||
for i, v := range *account.Addresses() {
|
||||
if v.ID == address1ID {
|
||||
address1Index = i
|
||||
}
|
||||
if v.ID == address2ID {
|
||||
address2Index = i
|
||||
}
|
||||
addressIDs[i] = v.ID
|
||||
}
|
||||
|
||||
addressIDs[address1Index], addressIDs[address2Index] = addressIDs[address2Index], addressIDs[address1Index]
|
||||
|
||||
ctx.ReorderAddresses(account.Username(), bddAddressID1, bddAddressID2)
|
||||
|
||||
return ctx.GetPMAPIController().ReorderAddresses(account.User(), addressIDs)
|
||||
}
|
||||
|
||||
37
test/common_checks_test.go
Normal file
37
test/common_checks_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"github.com/cucumber/godog"
|
||||
a "github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func CommonChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^last response is "([^"]*)"$`, lastResponseIs)
|
||||
}
|
||||
|
||||
func lastResponseIs(expectedResponse string) error {
|
||||
err := ctx.GetLastError()
|
||||
if expectedResponse == "OK" {
|
||||
a.NoError(ctx.GetTestingT(), err)
|
||||
} else {
|
||||
a.EqualError(ctx.GetTestingT(), err, expectedResponse)
|
||||
}
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
@ -32,9 +32,10 @@ func (ctx *TestContext) GetBridge() *bridge.Bridge {
|
||||
}
|
||||
|
||||
// withBridgeInstance creates a bridge instance for use in the test.
|
||||
// Every TestContext has this by default and thus this doesn't need to be exported.
|
||||
// TestContext has this by default once called with env variable TEST_APP=bridge.
|
||||
func (ctx *TestContext) withBridgeInstance() {
|
||||
ctx.bridge = newBridgeInstance(ctx.t, ctx.cfg, ctx.credStore, ctx.listener, ctx.clientManager)
|
||||
ctx.users = ctx.bridge.Users
|
||||
ctx.addCleanupChecked(ctx.bridge.ClearData, "Cleaning bridge data")
|
||||
}
|
||||
|
||||
@ -69,13 +70,3 @@ func newBridgeInstance(
|
||||
pref := preferences.New(cfg)
|
||||
return bridge.New(cfg, pref, panicHandler, eventListener, clientManager, credStore)
|
||||
}
|
||||
|
||||
// SetLastBridgeError sets the last error that occurred while executing a bridge action.
|
||||
func (ctx *TestContext) SetLastBridgeError(err error) {
|
||||
ctx.bridgeLastError = err
|
||||
}
|
||||
|
||||
// GetLastBridgeError returns the last error that occurred while executing a bridge action.
|
||||
func (ctx *TestContext) GetLastBridgeError() error {
|
||||
return ctx.bridgeLastError
|
||||
}
|
||||
|
||||
@ -77,6 +77,9 @@ func (c *fakeConfig) GetLogPrefix() string {
|
||||
func (c *fakeConfig) GetPreferencesPath() string {
|
||||
return filepath.Join(c.dir, "prefs.json")
|
||||
}
|
||||
func (c *fakeConfig) GetTransferDir() string {
|
||||
return c.dir
|
||||
}
|
||||
func (c *fakeConfig) GetTLSCertPath() string {
|
||||
return filepath.Join(c.dir, "cert.pem")
|
||||
}
|
||||
|
||||
@ -20,6 +20,8 @@ package context
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
@ -46,10 +48,12 @@ type TestContext struct {
|
||||
pmapiController PMAPIController
|
||||
clientManager *pmapi.ClientManager
|
||||
|
||||
// Bridge core related variables.
|
||||
bridge *bridge.Bridge
|
||||
bridgeLastError error
|
||||
credStore users.CredentialsStorer
|
||||
// Core related variables.
|
||||
bridge *bridge.Bridge
|
||||
importExport *importexport.ImportExport
|
||||
users *users.Users
|
||||
credStore users.CredentialsStorer
|
||||
lastError error
|
||||
|
||||
// IMAP related variables.
|
||||
imapAddr string
|
||||
@ -63,6 +67,12 @@ type TestContext struct {
|
||||
smtpClients map[string]*mocks.SMTPClient
|
||||
smtpLastResponses map[string]*mocks.SMTPResponse
|
||||
|
||||
// Transfer related variables.
|
||||
transferLocalRootForImport string
|
||||
transferLocalRootForExport string
|
||||
transferRemoteIMAPServer *mocks.IMAPServer
|
||||
transferProgress *transfer.Progress
|
||||
|
||||
// These are the cleanup steps executed when Cleanup() is called.
|
||||
cleanupSteps []*Cleaner
|
||||
|
||||
@ -71,7 +81,7 @@ type TestContext struct {
|
||||
}
|
||||
|
||||
// New returns a new test TestContext.
|
||||
func New() *TestContext {
|
||||
func New(app string) *TestContext {
|
||||
setLogrusVerbosityFromEnv()
|
||||
|
||||
cfg := newFakeConfig()
|
||||
@ -96,8 +106,15 @@ func New() *TestContext {
|
||||
// Ensure that the config is cleaned up after the test is over.
|
||||
ctx.addCleanupChecked(cfg.ClearData, "Cleaning bridge config data")
|
||||
|
||||
// Create bridge instance under test.
|
||||
ctx.withBridgeInstance()
|
||||
// Create bridge or import-export instance under test.
|
||||
switch app {
|
||||
case "bridge":
|
||||
ctx.withBridgeInstance()
|
||||
case "ie":
|
||||
ctx.withImportExportInstance()
|
||||
default:
|
||||
panic("unknown app: " + app)
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
@ -125,3 +142,13 @@ func (ctx *TestContext) GetTestingT() *bddT { //nolint[golint]
|
||||
func (ctx *TestContext) GetTestingError() error {
|
||||
return ctx.t.getErrors()
|
||||
}
|
||||
|
||||
// SetLastError sets the last error that occurred while executing an action.
|
||||
func (ctx *TestContext) SetLastError(err error) {
|
||||
ctx.lastError = err
|
||||
}
|
||||
|
||||
// GetLastError returns the last error that occurred while executing an action.
|
||||
func (ctx *TestContext) GetLastError() error {
|
||||
return ctx.lastError
|
||||
}
|
||||
|
||||
48
test/context/importexport.go
Normal file
48
test/context/importexport.go
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
)
|
||||
|
||||
// GetImportExport returns import-export instance.
|
||||
func (ctx *TestContext) GetImportExport() *importexport.ImportExport {
|
||||
return ctx.importExport
|
||||
}
|
||||
|
||||
// withImportExportInstance creates a import-export instance for use in the test.
|
||||
// TestContext has this by default once called with env variable TEST_APP=ie.
|
||||
func (ctx *TestContext) withImportExportInstance() {
|
||||
ctx.importExport = newImportExportInstance(ctx.t, ctx.cfg, ctx.credStore, ctx.listener, ctx.clientManager)
|
||||
ctx.users = ctx.importExport.Users
|
||||
}
|
||||
|
||||
// newImportExportInstance creates a new import-export instance configured to use the given config/credstore.
|
||||
func newImportExportInstance(
|
||||
t *bddT,
|
||||
cfg importexport.Configer,
|
||||
credStore users.CredentialsStorer,
|
||||
eventListener listener.Listener,
|
||||
clientManager users.ClientManager,
|
||||
) *importexport.ImportExport {
|
||||
panicHandler := &panicHandler{t: t}
|
||||
return importexport.New(cfg, panicHandler, eventListener, clientManager, credStore)
|
||||
}
|
||||
@ -33,6 +33,7 @@ type PMAPIController interface {
|
||||
GetLabelIDs(username string, labelNames []string) ([]string, error)
|
||||
AddUserMessage(username string, message *pmapi.Message) error
|
||||
GetMessageID(username, messageIndex string) string
|
||||
GetMessages(username, labelID string) ([]*pmapi.Message, error)
|
||||
ReorderAddresses(user *pmapi.User, addressIDs []string) error
|
||||
PrintCalls()
|
||||
WasCalled(method, path string, expectedRequest []byte) bool
|
||||
|
||||
91
test/context/transfer.go
Normal file
91
test/context/transfer.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/ProtonMail/proton-bridge/test/mocks"
|
||||
)
|
||||
|
||||
// SetTransferProgress sets transfer progress.
|
||||
func (ctx *TestContext) SetTransferProgress(progress *transfer.Progress) {
|
||||
ctx.transferProgress = progress
|
||||
}
|
||||
|
||||
// GetTransferProgress returns transfer progress.
|
||||
func (ctx *TestContext) GetTransferProgress() *transfer.Progress {
|
||||
return ctx.transferProgress
|
||||
}
|
||||
|
||||
// GetTransferLocalRootForImport creates temporary root for importing
|
||||
// if it not exists yet, and returns its path.
|
||||
func (ctx *TestContext) GetTransferLocalRootForImport() string {
|
||||
if ctx.transferLocalRootForImport != "" {
|
||||
return ctx.transferLocalRootForImport
|
||||
}
|
||||
root := ctx.createLocalRoot()
|
||||
ctx.transferLocalRootForImport = root
|
||||
return root
|
||||
}
|
||||
|
||||
// GetTransferLocalRootForExport creates temporary root for exporting
|
||||
// if it not exists yet, and returns its path.
|
||||
func (ctx *TestContext) GetTransferLocalRootForExport() string {
|
||||
if ctx.transferLocalRootForExport != "" {
|
||||
return ctx.transferLocalRootForExport
|
||||
}
|
||||
root := ctx.createLocalRoot()
|
||||
ctx.transferLocalRootForExport = root
|
||||
return root
|
||||
}
|
||||
|
||||
func (ctx *TestContext) createLocalRoot() string {
|
||||
root, err := ioutil.TempDir("", "transfer")
|
||||
if err != nil {
|
||||
panic("failed to create temp transfer root: " + err.Error())
|
||||
}
|
||||
|
||||
ctx.addCleanupChecked(func() error {
|
||||
return os.RemoveAll(root)
|
||||
}, "Cleaning transfer data")
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
// GetTransferRemoteIMAPServer creates mocked IMAP server if it not created yet, and returns it.
|
||||
func (ctx *TestContext) GetTransferRemoteIMAPServer() *mocks.IMAPServer {
|
||||
if ctx.transferRemoteIMAPServer != nil {
|
||||
return ctx.transferRemoteIMAPServer
|
||||
}
|
||||
|
||||
port := 21300 + rand.Intn(100)
|
||||
ctx.transferRemoteIMAPServer = mocks.NewIMAPServer("user", "pass", "127.0.0.1", strconv.Itoa(port))
|
||||
|
||||
ctx.transferRemoteIMAPServer.Start()
|
||||
ctx.addCleanupChecked(func() error {
|
||||
ctx.transferRemoteIMAPServer.Stop()
|
||||
return nil
|
||||
}, "Cleaning transfer IMAP server")
|
||||
|
||||
return ctx.transferRemoteIMAPServer
|
||||
}
|
||||
@ -30,11 +30,16 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// GetUsers returns users instance.
|
||||
func (ctx *TestContext) GetUsers() *users.Users {
|
||||
return ctx.users
|
||||
}
|
||||
|
||||
// LoginUser logs in the user with the given username, password, and mailbox password.
|
||||
func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) (err error) {
|
||||
srp.RandReader = rand.New(rand.NewSource(42))
|
||||
|
||||
client, auth, err := ctx.bridge.Login(username, password)
|
||||
client, auth, err := ctx.users.Login(username, password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to login")
|
||||
}
|
||||
@ -45,7 +50,7 @@ func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) (e
|
||||
}
|
||||
}
|
||||
|
||||
user, err := ctx.bridge.FinishLogin(client, auth, mailboxPassword)
|
||||
user, err := ctx.users.FinishLogin(client, auth, mailboxPassword)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to finish login")
|
||||
}
|
||||
@ -57,7 +62,7 @@ func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) (e
|
||||
|
||||
// GetUser retrieves the bridge user matching the given query string.
|
||||
func (ctx *TestContext) GetUser(username string) (*users.User, error) {
|
||||
return ctx.bridge.GetUser(username)
|
||||
return ctx.users.GetUser(username)
|
||||
}
|
||||
|
||||
// GetStore retrieves the store for given username.
|
||||
@ -100,6 +105,9 @@ func (ctx *TestContext) WaitForSync(username string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if store == nil {
|
||||
return nil
|
||||
}
|
||||
// First wait for ongoing sync to be done before starting and waiting for new one.
|
||||
ctx.eventuallySyncIsFinished(store)
|
||||
store.TestSync()
|
||||
@ -121,7 +129,7 @@ func (ctx *TestContext) EventuallySyncIsFinishedForUsername(username string) {
|
||||
|
||||
// LogoutUser logs out the given user.
|
||||
func (ctx *TestContext) LogoutUser(query string) (err error) {
|
||||
user, err := ctx.bridge.GetUser(query)
|
||||
user, err := ctx.users.GetUser(query)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get user")
|
||||
}
|
||||
@ -135,12 +143,12 @@ func (ctx *TestContext) LogoutUser(query string) (err error) {
|
||||
|
||||
// DeleteUser deletes the given user.
|
||||
func (ctx *TestContext) DeleteUser(query string, deleteStore bool) (err error) {
|
||||
user, err := ctx.bridge.GetUser(query)
|
||||
user, err := ctx.users.GetUser(query)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get user")
|
||||
}
|
||||
|
||||
if err = ctx.bridge.DeleteUser(user.ID(), deleteStore); err != nil {
|
||||
if err = ctx.users.DeleteUser(user.ID(), deleteStore); err != nil {
|
||||
err = errors.Wrap(err, "failed to delete user")
|
||||
}
|
||||
|
||||
@ -159,3 +159,17 @@ func (ctl *Controller) resetUsers() {
|
||||
func (ctl *Controller) GetMessageID(username, messageIndex string) string {
|
||||
return messageIndex
|
||||
}
|
||||
|
||||
func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message, error) {
|
||||
messages := []*pmapi.Message{}
|
||||
for _, fakeAPI := range ctl.fakeAPIs {
|
||||
if fakeAPI.username == username {
|
||||
for _, message := range fakeAPI.messages {
|
||||
if labelID == "" || message.HasLabelID(labelID) {
|
||||
messages = append(messages, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
62
test/fakeapi/keyring_userKey
Normal file
62
test/fakeapi/keyring_userKey
Normal file
@ -0,0 +1,62 @@
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
Version: OpenPGP.js v4.4.5
|
||||
Comment: testpassphrase
|
||||
|
||||
xcLYBFzGzhEBCADBxfqTFMqfQzT77A5tuuhPFwPq8dfC2evs8u1OvTqFbztY
|
||||
5FOuSxzduyeDqQ1Fx6dKEOKgcYE8t1Uh4VSS7z6bTdY8j9yrL81kCVB46sE1
|
||||
OzStzyx/5l7OdH/pM4F+aKslnLvqlw0UeJr+UNizVtOCEUaNfVjPK3cc1ocx
|
||||
v+36K4RnnyfEtjUW9gDZbhgaF02G5ILHmWmbgM7I+77gCd2wI0EdY9s/JZQ+
|
||||
VmkMFqoMdY9PyBchoOIPUkkGQi1SaF4IEzMaAUSbnCYkHHY/SbfDTcR46VGq
|
||||
cXlkB1rq5xskaUQ9r+giCC/K4pc7bBkI1lQ7ADVuWvdrWnWapK0FO6CfABEB
|
||||
AAEAB/0YPhPJ0phA/EWviN+16bmGVOZNaVapjt2zMMybWmrtEQv3OeWgO3nP
|
||||
4cohRi/zaCBCphcm+dxbLhftW7AFi/9PVcR09436MB+oTCQFugpUWw+4TmA5
|
||||
BidxTpDxf4X2vH3rquQLBufWL6U7JlPeKAGL1xZ2aCq0DIeOk5D+xTjZizV2
|
||||
GIyQRVCLWb+LfDmvvcp3Y94X60KXdBAMuS1ZMKcY3Sl8VAXNB4KQsC/kByzf
|
||||
6FCB097XZRYV7lvJJQ7+6Wisb3yVi8sEQx2sFm5fAp+0qi3a6zRTEp49r6Hr
|
||||
gyWViH5zOOpA7DcNwx1Bwhi7GG0tak6EUnnKUNLfOupglcphBADmpXCgT4nc
|
||||
uSBYTiZSVcB/ICCkTxVsHL1WcXtPK2Ikzussx2n9kb0rapvuC0YLipX9lUkQ
|
||||
fyeC3jQJeCyN79AkDGkOfWaESueT2hM0Po+RwDgMibKn6yJ1zebz4Lc2J3C9
|
||||
oVFcAnql+9KyGsAPn03fyQzDnvhNnJvHJi4Hx8AWoQQA1xLoXeVBjRi0IjjU
|
||||
E6Mqaq5RLEog4kXRp86VSSEGHBwyIYnDiM//gjseo/CXuVyHwL7UXitp8s1B
|
||||
D1uE3APrhqUS66fD5pkF+z+RcSqiIv7I76NJ24Cdg38L6seGSjOHrq7/dEeG
|
||||
K6WqfQUCEjta3yNSg7pXb2wn2WZqKIK+rz8EALZRuMXeql/FtO3Cjb0sv7oT
|
||||
9dLP4cn1bskGRJ+Vok9lfCERbfXGccoAk3V+qSfpHgKxsebkRbUhf+trOGnw
|
||||
tW+kBWo/5hYGQuN+A9JogSJViT+nuZyE+x1/rKswDFmlMSdf2GIDARWIV0gc
|
||||
b1yOEwUmNBSthPcnFXvBr4BG3XTtNPTNLSJhcm9uMjEtM0BzYWRlbWJlLm9y
|
||||
ZyIgPGFyb24yMS0zQHNhZGVtYmUub3JnPsLAdQQQAQgAHwUCXMbOEQYLCQcI
|
||||
AwIEFQgKAgMWAgECGQECGwMCHgEACgkQZ/B4v2b2xB6XUgf/dHGRHimyMR78
|
||||
QYbEm2cuaEvOtq4a+J6Zv3P4VOWAbvkGWS9LDKSvVi60vq4oYOmF54HgPzur
|
||||
nA4OtZDf0HKwQK45VZ7CYD693o70jkKPrAAJG3yTsbesfiS7RbFyGKzKJ7EL
|
||||
nsUIJkfgm/SlKmXU/u8MOBO5Wg7/TcsS33sRWHl90j+9jbhqdl92R+vY/CwC
|
||||
ieFkQA7/TDv1u+NAalH+Lpkd8AIuEcki+TAogZ7oi/SnofwnoB7BxRm+mIkp
|
||||
ZZhIDSCaPOzLG8CSZ81d3HVHhqbf8dh0DFKFoUYyKdbOqIkNWWASf+c/ZEme
|
||||
IWcekY8hqwf/raZ56tGM/bRwYPcotMfC1wRcxs4RAQgAsMb5/ELWmrfPy3ba
|
||||
5qif+RXhGSbjitATNgHpoPUHrfTC7cn4JWHqehoXLAQpFAoKd+O/ZNpZozK9
|
||||
ilpqGUx05yMw06jNQEhYIbgIF4wzPpz02Lp6YeMwdF5LF+Rw83PHdHrA/wRV
|
||||
/QjL04+kZnN+G5HmzMlhFY+oZSpL+Gp1bTXgtAVDkhCnMB5tP2VwULMGyJ+X
|
||||
vRYxwTK2CrLjIVZv5n1VYY+caCowU6j/XFqvlCJj+G5oV+UhFOWffaMRXhOh
|
||||
a64RrhqT1Np7wCLvLMP2wpys9xlMcLQJLqDNxqOTp504V7dm67ncC0fKUsT4
|
||||
m4oTktnxKPd6MU+4VYveaLCquwARAQABAAf4u9s7gpGErs1USxmDO9TlyGZK
|
||||
aBlri8nMf3s+hOJCOo3cRaRHJBfdY6pu/baG6H6JTsWzeY4MHwr6N+dhVIEh
|
||||
FPMa9EZAjagyc4GugxWGiMVTfU+2AEfdrdynhQKMgXSctnnNCdkRuX0nwqb3
|
||||
nlupm1hsz2ze4+Wg0BKSLS0FQdoUbITdJUR69OHr4dNJVHWYI0JSBx4SdhV3
|
||||
y9163dDvmc+lW9AEaD53vyZWfzCHZxsR/gI32VmT0z5gn1t8w9AOdXo2lA1H
|
||||
bf7wh4/qCyujGu64ToZtiEny/GCyM6PofLtiZuJNLw3s/y+B2tKv22aTJ760
|
||||
+Gib1xB9WcWjKyrxBADoeCyq+nHGrl0CwOkmjanlFymgo7mnBOXuiFOvGrKk
|
||||
M1meMU1TI4TEBWkVnDVMcSejgjAf/bX1dtouba1tMAMu7DlaV/0EwbSADRel
|
||||
RSqEbIzIOys+y9TY/BMI/uCKNyEKHvu1KUXADb+CBpdBpCfMBWDANFlo9xLz
|
||||
Ajcmu2dyawQAwquwC0VXQcvzfs+Hd5au5XvHdm1KidOiAdu6PH1SrOgenIN4
|
||||
lkEjHrJD9jmloO2/GVcxDBB2pmf0B4HEg7DuY9LXBrksP5eSbbRc5+UH1HUv
|
||||
u82AqQnfNKTd/jae+lLwaOS++ohtwMkkD6W0LdWnHPjyyXg4Oi9zPID3asRu
|
||||
3PED/3CYyjl6S8GTMY4FNH7Yxu9+NV2xpKE92Hf8K/hnYlmSSVKDCEeOJtLt
|
||||
BkkcSqY6liCNSMmJdVyAF2GrR+zmDac7UQRssf57oOWtSsGozt0aqJXuspMT
|
||||
6aB+P1UhZ8Ly9rWZNiJ0jwyfnQNOLCYDaqjFmiSpqrNnJ2Q1Xge3+k80P9DC
|
||||
wF8EGAEIAAkFAlzGzhECGwwACgkQZ/B4v2b2xB5wlwgAjZA1zdv5irFjyWVo
|
||||
4/itONtyO1NbdpyYpcct7vD0oV+a4wahQP0J3Kk1GhZ5tvAoZF/jakQQOM5o
|
||||
GjUYpXAGnr09Mv9EiQ2pDwXc2yq0WfXnGxNrpzOqdtV+IqY9NYkl55Tme7x+
|
||||
WRvrkPSUeUsyEGvxwR1stdv8eg9jUmxdl8Io3PYoFJJlrM/6aXeC1r3KOj7q
|
||||
XAnR0XHJ+QBSNKCWLlQv5hui9BKfcLiVKFK/dNhs82nRyhPr4sWFw6MTqdAK
|
||||
4zkn7l0jmy6Evi1AiiGPiHPnxeNErnofOIEh4REQj00deZADHrixTLtx2FuR
|
||||
uaSC3IcBmBsj1fNb4eYXElILjQ==
|
||||
=fMOl
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
@ -20,6 +20,7 @@ package fakeapi
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/mail"
|
||||
"time"
|
||||
|
||||
@ -67,7 +68,7 @@ func (api *FakePMAPI) ListMessages(filter *pmapi.MessagesFilter) ([]*pmapi.Messa
|
||||
|
||||
for idx := 0; idx < len(api.messages); idx++ {
|
||||
var message *pmapi.Message
|
||||
if !*filter.Desc {
|
||||
if filter.Desc == nil || !*filter.Desc {
|
||||
message = api.messages[idx]
|
||||
if filter.BeginID == "" || message.ID == filter.BeginID {
|
||||
skipByIDBegin = false
|
||||
@ -81,7 +82,7 @@ func (api *FakePMAPI) ListMessages(filter *pmapi.MessagesFilter) ([]*pmapi.Messa
|
||||
if skipByIDBegin || skipByIDEnd {
|
||||
continue
|
||||
}
|
||||
if !*filter.Desc {
|
||||
if filter.Desc == nil || !*filter.Desc {
|
||||
if message.ID == filter.EndID {
|
||||
skipByIDEnd = true
|
||||
}
|
||||
@ -189,36 +190,60 @@ func (api *FakePMAPI) SendMessage(messageID string, sendMessageRequest *pmapi.Se
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Import(importMessageRequests []*pmapi.ImportMsgReq) ([]*pmapi.ImportMsgRes, error) {
|
||||
if err := api.checkAndRecordCall(POST, "/import", importMessageRequests); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgRes := []*pmapi.ImportMsgRes{}
|
||||
for _, msgReq := range importMessageRequests {
|
||||
mailMessage, err := mail.ReadMessage(bytes.NewBuffer(msgReq.Body))
|
||||
message, err := api.generateMessageFromImportRequest(msgReq)
|
||||
if err != nil {
|
||||
msgRes = append(msgRes, &pmapi.ImportMsgRes{
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
messageID := api.controller.messageIDGenerator.next("")
|
||||
message := &pmapi.Message{
|
||||
ID: messageID,
|
||||
AddressID: msgReq.AddressID,
|
||||
Sender: &mail.Address{Address: mailMessage.Header.Get("From")},
|
||||
ToList: []*mail.Address{{Address: mailMessage.Header.Get("To")}},
|
||||
Subject: mailMessage.Header.Get("Subject"),
|
||||
Unread: msgReq.Unread,
|
||||
LabelIDs: msgReq.LabelIDs,
|
||||
Body: string(msgReq.Body),
|
||||
Flags: msgReq.Flags,
|
||||
Time: msgReq.Time,
|
||||
continue
|
||||
}
|
||||
msgRes = append(msgRes, &pmapi.ImportMsgRes{
|
||||
Error: nil,
|
||||
MessageID: messageID,
|
||||
MessageID: message.ID,
|
||||
})
|
||||
api.addMessage(message)
|
||||
}
|
||||
return msgRes, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) generateMessageFromImportRequest(msgReq *pmapi.ImportMsgReq) (*pmapi.Message, error) {
|
||||
mailMessage, err := mail.ReadMessage(bytes.NewBuffer(msgReq.Body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(mailMessage.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sender, err := mail.ParseAddress(mailMessage.Header.Get("From"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toList, err := mail.ParseAddressList(mailMessage.Header.Get("To"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messageID := api.controller.messageIDGenerator.next("")
|
||||
return &pmapi.Message{
|
||||
ID: messageID,
|
||||
AddressID: msgReq.AddressID,
|
||||
Sender: sender,
|
||||
ToList: toList,
|
||||
Subject: mailMessage.Header.Get("Subject"),
|
||||
Unread: msgReq.Unread,
|
||||
LabelIDs: append(msgReq.LabelIDs, pmapi.AllMailLabel),
|
||||
Body: string(body),
|
||||
Header: mailMessage.Header,
|
||||
Flags: msgReq.Flags,
|
||||
Time: msgReq.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) addMessage(message *pmapi.Message) {
|
||||
api.messages = append(api.messages, message)
|
||||
api.addEventMessage(pmapi.EventCreate, message)
|
||||
|
||||
@ -23,16 +23,8 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error {
|
||||
return api.checkInternetAndRecordCall(POST, "/reports/bug", &pmapi.ReportReq{
|
||||
OS: os,
|
||||
OSVersion: osVersion,
|
||||
Title: title,
|
||||
Description: description,
|
||||
Username: username,
|
||||
Email: email,
|
||||
Browser: emailClient,
|
||||
})
|
||||
func (api *FakePMAPI) Report(report pmapi.ReportReq) error {
|
||||
return api.checkInternetAndRecordCall(POST, "/reports/bug", report)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) SendSimpleMetric(category, action, label string) error {
|
||||
|
||||
@ -29,16 +29,16 @@ Feature: IMAP auth
|
||||
|
||||
Scenario: Authenticates with freshly logged-out user
|
||||
Given there is connected user "user"
|
||||
When "user" logs out from bridge
|
||||
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"
|
||||
|
||||
Scenario: Authenticates user which was re-logged in
|
||||
Given there is connected user "user"
|
||||
When "user" logs out from bridge
|
||||
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 to bridge
|
||||
When "user" logs in
|
||||
And IMAP client authenticates "user"
|
||||
Then IMAP response is "OK"
|
||||
When IMAP client selects "INBOX"
|
||||
@ -26,7 +26,7 @@ Feature: Address mode
|
||||
Scenario: Switch address mode from combined to split mode
|
||||
Given there is "userMoreAddresses" in "combined" address mode
|
||||
When "userMoreAddresses" changes the address mode
|
||||
Then bridge response is "OK"
|
||||
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 |
|
||||
@ -38,7 +38,7 @@ Feature: Address mode
|
||||
Scenario: Switch address mode from split to combined mode
|
||||
Given there is "userMoreAddresses" in "split" address mode
|
||||
When "userMoreAddresses" changes the address mode
|
||||
Then bridge response is "OK"
|
||||
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 |
|
||||
@ -1,36 +1,36 @@
|
||||
Feature: Delete user
|
||||
Scenario: Deleting connected user
|
||||
Given there is connected user "user"
|
||||
When user deletes "user" from bridge
|
||||
Then bridge response is "OK"
|
||||
When user deletes "user"
|
||||
Then last response is "OK"
|
||||
And "user" has database file
|
||||
|
||||
Scenario: Deleting connected user with cache
|
||||
Given there is connected user "user"
|
||||
When user deletes "user" from bridge with cache
|
||||
Then bridge response is "OK"
|
||||
When user deletes "user" with cache
|
||||
Then last response is "OK"
|
||||
And "user" does not have database file
|
||||
|
||||
Scenario: Deleting connected user without database file
|
||||
Given there is connected user "user"
|
||||
And there is no database file for "user"
|
||||
When user deletes "user" from bridge with cache
|
||||
Then bridge response is "OK"
|
||||
When user deletes "user" with cache
|
||||
Then last response is "OK"
|
||||
|
||||
Scenario: Deleting disconnected user
|
||||
Given there is disconnected user "user"
|
||||
When user deletes "user" from bridge
|
||||
Then bridge response is "OK"
|
||||
When user deletes "user"
|
||||
Then last response is "OK"
|
||||
And "user" has database file
|
||||
|
||||
Scenario: Deleting disconnected user with cache
|
||||
Given there is disconnected user "user"
|
||||
When user deletes "user" from bridge with cache
|
||||
Then bridge response is "OK"
|
||||
When user deletes "user" with cache
|
||||
Then last response is "OK"
|
||||
And "user" does not have database file
|
||||
|
||||
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" from bridge with cache
|
||||
Then bridge response is "OK"
|
||||
When user deletes "user" with cache
|
||||
Then last response is "OK"
|
||||
@ -1,47 +1,47 @@
|
||||
Feature: Login to bridge for the first time
|
||||
Scenario: Normal bridge login
|
||||
Feature: Login for the first time
|
||||
Scenario: Normal login
|
||||
Given there is user "user"
|
||||
When "user" logs in to bridge
|
||||
Then bridge response is "OK"
|
||||
When "user" logs in
|
||||
Then last response is "OK"
|
||||
And "user" is connected
|
||||
And "user" has database file
|
||||
And "user" has running event loop
|
||||
|
||||
Scenario: Login with bad username
|
||||
When "user" logs in to bridge with bad password
|
||||
Then bridge response is "failed to login: Incorrect login credentials. Please try again"
|
||||
When "user" logs in with bad password
|
||||
Then last response is "failed to login: Incorrect login credentials. Please try again"
|
||||
|
||||
Scenario: Login with bad password
|
||||
Given there is user "user"
|
||||
When "user" logs in to bridge with bad password
|
||||
Then bridge response is "failed to login: Incorrect login credentials. Please try again"
|
||||
When "user" logs in with bad password
|
||||
Then last response is "failed to login: Incorrect login credentials. Please try again"
|
||||
|
||||
Scenario: Login without internet connection
|
||||
Given there is no internet connection
|
||||
When "user" logs in to bridge
|
||||
Then bridge response is "failed to login: cannot reach the server"
|
||||
When "user" logs in
|
||||
Then last response is "failed to login: cannot reach the server"
|
||||
|
||||
@ignore-live
|
||||
Scenario: Login user with 2FA
|
||||
Given there is user "user2fa"
|
||||
When "user2fa" logs in to bridge
|
||||
Then bridge response is "OK"
|
||||
When "user2fa" logs in
|
||||
Then last response is "OK"
|
||||
And "user2fa" is connected
|
||||
And "user2fa" has database file
|
||||
And "user2fa" has running event loop
|
||||
|
||||
Scenario: Login user with capital letters in address
|
||||
Given there is user "userAddressWithCapitalLetter"
|
||||
When "userAddressWithCapitalLetter" logs in to bridge
|
||||
Then bridge response is "OK"
|
||||
When "userAddressWithCapitalLetter" logs in
|
||||
Then last response is "OK"
|
||||
And "userAddressWithCapitalLetter" is connected
|
||||
And "userAddressWithCapitalLetter" has database file
|
||||
And "userAddressWithCapitalLetter" has running event loop
|
||||
|
||||
Scenario: Login user with more addresses
|
||||
Given there is user "userMoreAddresses"
|
||||
When "userMoreAddresses" logs in to bridge
|
||||
Then bridge response is "OK"
|
||||
When "userMoreAddresses" logs in
|
||||
Then last response is "OK"
|
||||
And "userMoreAddresses" is connected
|
||||
And "userMoreAddresses" has database file
|
||||
And "userMoreAddresses" has running event loop
|
||||
@ -49,8 +49,8 @@ Feature: Login to bridge for the first time
|
||||
@ignore-live
|
||||
Scenario: Login user with disabled primary address
|
||||
Given there is user "userDisabledPrimaryAddress"
|
||||
When "userDisabledPrimaryAddress" logs in to bridge
|
||||
Then bridge response is "OK"
|
||||
When "userDisabledPrimaryAddress" logs in
|
||||
Then last response is "OK"
|
||||
And "userDisabledPrimaryAddress" is connected
|
||||
And "userDisabledPrimaryAddress" has database file
|
||||
And "userDisabledPrimaryAddress" has running event loop
|
||||
@ -58,9 +58,9 @@ Feature: Login to bridge for the first time
|
||||
Scenario: Login two users
|
||||
Given there is user "user"
|
||||
And there is user "userMoreAddresses"
|
||||
When "user" logs in to bridge
|
||||
Then bridge response is "OK"
|
||||
When "user" logs in
|
||||
Then last response is "OK"
|
||||
And "user" is connected
|
||||
When "userMoreAddresses" logs in to bridge
|
||||
Then bridge response is "OK"
|
||||
When "userMoreAddresses" logs in
|
||||
Then last response is "OK"
|
||||
And "userMoreAddresses" is connected
|
||||
@ -1,9 +1,9 @@
|
||||
Feature: Re-login to bridge
|
||||
Feature: Re-login
|
||||
Scenario: Re-login with connected user and database file
|
||||
Given there is connected user "user"
|
||||
And there is database file for "user"
|
||||
When "user" logs in to bridge
|
||||
Then bridge response is "failed to finish login: user is already connected"
|
||||
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
|
||||
|
||||
@ -11,8 +11,8 @@ Feature: Re-login to bridge
|
||||
Scenario: Re-login with connected user and no database file
|
||||
Given there is connected user "user"
|
||||
And there is no database file for "user"
|
||||
When "user" logs in to bridge
|
||||
Then bridge response is "failed to finish login: user is already connected"
|
||||
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
|
||||
@ -20,16 +20,16 @@ Feature: Re-login to bridge
|
||||
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 to bridge
|
||||
Then bridge response is "OK"
|
||||
When "user" logs in
|
||||
Then last response is "OK"
|
||||
And "user" is connected
|
||||
And "user" has running event loop
|
||||
|
||||
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 to bridge
|
||||
Then bridge response is "OK"
|
||||
When "user" logs in
|
||||
Then last response is "OK"
|
||||
And "user" is connected
|
||||
And "user" has database file
|
||||
And "user" has running event loop
|
||||
@ -28,7 +28,7 @@ Feature: Sync bridge
|
||||
Scenario: Sync in combined mode
|
||||
And there is "userMoreAddresses" in "combined" address mode
|
||||
When bridge syncs "userMoreAddresses"
|
||||
Then bridge response is "OK"
|
||||
Then last response is "OK"
|
||||
And "userMoreAddresses" has the following messages
|
||||
| mailboxes | messages |
|
||||
| INBOX | 1101 |
|
||||
@ -43,7 +43,7 @@ Feature: Sync bridge
|
||||
Scenario: Sync in split mode
|
||||
And there is "userMoreAddresses" in "split" address mode
|
||||
When bridge syncs "userMoreAddresses"
|
||||
Then bridge response is "OK"
|
||||
Then last response is "OK"
|
||||
And "userMoreAddresses" has the following messages
|
||||
| address | mailboxes | messages |
|
||||
| primary | INBOX | 1001 |
|
||||
43
test/features/ie/transfer/export_eml.feature
Normal file
43
test/features/ie/transfer/export_eml.feature
Normal file
@ -0,0 +1,43 @@
|
||||
Feature: Export to EML files
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
And there is "user" with mailbox "Folders/Foo"
|
||||
And there are messages in mailbox "INBOX" for "user"
|
||||
| from | to | subject | time |
|
||||
| bridgetest@pm.test | test@protonmail.com | hello | 2020-01-01T12:00:00 |
|
||||
And there are messages in mailbox "Folders/Foo" for "user"
|
||||
| from | to | subject | time |
|
||||
| foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
|
||||
Scenario: Export all
|
||||
When user "user" exports to EML files
|
||||
Then progress result is "OK"
|
||||
# Every message is also in All Mail.
|
||||
And transfer exported 8 messages
|
||||
And transfer imported 8 messages
|
||||
And transfer failed for 0 messages
|
||||
And transfer exported messages
|
||||
| folder | from | to | subject | time |
|
||||
| Inbox | bridgetest@pm.test | test@protonmail.com | hello | 2020-01-01T12:00:00 |
|
||||
| Foo | foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| Foo | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| Foo | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
| All Mail | bridgetest@pm.test | test@protonmail.com | hello | 2020-01-01T12:00:00 |
|
||||
| All Mail | foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| All Mail | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| All Mail | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
|
||||
Scenario: Export only Foo with time limit
|
||||
When user "user" exports to EML files with rules
|
||||
| source | target | from | to |
|
||||
| Foo | | 2020-01-01T12:10:00 | 2020-01-01T13:00:00 |
|
||||
Then progress result is "OK"
|
||||
And transfer exported 2 messages
|
||||
And transfer imported 2 messages
|
||||
And transfer failed for 0 messages
|
||||
And transfer exported messages
|
||||
| folder | from | to | subject | time |
|
||||
| Foo | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| Foo | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
43
test/features/ie/transfer/export_mbox.feature
Normal file
43
test/features/ie/transfer/export_mbox.feature
Normal file
@ -0,0 +1,43 @@
|
||||
Feature: Export to MBOX files
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
And there is "user" with mailbox "Folders/Foo"
|
||||
And there are messages in mailbox "INBOX" for "user"
|
||||
| from | to | subject | time |
|
||||
| bridgetest@pm.test | test@protonmail.com | hello | 2020-01-01T12:00:00 |
|
||||
And there are messages in mailbox "Folders/Foo" for "user"
|
||||
| from | to | subject | time |
|
||||
| foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
|
||||
Scenario: Export all
|
||||
When user "user" exports to MBOX files
|
||||
Then progress result is "OK"
|
||||
# Every message is also in All Mail.
|
||||
And transfer exported 8 messages
|
||||
And transfer imported 8 messages
|
||||
And transfer failed for 0 messages
|
||||
And transfer exported messages
|
||||
| folder | from | to | subject | time |
|
||||
| Inbox | bridgetest@pm.test | test@protonmail.com | hello | 2020-01-01T12:00:00 |
|
||||
| Foo | foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| Foo | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| Foo | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
| All Mail | bridgetest@pm.test | test@protonmail.com | hello | 2020-01-01T12:00:00 |
|
||||
| All Mail | foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| All Mail | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| All Mail | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
|
||||
Scenario: Export only Foo with time limit
|
||||
When user "user" exports to MBOX files with rules
|
||||
| source | target | from | to |
|
||||
| Foo | | 2020-01-01T12:10:00 | 2020-01-01T13:00:00 |
|
||||
Then progress result is "OK"
|
||||
And transfer exported 2 messages
|
||||
And transfer imported 2 messages
|
||||
And transfer failed for 0 messages
|
||||
And transfer exported messages
|
||||
| folder | from | to | subject | time |
|
||||
| Foo | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| Foo | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
60
test/features/ie/transfer/import_eml.feature
Normal file
60
test/features/ie/transfer/import_eml.feature
Normal file
@ -0,0 +1,60 @@
|
||||
Feature: Import from EML files
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
And there is "user" with mailbox "Folders/Foo"
|
||||
And there is "user" with mailbox "Folders/Bar"
|
||||
And there are EML files
|
||||
| file | from | to | subject | time |
|
||||
| Foo/one.eml | foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| Foo/two.eml | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| Sub/Foo/three.eml | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
And there is EML file "Inbox/hello.eml"
|
||||
"""
|
||||
Subject: hello
|
||||
From: Bridge Test <bridgetest@pm.test>
|
||||
To: Internal Bridge <test@protonmail.com>
|
||||
|
||||
hello
|
||||
|
||||
"""
|
||||
|
||||
Scenario: Import all
|
||||
When user "user" imports local files
|
||||
Then progress result is "OK"
|
||||
And transfer exported 4 messages
|
||||
And transfer imported 4 messages
|
||||
And transfer failed for 0 messages
|
||||
And API mailbox "INBOX" for "user" has messages
|
||||
| from | to | subject |
|
||||
| bridgetest@pm.test | test@protonmail.com | hello |
|
||||
And API mailbox "Folders/Foo" for "user" has messages
|
||||
| from | to | subject |
|
||||
| foo@example.com | bridgetest@protonmail.com | one |
|
||||
| bar@example.com | bridgetest@protonmail.com | two |
|
||||
| bar@example.com | bridgetest@protonmail.com | three |
|
||||
|
||||
Scenario: Import only Foo to Bar with time limit
|
||||
When user "user" imports local files with rules
|
||||
| source | target | from | to |
|
||||
| Foo | Bar | 2020-01-01T12:10:00 | 2020-01-01T13:00:00 |
|
||||
Then progress result is "OK"
|
||||
And transfer exported 2 messages
|
||||
And transfer imported 2 messages
|
||||
And transfer failed for 0 messages
|
||||
And API mailbox "Folders/Bar" for "user" has messages
|
||||
| from | to | subject |
|
||||
| bar@example.com | bridgetest@protonmail.com | two |
|
||||
| bar@example.com | bridgetest@protonmail.com | three |
|
||||
|
||||
Scenario: Import broken EML message
|
||||
Given there is EML file "Broken/broken.eml"
|
||||
"""
|
||||
Content-type: image/png
|
||||
"""
|
||||
When user "user" imports local files with rules
|
||||
| source | target |
|
||||
| Broken | Foo |
|
||||
Then progress result is "OK"
|
||||
And transfer exported 1 messages
|
||||
And transfer imported 0 messages
|
||||
And transfer failed for 1 messages
|
||||
49
test/features/ie/transfer/import_export.feature
Normal file
49
test/features/ie/transfer/import_export.feature
Normal file
@ -0,0 +1,49 @@
|
||||
Feature: Import-Export app
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
And there is "user" with mailbox "Folders/Foo"
|
||||
And there is "user" with mailbox "Folders/Bar"
|
||||
|
||||
Scenario: EML -> PM -> EML
|
||||
Given there are EML files
|
||||
| file | from | to | subject | time |
|
||||
| Inbox/hello.eml | bridgetest@pm.test | test@protonmail.com | hello | 2020-01-01T12:00:00 |
|
||||
| Foo/one.eml | foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| Foo/two.eml | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| Sub/Foo/three.eml | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
|
||||
When user "user" imports local files
|
||||
Then progress result is "OK"
|
||||
And transfer failed for 0 messages
|
||||
And transfer imported 4 messages
|
||||
|
||||
When user "user" exports to EML files
|
||||
Then progress result is "OK"
|
||||
And transfer failed for 0 messages
|
||||
# Every message is also in All Mail.
|
||||
And transfer imported 8 messages
|
||||
|
||||
And exported messages match the original ones
|
||||
|
||||
Scenario: MBOX -> PM -> MBOX
|
||||
Given there is MBOX file "Inbox.mbox" with messages
|
||||
| from | to | subject | time |
|
||||
| bridgetest@pm.test | test@protonmail.com | hello | 2020-01-01T12:00:00 |
|
||||
And there is MBOX file "Foo.mbox" with messages
|
||||
| from | to | subject | time |
|
||||
| foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
|
||||
When user "user" imports local files
|
||||
Then progress result is "OK"
|
||||
And transfer failed for 0 messages
|
||||
And transfer imported 4 messages
|
||||
|
||||
When user "user" exports to MBOX files
|
||||
Then progress result is "OK"
|
||||
And transfer failed for 0 messages
|
||||
# Every message is also in All Mail.
|
||||
And transfer imported 8 messages
|
||||
|
||||
And exported messages match the original ones
|
||||
79
test/features/ie/transfer/import_imap.feature
Normal file
79
test/features/ie/transfer/import_imap.feature
Normal file
@ -0,0 +1,79 @@
|
||||
Feature: Import from IMAP server
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
And there is "user" with mailbox "Folders/Foo"
|
||||
And there is "user" with mailbox "Folders/Bar"
|
||||
And there are IMAP mailboxes
|
||||
| name |
|
||||
| Inbox |
|
||||
| Foo |
|
||||
| Broken |
|
||||
And there are IMAP messages
|
||||
| mailbox | seqnum | uid | from | to | subject | time |
|
||||
| Foo | 1 | 12 | foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| Foo | 2 | 14 | bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
| Foo | 3 | 15 | bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
And there is IMAP message in mailbox "Inbox" with seq 1, uid 42, time "2020-01-01T12:34:56" and subject "hello"
|
||||
"""
|
||||
Subject: hello
|
||||
From: Bridge Test <bridgetest@pm.test>
|
||||
To: Internal Bridge <test@protonmail.com>
|
||||
|
||||
hello
|
||||
|
||||
"""
|
||||
|
||||
Scenario: Import all
|
||||
When user "user" imports remote messages
|
||||
Then progress result is "OK"
|
||||
And transfer exported 4 messages
|
||||
And transfer imported 4 messages
|
||||
And transfer failed for 0 messages
|
||||
And API mailbox "INBOX" for "user" has messages
|
||||
| from | to | subject |
|
||||
| bridgetest@pm.test | test@protonmail.com | hello |
|
||||
And API mailbox "Folders/Foo" for "user" has messages
|
||||
| from | to | subject |
|
||||
| foo@example.com | bridgetest@protonmail.com | one |
|
||||
| bar@example.com | bridgetest@protonmail.com | two |
|
||||
| bar@example.com | bridgetest@protonmail.com | three |
|
||||
|
||||
Scenario: Import only Foo to Bar with time limit
|
||||
When user "user" imports remote messages with rules
|
||||
| source | target | from | to |
|
||||
| Foo | Bar | 2020-01-01T12:10:00 | 2020-01-01T13:00:00 |
|
||||
Then progress result is "OK"
|
||||
And transfer exported 2 messages
|
||||
And transfer imported 2 messages
|
||||
And transfer failed for 0 messages
|
||||
And API mailbox "Folders/Bar" for "user" has messages
|
||||
| from | to | subject |
|
||||
| bar@example.com | bridgetest@protonmail.com | two |
|
||||
| bar@example.com | bridgetest@protonmail.com | three |
|
||||
|
||||
# Note we need to have message which we can parse and use in go-imap
|
||||
# but which has problem on our side. Used example with missing boundary
|
||||
# is real example which we want to solve one day. Probabl this test
|
||||
# can be removed once we import any time of message or switch is to
|
||||
# something we will never allow.
|
||||
Scenario: Import broken message
|
||||
Given there is IMAP message in mailbox "Broken" with seq 1, uid 42, time "2020-01-01T12:34:56" and subject "broken"
|
||||
"""
|
||||
Subject: missing boundary end
|
||||
Content-Type: multipart/related; boundary=boundary
|
||||
|
||||
--boundary
|
||||
Content-Disposition: inline
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
body
|
||||
|
||||
"""
|
||||
When user "user" imports remote messages with rules
|
||||
| source | target |
|
||||
| Broken | Foo |
|
||||
Then progress result is "OK"
|
||||
And transfer exported 1 messages
|
||||
And transfer imported 0 messages
|
||||
And transfer failed for 1 messages
|
||||
64
test/features/ie/transfer/import_mbox.feature
Normal file
64
test/features/ie/transfer/import_mbox.feature
Normal file
@ -0,0 +1,64 @@
|
||||
Feature: Import from MBOX files
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
And there is "user" with mailbox "Folders/Foo"
|
||||
And there is "user" with mailbox "Folders/Bar"
|
||||
And there is MBOX file "Foo.mbox" with messages
|
||||
| from | to | subject | time |
|
||||
| foo@example.com | bridgetest@protonmail.com | one | 2020-01-01T12:00:00 |
|
||||
| bar@example.com | bridgetest@protonmail.com | two | 2020-01-01T13:00:00 |
|
||||
And there is MBOX file "Sub/Foo.mbox" with messages
|
||||
| from | to | subject | time |
|
||||
| bar@example.com | bridgetest@protonmail.com | three | 2020-01-01T12:30:00 |
|
||||
And there is MBOX file "Inbox.mbox"
|
||||
"""
|
||||
From bridgetest@pm.test Thu Feb 20 20:20:20 2020
|
||||
Subject: hello
|
||||
From: Bridge Test <bridgetest@pm.test>
|
||||
To: Internal Bridge <test@protonmail.com>
|
||||
|
||||
hello
|
||||
|
||||
"""
|
||||
|
||||
Scenario: Import all
|
||||
When user "user" imports local files
|
||||
Then progress result is "OK"
|
||||
And transfer exported 4 messages
|
||||
And transfer imported 4 messages
|
||||
And transfer failed for 0 messages
|
||||
And API mailbox "INBOX" for "user" has messages
|
||||
| from | to | subject |
|
||||
| bridgetest@pm.test | test@protonmail.com | hello |
|
||||
And API mailbox "Folders/Foo" for "user" has messages
|
||||
| from | to | subject |
|
||||
| foo@example.com | bridgetest@protonmail.com | one |
|
||||
| bar@example.com | bridgetest@protonmail.com | two |
|
||||
| bar@example.com | bridgetest@protonmail.com | three |
|
||||
|
||||
Scenario: Import only Foo to Bar with time limit
|
||||
When user "user" imports local files with rules
|
||||
| source | target | from | to |
|
||||
| Foo | Bar | 2020-01-01T12:10:00 | 2020-01-01T13:00:00 |
|
||||
Then progress result is "OK"
|
||||
And transfer exported 2 messages
|
||||
And transfer imported 2 messages
|
||||
And transfer failed for 0 messages
|
||||
And API mailbox "Folders/Bar" for "user" has messages
|
||||
| from | to | subject |
|
||||
| bar@example.com | bridgetest@protonmail.com | two |
|
||||
| bar@example.com | bridgetest@protonmail.com | three |
|
||||
|
||||
Scenario: Import broken message
|
||||
Given there is MBOX file "Broken.mbox"
|
||||
"""
|
||||
From bridgetest@pm.test Thu Feb 20 20:20:20 2020
|
||||
Content-type: image/png
|
||||
"""
|
||||
When user "user" imports local files with rules
|
||||
| source | target |
|
||||
| Broken | Foo |
|
||||
Then progress result is "OK"
|
||||
And transfer exported 1 messages
|
||||
And transfer imported 0 messages
|
||||
And transfer failed for 1 messages
|
||||
20
test/features/ie/users/delete.feature
Normal file
20
test/features/ie/users/delete.feature
Normal file
@ -0,0 +1,20 @@
|
||||
Feature: Delete user
|
||||
Scenario: Deleting connected user
|
||||
Given there is connected user "user"
|
||||
When user deletes "user"
|
||||
Then last response is "OK"
|
||||
|
||||
Scenario: Deleting connected user with cache
|
||||
Given there is connected user "user"
|
||||
When user deletes "user" with cache
|
||||
Then last response is "OK"
|
||||
|
||||
Scenario: Deleting disconnected user
|
||||
Given there is disconnected user "user"
|
||||
When user deletes "user"
|
||||
Then last response is "OK"
|
||||
|
||||
Scenario: Deleting disconnected user with cache
|
||||
Given there is disconnected user "user"
|
||||
When user deletes "user" with cache
|
||||
Then last response is "OK"
|
||||
56
test/features/ie/users/login.feature
Normal file
56
test/features/ie/users/login.feature
Normal file
@ -0,0 +1,56 @@
|
||||
Feature: Login for the first time
|
||||
Scenario: Normal login
|
||||
Given there is user "user"
|
||||
When "user" logs in
|
||||
Then last response is "OK"
|
||||
And "user" is connected
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
Scenario: Login without internet connection
|
||||
Given there is no internet connection
|
||||
When "user" logs in
|
||||
Then last response is "failed to login: cannot reach the server"
|
||||
|
||||
@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
|
||||
|
||||
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
|
||||
|
||||
Scenario: Login user with more addresses
|
||||
Given there is user "userMoreAddresses"
|
||||
When "userMoreAddresses" logs in
|
||||
Then last response is "OK"
|
||||
And "userMoreAddresses" is connected
|
||||
|
||||
@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
|
||||
|
||||
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
|
||||
12
test/features/ie/users/relogin.feature
Normal file
12
test/features/ie/users/relogin.feature
Normal file
@ -0,0 +1,12 @@
|
||||
Feature: Re-login
|
||||
Scenario: Re-login with connected user
|
||||
Given there is connected user "user"
|
||||
When "user" logs in
|
||||
Then last response is "failed to finish login: user is already connected"
|
||||
And "user" is connected
|
||||
|
||||
Scenario: Re-login with disconnected user
|
||||
Given there is disconnected user "user"
|
||||
When "user" logs in
|
||||
Then last response is "OK"
|
||||
And "user" is connected
|
||||
@ -135,3 +135,31 @@ func (ctl *Controller) GetMessageID(username, messageIndex string) string {
|
||||
}
|
||||
return ctl.messageIDsByUsername[username][idx-1]
|
||||
}
|
||||
|
||||
func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message, error) {
|
||||
client, ok := ctl.pmapiByUsername[username]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("user %s does not exist", username)
|
||||
}
|
||||
|
||||
page := 0
|
||||
messages := []*pmapi.Message{}
|
||||
|
||||
for {
|
||||
// ListMessages returns empty result, not error, asking for page out of range.
|
||||
pageMessages, _, err := client.ListMessages(&pmapi.MessagesFilter{
|
||||
Page: page,
|
||||
PageSize: 150,
|
||||
LabelID: labelID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to list messages")
|
||||
}
|
||||
messages = append(messages, pageMessages...)
|
||||
if len(pageMessages) < 150 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
@ -18,13 +18,13 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/pkg/errors"
|
||||
a "github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -37,7 +37,7 @@ type IMAPResponse struct {
|
||||
done bool
|
||||
}
|
||||
|
||||
func (ir *IMAPResponse) sendCommand(reqTag string, reqIndex int, command string, debug *debug, conn io.Writer, response *bufio.Reader) {
|
||||
func (ir *IMAPResponse) sendCommand(reqTag string, reqIndex int, command string, debug *debug, conn io.Writer, response imap.StringReader) {
|
||||
defer func() { ir.done = true }()
|
||||
|
||||
tstart := time.Now()
|
||||
|
||||
227
test/mocks/imap_server.go
Normal file
227
test/mocks/imap_server.go
Normal file
@ -0,0 +1,227 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
imapbackend "github.com/emersion/go-imap/backend"
|
||||
imapserver "github.com/emersion/go-imap/server"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type IMAPServer struct {
|
||||
Username string
|
||||
Password string
|
||||
Host string
|
||||
Port string
|
||||
|
||||
mailboxes []string
|
||||
messages map[string][]*imap.Message // Key is mailbox.
|
||||
server *imapserver.Server
|
||||
}
|
||||
|
||||
func NewIMAPServer(username, password, host, port string) *IMAPServer {
|
||||
return &IMAPServer{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Host: host,
|
||||
Port: port,
|
||||
|
||||
mailboxes: []string{},
|
||||
messages: map[string][]*imap.Message{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IMAPServer) AddMailbox(mailboxName string) {
|
||||
s.mailboxes = append(s.mailboxes, mailboxName)
|
||||
s.messages[strings.ToLower(mailboxName)] = []*imap.Message{}
|
||||
}
|
||||
|
||||
func (s *IMAPServer) AddMessage(mailboxName string, message *imap.Message) {
|
||||
mailboxName = strings.ToLower(mailboxName)
|
||||
s.messages[mailboxName] = append(s.messages[mailboxName], message)
|
||||
}
|
||||
|
||||
func (s *IMAPServer) Start() {
|
||||
server := imapserver.New(&IMAPBackend{server: s})
|
||||
server.Addr = net.JoinHostPort(s.Host, s.Port)
|
||||
server.AllowInsecureAuth = true
|
||||
server.ErrorLog = logrus.WithField("pkg", "imap-server")
|
||||
server.Debug = logrus.WithField("pkg", "imap-server").WriterLevel(logrus.DebugLevel)
|
||||
server.AutoLogout = 30 * time.Minute
|
||||
|
||||
s.server = server
|
||||
|
||||
go func() {
|
||||
err := server.ListenAndServe()
|
||||
logrus.WithError(err).Warn("IMAP server stopped")
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func (s *IMAPServer) Stop() {
|
||||
_ = s.server.Close()
|
||||
}
|
||||
|
||||
type IMAPBackend struct {
|
||||
server *IMAPServer
|
||||
}
|
||||
|
||||
func (b *IMAPBackend) Login(connInfo *imap.ConnInfo, username, password string) (imapbackend.User, error) {
|
||||
if username != b.server.Username || password != b.server.Password {
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
return &IMAPUser{
|
||||
server: b.server,
|
||||
username: username,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type IMAPUser struct {
|
||||
server *IMAPServer
|
||||
username string
|
||||
}
|
||||
|
||||
func (u *IMAPUser) Username() string {
|
||||
return u.username
|
||||
}
|
||||
|
||||
func (u *IMAPUser) ListMailboxes(subscribed bool) ([]imapbackend.Mailbox, error) {
|
||||
mailboxes := []imapbackend.Mailbox{}
|
||||
for _, mailboxName := range u.server.mailboxes {
|
||||
mailboxes = append(mailboxes, &IMAPMailbox{
|
||||
server: u.server,
|
||||
name: mailboxName,
|
||||
})
|
||||
}
|
||||
return mailboxes, nil
|
||||
}
|
||||
|
||||
func (u *IMAPUser) GetMailbox(name string) (imapbackend.Mailbox, error) {
|
||||
name = strings.ToLower(name)
|
||||
_, ok := u.server.messages[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("mailbox %s not found", name)
|
||||
}
|
||||
return &IMAPMailbox{
|
||||
server: u.server,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *IMAPUser) CreateMailbox(name string) error {
|
||||
return errors.New("not supported: create mailbox")
|
||||
}
|
||||
|
||||
func (u *IMAPUser) DeleteMailbox(name string) error {
|
||||
return errors.New("not supported: delete mailbox")
|
||||
}
|
||||
|
||||
func (u *IMAPUser) RenameMailbox(existingName, newName string) error {
|
||||
return errors.New("not supported: rename mailbox")
|
||||
}
|
||||
|
||||
func (u *IMAPUser) Logout() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type IMAPMailbox struct {
|
||||
server *IMAPServer
|
||||
name string
|
||||
attributes []string
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) Info() (*imap.MailboxInfo, error) {
|
||||
return &imap.MailboxInfo{
|
||||
Name: m.name,
|
||||
Attributes: m.attributes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) {
|
||||
status := imap.NewMailboxStatus(m.name, items)
|
||||
status.UidValidity = 1
|
||||
status.Messages = uint32(len(m.server.messages[m.name]))
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) SetSubscribed(subscribed bool) error {
|
||||
return errors.New("not supported: set subscribed")
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) Check() error {
|
||||
return errors.New("not supported: check")
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error {
|
||||
defer func() {
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
for index, message := range m.server.messages[m.name] {
|
||||
seqNum := uint32(index + 1)
|
||||
var id uint32
|
||||
if uid {
|
||||
id = message.Uid
|
||||
} else {
|
||||
id = seqNum
|
||||
}
|
||||
if seqset.Contains(id) {
|
||||
msg := imap.NewMessage(seqNum, items)
|
||||
msg.Envelope = message.Envelope
|
||||
msg.BodyStructure = message.BodyStructure
|
||||
msg.Body = message.Body
|
||||
msg.Size = message.Size
|
||||
msg.Uid = message.Uid
|
||||
|
||||
ch <- msg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error) {
|
||||
return nil, errors.New("not supported: search")
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error {
|
||||
return errors.New("not supported: create")
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error {
|
||||
return errors.New("not supported: update flags")
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) CopyMessages(uid bool, seqset *imap.SeqSet, dest string) error {
|
||||
return errors.New("not supported: copy")
|
||||
}
|
||||
|
||||
func (m *IMAPMailbox) Expunge() error {
|
||||
return errors.New("not supported: expunge")
|
||||
}
|
||||
@ -172,12 +172,12 @@ func messagesContainsMessageRow(account *accounts.TestAccount, allMessages []*pm
|
||||
matches := true
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "from":
|
||||
case "from": //nolint[goconst]
|
||||
address := ctx.EnsureAddress(account.Username(), cell.Value)
|
||||
if !areAddressesSame(message.Sender.Address, address) {
|
||||
matches = false
|
||||
}
|
||||
case "to":
|
||||
case "to": //nolint[goconst]
|
||||
for _, address := range strings.Split(cell.Value, ",") {
|
||||
address = ctx.EnsureAddress(account.Username(), address)
|
||||
for _, to := range message.ToList {
|
||||
@ -197,7 +197,7 @@ func messagesContainsMessageRow(account *accounts.TestAccount, allMessages []*pm
|
||||
}
|
||||
}
|
||||
}
|
||||
case "subject":
|
||||
case "subject": //nolint[goconst]
|
||||
expectedSubject := cell.Value
|
||||
if expectedSubject == "" {
|
||||
expectedSubject = "(No Subject)"
|
||||
@ -205,7 +205,7 @@ func messagesContainsMessageRow(account *accounts.TestAccount, allMessages []*pm
|
||||
if message.Subject != expectedSubject {
|
||||
matches = false
|
||||
}
|
||||
case "body":
|
||||
case "body": //nolint[goconst]
|
||||
if message.Body != cell.Value {
|
||||
matches = false
|
||||
}
|
||||
@ -238,7 +238,7 @@ func areAddressesSame(first, second string) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return firstAddress.String() == secondAddress.String()
|
||||
return firstAddress.Address == secondAddress.Address
|
||||
}
|
||||
|
||||
func messagesInMailboxForUserIsMarkedAsRead(messageIDs, mailboxName, bddUserID string) error {
|
||||
|
||||
@ -22,6 +22,7 @@ import (
|
||||
"net/mail"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/cucumber/godog"
|
||||
@ -63,6 +64,9 @@ func thereIsUserWithMailbox(bddUserID, mailboxName string) error {
|
||||
if err != nil {
|
||||
return internalError(err, "getting store of %s", account.Username())
|
||||
}
|
||||
if store == nil {
|
||||
return nil
|
||||
}
|
||||
return internalError(store.RebuildMailboxes(), "rebuilding mailboxes")
|
||||
}
|
||||
|
||||
@ -122,6 +126,12 @@ func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bdd
|
||||
if cell.Value == "true" {
|
||||
message.LabelIDs = append(message.LabelIDs, "10")
|
||||
}
|
||||
case "time": //nolint[goconst]
|
||||
date, err := time.Parse(timeFormat, cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "parsing time")
|
||||
}
|
||||
message.Time = date.Unix()
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
|
||||
227
test/transfer_actions_test.go
Normal file
227
test/transfer_actions_test.go
Normal file
@ -0,0 +1,227 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
func TransferActionsFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^user "([^"]*)" imports local files$`, userImportsLocalFiles)
|
||||
s.Step(`^user "([^"]*)" imports local files with rules$`, userImportsLocalFilesWithRules)
|
||||
s.Step(`^user "([^"]*)" imports local files to address "([^"]*)"$`, userImportsLocalFilesToAddress)
|
||||
s.Step(`^user "([^"]*)" imports local files to address "([^"]*)" with rules$`, userImportsLocalFilesToAddressWithRules)
|
||||
s.Step(`^user "([^"]*)" imports remote messages$`, userImportsRemoteMessages)
|
||||
s.Step(`^user "([^"]*)" imports remote messages with rules$`, userImportsRemoteMessagesWithRules)
|
||||
s.Step(`^user "([^"]*)" imports remote messages to address "([^"]*)"$`, userImportsRemoteMessagesToAddress)
|
||||
s.Step(`^user "([^"]*)" imports remote messages to address "([^"]*)" with rules$`, userImportsRemoteMessagesToAddressWithRules)
|
||||
s.Step(`^user "([^"]*)" exports to EML files$`, userExportsToEMLFiles)
|
||||
s.Step(`^user "([^"]*)" exports to EML files with rules$`, userExportsToEMLFilesWithRules)
|
||||
s.Step(`^user "([^"]*)" exports address "([^"]*)" to EML files$`, userExportsAddressToEMLFiles)
|
||||
s.Step(`^user "([^"]*)" exports address "([^"]*)" to EML files with rules$`, userExportsAddressToEMLFilesWithRules)
|
||||
s.Step(`^user "([^"]*)" exports to MBOX files$`, userExportsToMBOXFiles)
|
||||
s.Step(`^user "([^"]*)" exports to MBOX files with rules$`, userExportsToMBOXFilesWithRules)
|
||||
s.Step(`^user "([^"]*)" exports address "([^"]*)" to MBOX files$`, userExportsAddressToMBOXFiles)
|
||||
s.Step(`^user "([^"]*)" exports address "([^"]*)" to MBOX files with rules$`, userExportsAddressToMBOXFilesWithRules)
|
||||
}
|
||||
|
||||
// Local import.
|
||||
|
||||
func userImportsLocalFiles(bddUserID string) error {
|
||||
return userImportsLocalFilesToAddressWithRules(bddUserID, "", nil)
|
||||
}
|
||||
|
||||
func userImportsLocalFilesWithRules(bddUserID string, rules *gherkin.DataTable) error {
|
||||
return userImportsLocalFilesToAddressWithRules(bddUserID, "", rules)
|
||||
}
|
||||
|
||||
func userImportsLocalFilesToAddress(bddUserID, bddAddressID string) error {
|
||||
return userImportsLocalFilesToAddressWithRules(bddUserID, bddAddressID, nil)
|
||||
}
|
||||
|
||||
func userImportsLocalFilesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
||||
path := ctx.GetTransferLocalRootForImport()
|
||||
return ctx.GetImportExport().GetLocalImporter(address, path)
|
||||
})
|
||||
}
|
||||
|
||||
// Remote import.
|
||||
|
||||
func userImportsRemoteMessages(bddUserID string) error {
|
||||
return userImportsRemoteMessagesToAddressWithRules(bddUserID, "", nil)
|
||||
}
|
||||
|
||||
func userImportsRemoteMessagesWithRules(bddUserID string, rules *gherkin.DataTable) error {
|
||||
return userImportsRemoteMessagesToAddressWithRules(bddUserID, "", rules)
|
||||
}
|
||||
|
||||
func userImportsRemoteMessagesToAddress(bddUserID, bddAddressID string) error {
|
||||
return userImportsRemoteMessagesToAddressWithRules(bddUserID, bddAddressID, nil)
|
||||
}
|
||||
|
||||
func userImportsRemoteMessagesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
||||
imapServer := ctx.GetTransferRemoteIMAPServer()
|
||||
return ctx.GetImportExport().GetRemoteImporter(address, imapServer.Username, imapServer.Password, imapServer.Host, imapServer.Port)
|
||||
})
|
||||
}
|
||||
|
||||
// EML export.
|
||||
|
||||
func userExportsToEMLFiles(bddUserID string) error {
|
||||
return userExportsAddressToEMLFilesWithRules(bddUserID, "", nil)
|
||||
}
|
||||
|
||||
func userExportsToEMLFilesWithRules(bddUserID string, rules *gherkin.DataTable) error {
|
||||
return userExportsAddressToEMLFilesWithRules(bddUserID, "", rules)
|
||||
}
|
||||
|
||||
func userExportsAddressToEMLFiles(bddUserID, bddAddressID string) error {
|
||||
return userExportsAddressToEMLFilesWithRules(bddUserID, bddAddressID, nil)
|
||||
}
|
||||
|
||||
func userExportsAddressToEMLFilesWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
||||
path := ctx.GetTransferLocalRootForExport()
|
||||
return ctx.GetImportExport().GetEMLExporter(address, path)
|
||||
})
|
||||
}
|
||||
|
||||
// MBOX export.
|
||||
|
||||
func userExportsToMBOXFiles(bddUserID string) error {
|
||||
return userExportsAddressToMBOXFilesWithRules(bddUserID, "", nil)
|
||||
}
|
||||
|
||||
func userExportsToMBOXFilesWithRules(bddUserID string, rules *gherkin.DataTable) error {
|
||||
return userExportsAddressToMBOXFilesWithRules(bddUserID, "", rules)
|
||||
}
|
||||
|
||||
func userExportsAddressToMBOXFiles(bddUserID, bddAddressID string) error {
|
||||
return userExportsAddressToMBOXFilesWithRules(bddUserID, bddAddressID, nil)
|
||||
}
|
||||
|
||||
func userExportsAddressToMBOXFilesWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
||||
path := ctx.GetTransferLocalRootForExport()
|
||||
return ctx.GetImportExport().GetMBOXExporter(address, path)
|
||||
})
|
||||
}
|
||||
|
||||
// Helpers.
|
||||
|
||||
func doTransfer(bddUserID, bddAddressID string, rules *gherkin.DataTable, getTransferrer func(string) (*transfer.Transfer, error)) error {
|
||||
account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
transferrer, err := getTransferrer(account.Address())
|
||||
if err != nil {
|
||||
return internalError(err, "failed to init transfer")
|
||||
}
|
||||
if err := setRules(transferrer, rules); err != nil {
|
||||
return internalError(err, "failed to set rules")
|
||||
}
|
||||
progress := transferrer.Start()
|
||||
ctx.SetTransferProgress(progress)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRules(transferrer *transfer.Transfer, rules *gherkin.DataTable) error {
|
||||
if rules == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
transferrer.ResetRules()
|
||||
|
||||
allSourceMailboxes, err := transferrer.SourceMailboxes()
|
||||
if err != nil {
|
||||
return internalError(err, "failed to get source mailboxes")
|
||||
}
|
||||
allTargetMailboxes, err := transferrer.TargetMailboxes()
|
||||
if err != nil {
|
||||
return internalError(err, "failed to get target mailboxes")
|
||||
}
|
||||
|
||||
head := rules.Rows[0].Cells
|
||||
for _, row := range rules.Rows[1:] {
|
||||
source := ""
|
||||
target := ""
|
||||
fromTime := int64(0)
|
||||
toTime := int64(0)
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "source":
|
||||
source = cell.Value
|
||||
case "target":
|
||||
target = cell.Value
|
||||
case "from":
|
||||
date, err := time.Parse(timeFormat, cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to parse from time")
|
||||
}
|
||||
fromTime = date.Unix()
|
||||
case "to":
|
||||
date, err := time.Parse(timeFormat, cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to parse to time")
|
||||
}
|
||||
toTime = date.Unix()
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
}
|
||||
|
||||
sourceMailbox, err := getMailboxByName(allSourceMailboxes, source)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to match source mailboxes")
|
||||
}
|
||||
|
||||
// Empty target means the same as source. Useful for exports.
|
||||
targetMailboxes := []transfer.Mailbox{}
|
||||
if target == "" {
|
||||
targetMailboxes = append(targetMailboxes, sourceMailbox)
|
||||
} else {
|
||||
targetMailbox, err := getMailboxByName(allTargetMailboxes, target)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to match target mailboxes")
|
||||
}
|
||||
targetMailboxes = append(targetMailboxes, targetMailbox)
|
||||
}
|
||||
|
||||
if err := transferrer.SetRule(sourceMailbox, targetMailboxes, fromTime, toTime); err != nil {
|
||||
return internalError(err, "failed to set rule")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMailboxByName(mailboxes []transfer.Mailbox, name string) (transfer.Mailbox, error) {
|
||||
for _, mailbox := range mailboxes {
|
||||
if mailbox.Name == name {
|
||||
return mailbox, nil
|
||||
}
|
||||
}
|
||||
return transfer.Mailbox{}, fmt.Errorf("mailbox %s not found", name)
|
||||
}
|
||||
267
test/transfer_checks_test.go
Normal file
267
test/transfer_checks_test.go
Normal file
@ -0,0 +1,267 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/mail"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
"github.com/emersion/go-mbox"
|
||||
"github.com/emersion/go-message"
|
||||
"github.com/pkg/errors"
|
||||
a "github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TransferChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^progress result is "([^"]*)"$`, progressFinishedWith)
|
||||
s.Step(`^transfer exported (\d+) messages$`, transferExportedNumberOfMessages)
|
||||
s.Step(`^transfer imported (\d+) messages$`, transferImportedNumberOfMessages)
|
||||
s.Step(`^transfer failed for (\d+) messages$`, transferFailedForNumberOfMessages)
|
||||
s.Step(`^transfer exported messages$`, transferExportedMessages)
|
||||
s.Step(`^exported messages match the original ones$`, exportedMessagesMatchTheOriginalOnes)
|
||||
}
|
||||
|
||||
func progressFinishedWith(wantResponse string) error {
|
||||
progress := ctx.GetTransferProgress()
|
||||
// Wait till transport is finished.
|
||||
for range progress.GetUpdateChannel() {
|
||||
}
|
||||
|
||||
err := progress.GetFatalError()
|
||||
if wantResponse == "OK" {
|
||||
a.NoError(ctx.GetTestingT(), err)
|
||||
} else {
|
||||
a.EqualError(ctx.GetTestingT(), err, wantResponse)
|
||||
}
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func transferExportedNumberOfMessages(wantCount int) error {
|
||||
progress := ctx.GetTransferProgress()
|
||||
_, _, exported, _, _ := progress.GetCounts() //nolint[dogsled]
|
||||
a.Equal(ctx.GetTestingT(), uint(wantCount), exported)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func transferImportedNumberOfMessages(wantCount int) error {
|
||||
progress := ctx.GetTransferProgress()
|
||||
_, imported, _, _, _ := progress.GetCounts() //nolint[dogsled]
|
||||
a.Equal(ctx.GetTestingT(), uint(wantCount), imported)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func transferFailedForNumberOfMessages(wantCount int) error {
|
||||
progress := ctx.GetTransferProgress()
|
||||
failedMessages := progress.GetFailedMessages()
|
||||
a.Equal(ctx.GetTestingT(), wantCount, len(failedMessages), "failed messages: %v", failedMessages)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func transferExportedMessages(messages *gherkin.DataTable) error {
|
||||
expectedMessages := map[string][]MessageAttributes{}
|
||||
|
||||
head := messages.Rows[0].Cells
|
||||
for _, row := range messages.Rows[1:] {
|
||||
folder := ""
|
||||
msg := MessageAttributes{}
|
||||
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "folder":
|
||||
folder = cell.Value
|
||||
case "subject":
|
||||
msg.subject = cell.Value
|
||||
case "from":
|
||||
msg.from = cell.Value
|
||||
case "to":
|
||||
msg.to = []string{cell.Value}
|
||||
case "time":
|
||||
date, err := time.Parse(timeFormat, cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to parse time")
|
||||
}
|
||||
msg.date = date.Unix()
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
}
|
||||
|
||||
expectedMessages[folder] = append(expectedMessages[folder], msg)
|
||||
sort.Sort(BySubject(expectedMessages[folder]))
|
||||
}
|
||||
|
||||
exportRoot := ctx.GetTransferLocalRootForExport()
|
||||
exportedMessages, err := readMessages(exportRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scanning exported messages")
|
||||
}
|
||||
|
||||
a.Equal(ctx.GetTestingT(), expectedMessages, exportedMessages)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func exportedMessagesMatchTheOriginalOnes() error {
|
||||
importRoot := ctx.GetTransferLocalRootForImport()
|
||||
exportRoot := ctx.GetTransferLocalRootForExport()
|
||||
|
||||
importMessages, err := readMessages(importRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scanning messages for import")
|
||||
}
|
||||
exportMessages, err := readMessages(exportRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scanning exported messages")
|
||||
}
|
||||
delete(exportMessages, "All Mail") // Ignore All Mail.
|
||||
|
||||
a.Equal(ctx.GetTestingT(), importMessages, exportMessages)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func readMessages(root string) (map[string][]MessageAttributes, error) {
|
||||
files, err := ioutil.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
messagesPerLabel := map[string][]MessageAttributes{}
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
fileReader, err := os.Open(filepath.Join(root, file.Name()))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "opening file")
|
||||
}
|
||||
|
||||
if filepath.Ext(file.Name()) == ".eml" {
|
||||
label := filepath.Base(root)
|
||||
msg, err := readMessageAttributes(fileReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messagesPerLabel[label] = append(messagesPerLabel[label], msg)
|
||||
sort.Sort(BySubject(messagesPerLabel[label]))
|
||||
} else if filepath.Ext(file.Name()) == ".mbox" {
|
||||
label := strings.TrimSuffix(file.Name(), ".mbox")
|
||||
mboxReader := mbox.NewReader(fileReader)
|
||||
for {
|
||||
msgReader, err := mboxReader.NextMessage()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "reading next message")
|
||||
}
|
||||
msg, err := readMessageAttributes(msgReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messagesPerLabel[label] = append(messagesPerLabel[label], msg)
|
||||
}
|
||||
sort.Sort(BySubject(messagesPerLabel[label]))
|
||||
}
|
||||
} else {
|
||||
subfolderRoot := filepath.Join(root, file.Name())
|
||||
subfolderMessagesPerLabel, err := readMessages(subfolderRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range subfolderMessagesPerLabel {
|
||||
messagesPerLabel[key] = append(messagesPerLabel[key], value...)
|
||||
sort.Sort(BySubject(messagesPerLabel[key]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return messagesPerLabel, nil
|
||||
}
|
||||
|
||||
type MessageAttributes struct {
|
||||
subject string
|
||||
from string
|
||||
to []string
|
||||
date int64
|
||||
}
|
||||
|
||||
func readMessageAttributes(fileReader io.Reader) (MessageAttributes, error) {
|
||||
entity, err := message.Read(fileReader)
|
||||
if err != nil {
|
||||
return MessageAttributes{}, errors.Wrap(err, "reading file")
|
||||
}
|
||||
date, err := parseTime(entity.Header.Get("date"))
|
||||
if err != nil {
|
||||
return MessageAttributes{}, errors.Wrap(err, "parsing date")
|
||||
}
|
||||
from, err := parseAddress(entity.Header.Get("from"))
|
||||
if err != nil {
|
||||
return MessageAttributes{}, errors.Wrap(err, "parsing from")
|
||||
}
|
||||
to, err := parseAddresses(entity.Header.Get("to"))
|
||||
if err != nil {
|
||||
return MessageAttributes{}, errors.Wrap(err, "parsing to")
|
||||
}
|
||||
return MessageAttributes{
|
||||
subject: entity.Header.Get("subject"),
|
||||
from: from,
|
||||
to: to,
|
||||
date: date.Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseTime(input string) (time.Time, error) {
|
||||
for _, format := range []string{time.RFC1123, time.RFC1123Z} {
|
||||
t, err := time.Parse(format, input)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, errors.New("Unrecognized time format")
|
||||
}
|
||||
|
||||
func parseAddresses(input string) ([]string, error) {
|
||||
addresses, err := mail.ParseAddressList(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := []string{}
|
||||
for _, address := range addresses {
|
||||
result = append(result, address.Address)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseAddress(input string) (string, error) {
|
||||
address, err := mail.ParseAddress(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return address.Address, nil
|
||||
}
|
||||
|
||||
// BySubject implements sort.Interface based on the subject field.
|
||||
type BySubject []MessageAttributes
|
||||
|
||||
func (a BySubject) Len() int { return len(a) }
|
||||
func (a BySubject) Less(i, j int) bool { return a[i].subject < a[j].subject }
|
||||
func (a BySubject) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
261
test/transfer_setup_test.go
Normal file
261
test/transfer_setup_test.go
Normal file
@ -0,0 +1,261 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-mbox"
|
||||
)
|
||||
|
||||
func TransferSetupFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^there are EML files$`, thereAreEMLFiles)
|
||||
s.Step(`^there is EML file "([^"]*)"$`, thereIsEMLFile)
|
||||
s.Step(`^there is MBOX file "([^"]*)" with messages$`, thereIsMBOXFileWithMessages)
|
||||
s.Step(`^there is MBOX file "([^"]*)"$`, thereIsMBOXFile)
|
||||
s.Step(`^there are IMAP mailboxes$`, thereAreIMAPMailboxes)
|
||||
s.Step(`^there are IMAP messages$`, thereAreIMAPMessages)
|
||||
s.Step(`^there is IMAP message in mailbox "([^"]*)" with seq (\d+), uid (\d+), time "([^"]*)" and subject "([^"]*)"$`, thereIsIMAPMessage)
|
||||
}
|
||||
|
||||
func thereAreEMLFiles(messages *gherkin.DataTable) error {
|
||||
head := messages.Rows[0].Cells
|
||||
for _, row := range messages.Rows[1:] {
|
||||
fileName := ""
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "file":
|
||||
fileName = cell.Value
|
||||
case "from", "to", "subject", "time", "body":
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
}
|
||||
|
||||
body := getBodyFromDataRow(head, row)
|
||||
if err := createFile(fileName, body); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func thereIsEMLFile(fileName string, message *gherkin.DocString) error {
|
||||
return createFile(fileName, message.Content)
|
||||
}
|
||||
|
||||
func thereIsMBOXFileWithMessages(fileName string, messages *gherkin.DataTable) error {
|
||||
mboxBuffer := &bytes.Buffer{}
|
||||
mboxWriter := mbox.NewWriter(mboxBuffer)
|
||||
|
||||
head := messages.Rows[0].Cells
|
||||
for _, row := range messages.Rows[1:] {
|
||||
from := ""
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "from":
|
||||
from = cell.Value
|
||||
case "to", "subject", "time", "body":
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
}
|
||||
|
||||
body := getBodyFromDataRow(head, row)
|
||||
|
||||
messageWriter, err := mboxWriter.CreateMessage(from, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = messageWriter.Write([]byte(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return createFile(fileName, mboxBuffer.String())
|
||||
}
|
||||
|
||||
func thereIsMBOXFile(fileName string, messages *gherkin.DocString) error {
|
||||
return createFile(fileName, messages.Content)
|
||||
}
|
||||
|
||||
func thereAreIMAPMailboxes(mailboxes *gherkin.DataTable) error {
|
||||
imapServer := ctx.GetTransferRemoteIMAPServer()
|
||||
head := mailboxes.Rows[0].Cells
|
||||
for _, row := range mailboxes.Rows[1:] {
|
||||
mailboxName := ""
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "name":
|
||||
mailboxName = cell.Value
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
}
|
||||
imapServer.AddMailbox(mailboxName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func thereAreIMAPMessages(messages *gherkin.DataTable) (err error) {
|
||||
imapServer := ctx.GetTransferRemoteIMAPServer()
|
||||
head := messages.Rows[0].Cells
|
||||
for _, row := range messages.Rows[1:] {
|
||||
mailboxName := ""
|
||||
date := time.Now()
|
||||
subject := ""
|
||||
seqNum := 0
|
||||
uid := 0
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "mailbox":
|
||||
mailboxName = cell.Value
|
||||
case "uid":
|
||||
uid, err = strconv.Atoi(cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to parse uid")
|
||||
}
|
||||
case "seqnum":
|
||||
seqNum, err = strconv.Atoi(cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to parse seqnum")
|
||||
}
|
||||
case "time":
|
||||
date, err = time.Parse(timeFormat, cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to parse time")
|
||||
}
|
||||
case "subject":
|
||||
subject = cell.Value
|
||||
case "from", "to", "body":
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
}
|
||||
|
||||
body := getBodyFromDataRow(head, row)
|
||||
imapMessage, err := getIMAPMessage(seqNum, uid, date, subject, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imapServer.AddMessage(mailboxName, imapMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func thereIsIMAPMessage(mailboxName string, seqNum, uid int, dateValue, subject string, message *gherkin.DocString) error {
|
||||
imapServer := ctx.GetTransferRemoteIMAPServer()
|
||||
|
||||
date, err := time.Parse(timeFormat, dateValue)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to parse time")
|
||||
}
|
||||
|
||||
imapMessage, err := getIMAPMessage(seqNum, uid, date, subject, message.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imapServer.AddMessage(mailboxName, imapMessage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBodyFromDataRow(head []*gherkin.TableCell, row *gherkin.TableRow) string {
|
||||
body := "hello"
|
||||
headers := textproto.MIMEHeader{}
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "from":
|
||||
headers.Set("from", cell.Value)
|
||||
case "to":
|
||||
headers.Set("to", cell.Value)
|
||||
case "subject":
|
||||
headers.Set("subject", cell.Value)
|
||||
case "time":
|
||||
date, err := time.Parse(timeFormat, cell.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
headers.Set("date", date.Format(time.RFC1123))
|
||||
case "body":
|
||||
body = cell.Value
|
||||
}
|
||||
}
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
_ = message.WriteHeader(buffer, headers)
|
||||
return buffer.String() + body + "\n\n"
|
||||
}
|
||||
|
||||
func getIMAPMessage(seqNum, uid int, date time.Time, subject, body string) (*imap.Message, error) {
|
||||
reader := bytes.NewBufferString(body)
|
||||
bodyStructure, err := message.NewBodyStructure(reader)
|
||||
if err != nil {
|
||||
return nil, internalError(err, "failed to parse body structure")
|
||||
}
|
||||
imapBodyStructure, err := bodyStructure.IMAPBodyStructure([]int{})
|
||||
if err != nil {
|
||||
return nil, internalError(err, "failed to parse body structure")
|
||||
}
|
||||
bodySection, _ := imap.ParseBodySectionName("BODY[]")
|
||||
|
||||
return &imap.Message{
|
||||
SeqNum: uint32(seqNum),
|
||||
Uid: uint32(uid),
|
||||
Size: uint32(len(body)),
|
||||
Envelope: &imap.Envelope{
|
||||
Date: date,
|
||||
Subject: subject,
|
||||
},
|
||||
BodyStructure: imapBodyStructure,
|
||||
Body: map[*imap.BodySectionName]imap.Literal{
|
||||
bodySection: bytes.NewBufferString(body),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createFile(fileName, body string) error {
|
||||
root := ctx.GetTransferLocalRootForImport()
|
||||
filePath := filepath.Join(root, fileName)
|
||||
|
||||
dirPath := filepath.Dir(filePath)
|
||||
err := os.MkdirAll(dirPath, os.ModePerm)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to create dir")
|
||||
}
|
||||
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to create file")
|
||||
}
|
||||
defer f.Close() //nolint
|
||||
|
||||
_, err = f.WriteString(body)
|
||||
return internalError(err, "failed to write to file")
|
||||
}
|
||||
125
test/users_actions_test.go
Normal file
125
test/users_actions_test.go
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail 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.
|
||||
//
|
||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"github.com/cucumber/godog"
|
||||
)
|
||||
|
||||
func UsersActionsFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^"([^"]*)" logs in$`, userLogsIn)
|
||||
s.Step(`^"([^"]*)" logs in with bad password$`, userLogsInWithBadPassword)
|
||||
s.Step(`^"([^"]*)" logs out$`, userLogsOut)
|
||||
s.Step(`^"([^"]*)" changes the address mode$`, userChangesTheAddressMode)
|
||||
s.Step(`^user deletes "([^"]*)"$`, userDeletesUser)
|
||||
s.Step(`^user deletes "([^"]*)" with cache$`, userDeletesUserWithCache)
|
||||
s.Step(`^"([^"]*)" swaps address "([^"]*)" with address "([^"]*)"$`, swapsAddressWithAddress)
|
||||
}
|
||||
|
||||
func userLogsIn(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
ctx.SetLastError(ctx.LoginUser(account.Username(), account.Password(), account.MailboxPassword()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func userLogsInWithBadPassword(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
ctx.SetLastError(ctx.LoginUser(account.Username(), "you shall not pass!", "123"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func userLogsOut(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
ctx.SetLastError(ctx.LogoutUser(account.Username()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func userChangesTheAddressMode(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
if err := user.SwitchAddressMode(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.EventuallySyncIsFinishedForUsername(account.Username())
|
||||
return nil
|
||||
}
|
||||
|
||||
func userDeletesUser(bddUserID string) error {
|
||||
return deleteUser(bddUserID, false)
|
||||
}
|
||||
|
||||
func userDeletesUserWithCache(bddUserID string) error {
|
||||
return deleteUser(bddUserID, true)
|
||||
}
|
||||
|
||||
func deleteUser(bddUserID string, cache bool) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
ctx.SetLastError(ctx.GetUsers().DeleteUser(user.ID(), cache))
|
||||
return nil
|
||||
}
|
||||
|
||||
func swapsAddressWithAddress(bddUserID, bddAddressID1, bddAddressID2 string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
|
||||
address1ID := account.GetAddressID(bddAddressID1)
|
||||
address2ID := account.GetAddressID(bddAddressID2)
|
||||
addressIDs := make([]string, len(*account.Addresses()))
|
||||
|
||||
var address1Index, address2Index int
|
||||
for i, v := range *account.Addresses() {
|
||||
if v.ID == address1ID {
|
||||
address1Index = i
|
||||
}
|
||||
if v.ID == address2ID {
|
||||
address2Index = i
|
||||
}
|
||||
addressIDs[i] = v.ID
|
||||
}
|
||||
|
||||
addressIDs[address1Index], addressIDs[address2Index] = addressIDs[address2Index], addressIDs[address1Index]
|
||||
|
||||
ctx.ReorderAddresses(account.Username(), bddAddressID1, bddAddressID2)
|
||||
|
||||
return ctx.GetPMAPIController().ReorderAddresses(account.User(), addressIDs)
|
||||
}
|
||||
@ -24,8 +24,7 @@ import (
|
||||
a "github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BridgeChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^bridge response is "([^"]*)"$`, bridgeResponseIs)
|
||||
func UsersChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^"([^"]*)" has address mode in "([^"]*)" mode$`, userHasAddressModeInMode)
|
||||
s.Step(`^"([^"]*)" is disconnected$`, userIsDisconnected)
|
||||
s.Step(`^"([^"]*)" is connected$`, userIsConnected)
|
||||
@ -39,27 +38,17 @@ func BridgeChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^"([^"]*)" has API auth$`, isAuthorized)
|
||||
}
|
||||
|
||||
func bridgeResponseIs(expectedResponse string) error {
|
||||
err := ctx.GetLastBridgeError()
|
||||
if expectedResponse == "OK" {
|
||||
a.NoError(ctx.GetTestingT(), err)
|
||||
} else {
|
||||
a.EqualError(ctx.GetTestingT(), err, expectedResponse)
|
||||
}
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func userHasAddressModeInMode(bddUserID, wantAddressMode string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
addressMode := "split"
|
||||
if bridgeUser.IsCombinedAddressMode() {
|
||||
if user.IsCombinedAddressMode() {
|
||||
addressMode = "combined"
|
||||
}
|
||||
a.Equal(ctx.GetTestingT(), wantAddressMode, addressMode)
|
||||
@ -71,12 +60,12 @@ func userIsDisconnected(bddUserID string) error {
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
a.Eventually(ctx.GetTestingT(), func() bool {
|
||||
return !bridgeUser.IsConnected()
|
||||
return !user.IsConnected()
|
||||
}, 5*time.Second, 10*time.Millisecond)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
@ -87,13 +76,13 @@ func userIsConnected(bddUserID string) error {
|
||||
return godog.ErrPending
|
||||
}
|
||||
t := ctx.GetTestingT()
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
a.Eventually(ctx.GetTestingT(), bridgeUser.IsConnected, 5*time.Second, 10*time.Millisecond)
|
||||
a.NotEmpty(t, bridgeUser.GetPrimaryAddress())
|
||||
a.NotEmpty(t, bridgeUser.GetStoreAddresses())
|
||||
a.Eventually(ctx.GetTestingT(), user.IsConnected, 5*time.Second, 10*time.Millisecond)
|
||||
a.NotEmpty(t, user.GetPrimaryAddress())
|
||||
a.NotEmpty(t, user.GetStoreAddresses())
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
@ -122,11 +111,11 @@ func userHasLoadedStore(bddUserID string) error {
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
a.NotNil(ctx.GetTestingT(), bridgeUser.GetStore())
|
||||
a.NotNil(ctx.GetTestingT(), user.GetStore())
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
@ -135,11 +124,11 @@ func userDoesNotHaveLoadedStore(bddUserID string) error {
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
a.Nil(ctx.GetTestingT(), bridgeUser.GetStore())
|
||||
a.Nil(ctx.GetTestingT(), user.GetStore())
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
@ -173,28 +162,28 @@ func userDoesNotHaveRunningEventLoop(bddUserID string) error {
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func isAuthorized(accountName string) error {
|
||||
account := ctx.GetTestAccount(accountName)
|
||||
func isAuthorized(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
a.Eventually(ctx.GetTestingT(), bridgeUser.IsAuthorized, 5*time.Second, 10*time.Millisecond)
|
||||
a.Eventually(ctx.GetTestingT(), user.IsAuthorized, 5*time.Second, 10*time.Millisecond)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func isNotAuthorized(accountName string) error {
|
||||
account := ctx.GetTestAccount(accountName)
|
||||
func isNotAuthorized(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
a.Eventually(ctx.GetTestingT(), func() bool { return !bridgeUser.IsAuthorized() }, 5*time.Second, 10*time.Millisecond)
|
||||
a.Eventually(ctx.GetTestingT(), func() bool { return !user.IsAuthorized() }, 5*time.Second, 10*time.Millisecond)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
@ -25,8 +25,7 @@ import (
|
||||
a "github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BridgeSetupFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^there is no internet connection$`, thereIsNoInternetConnection)
|
||||
func UsersSetupFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^there is user "([^"]*)"$`, thereIsUser)
|
||||
s.Step(`^there is connected user "([^"]*)"$`, thereIsConnectedUser)
|
||||
s.Step(`^there is disconnected user "([^"]*)"$`, thereIsDisconnectedUser)
|
||||
@ -35,11 +34,6 @@ func BridgeSetupFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^there is "([^"]*)" in "([^"]*)" address mode$`, thereIsUserWithAddressMode)
|
||||
}
|
||||
|
||||
func thereIsNoInternetConnection() error {
|
||||
ctx.GetPMAPIController().TurnInternetConnectionOff()
|
||||
return nil
|
||||
}
|
||||
|
||||
func thereIsUser(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
@ -87,7 +81,11 @@ func thereIsDisconnectedUser(bddUserID string) error {
|
||||
// logout is also called and if we would do login at the same time, it
|
||||
// wouldn't work. 100 ms after event loop is stopped should be enough.
|
||||
a.Eventually(ctx.GetTestingT(), func() bool {
|
||||
return !user.GetStore().TestGetEventLoop().IsRunning()
|
||||
store := user.GetStore()
|
||||
if store == nil {
|
||||
return true
|
||||
}
|
||||
return !store.TestGetEventLoop().IsRunning()
|
||||
}, 1*time.Second, 10*time.Millisecond)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return ctx.GetTestingError()
|
||||
@ -120,20 +118,20 @@ func thereIsUserWithAddressMode(bddUserID, wantAddressMode string) error {
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
bridgeUser, err := ctx.GetUser(account.Username())
|
||||
user, err := ctx.GetUser(account.Username())
|
||||
if err != nil {
|
||||
return internalError(err, "getting user %s", account.Username())
|
||||
}
|
||||
addressMode := "split"
|
||||
if bridgeUser.IsCombinedAddressMode() {
|
||||
if user.IsCombinedAddressMode() {
|
||||
addressMode = "combined"
|
||||
}
|
||||
if wantAddressMode != addressMode {
|
||||
err := bridgeUser.SwitchAddressMode()
|
||||
err := user.SwitchAddressMode()
|
||||
if err != nil {
|
||||
return internalError(err, "switching mode")
|
||||
}
|
||||
}
|
||||
ctx.EventuallySyncIsFinishedForUsername(bridgeUser.Username())
|
||||
ctx.EventuallySyncIsFinishedForUsername(user.Username())
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user