mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2026-02-04 08:18:34 +00:00
Merge release/golden-gate into devel
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
.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 BRIDGE_VERSION:=1.5.5+integrationtests
|
||||
export VERBOSITY?=fatal
|
||||
export TEST_DATA=testdata
|
||||
export TEST_APP?=bridge
|
||||
|
||||
@ -20,10 +20,13 @@ package tests
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func APIChecksFeatureContext(s *godog.Suite) {
|
||||
@ -31,6 +34,7 @@ func APIChecksFeatureContext(s *godog.Suite) {
|
||||
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)
|
||||
s.Step(`^API client manager user-agent is "([^"]*)"$`, clientManagerUserAgent)
|
||||
}
|
||||
|
||||
func apiIsCalledWith(endpoint string, data *gherkin.DocString) error {
|
||||
@ -117,3 +121,14 @@ func apiMailboxForAddressOfUserHasMessages(mailboxName, bddAddressID, bddUserID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientManagerUserAgent(expectedUserAgent string) error {
|
||||
expectedUserAgent = strings.ReplaceAll(expectedUserAgent, "[GOOS]", runtime.GOOS)
|
||||
|
||||
assert.Eventually(ctx.GetTestingT(), func() bool {
|
||||
userAgent := ctx.GetClientManager().GetUserAgent()
|
||||
return userAgent == expectedUserAgent
|
||||
}, 5*time.Second, time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
)
|
||||
@ -34,7 +33,7 @@ func (ctx *TestContext) GetBridge() *bridge.Bridge {
|
||||
// withBridgeInstance creates a bridge instance for use in the test.
|
||||
// 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.bridge = newBridgeInstance(ctx.t, ctx.locations, ctx.cache, ctx.settings, ctx.credStore, ctx.listener, ctx.clientManager)
|
||||
ctx.users = ctx.bridge.Users
|
||||
ctx.addCleanupChecked(ctx.bridge.ClearData, "Cleaning bridge data")
|
||||
}
|
||||
@ -61,12 +60,13 @@ func (ctx *TestContext) RestartBridge() error {
|
||||
// newBridgeInstance creates a new bridge instance configured to use the given config/credstore.
|
||||
func newBridgeInstance(
|
||||
t *bddT,
|
||||
cfg *fakeConfig,
|
||||
locations bridge.Locator,
|
||||
cache bridge.Cacher,
|
||||
settings *fakeSettings,
|
||||
credStore users.CredentialsStorer,
|
||||
eventListener listener.Listener,
|
||||
clientManager users.ClientManager,
|
||||
) *bridge.Bridge {
|
||||
panicHandler := &panicHandler{t: t}
|
||||
pref := preferences.New(cfg)
|
||||
return bridge.New(cfg, pref, panicHandler, eventListener, clientManager, credStore)
|
||||
return bridge.New(locations, cache, settings, panicHandler, eventListener, clientManager, credStore)
|
||||
}
|
||||
|
||||
55
test/context/cache.go
Normal file
55
test/context/cache.go
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2021 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"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type fakeCache struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
// newFakeCache creates a temporary folder for files.
|
||||
// It's expected the test calls `ClearData` before finish to remove it from the file system.
|
||||
func newFakeCache() *fakeCache {
|
||||
dir, err := ioutil.TempDir("", "test-cache")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &fakeCache{
|
||||
dir: dir,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDBDir returns folder for db files.
|
||||
func (c *fakeCache) GetDBDir() string {
|
||||
return c.dir
|
||||
}
|
||||
|
||||
// GetIMAPCachePath returns path to file with IMAP status.
|
||||
func (c *fakeCache) GetIMAPCachePath() string {
|
||||
return filepath.Join(c.dir, "user_info.json")
|
||||
}
|
||||
|
||||
// GetTransferDir returns folder for import-export rules files.
|
||||
func (c *fakeCache) GetTransferDir() string {
|
||||
return c.dir
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
// Copyright (c) 2021 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"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type fakeConfig struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
// newFakeConfig creates a temporary folder for files.
|
||||
// It's expected the test calls `ClearData` before finish to remove it from the file system.
|
||||
func newFakeConfig() *fakeConfig {
|
||||
dir, err := ioutil.TempDir("", "example")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cfg := &fakeConfig{
|
||||
dir: dir,
|
||||
}
|
||||
|
||||
// We must generate cert.pem and key.pem to prevent errors when attempting to open them.
|
||||
if _, err = config.GenerateTLSConfig(cfg.GetTLSCertPath(), cfg.GetTLSKeyPath()); err != nil {
|
||||
logrus.WithError(err).Fatal()
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (c *fakeConfig) ClearData() error {
|
||||
return os.RemoveAll(c.dir)
|
||||
}
|
||||
func (c *fakeConfig) GetAPIConfig() *pmapi.ClientConfig {
|
||||
return &pmapi.ClientConfig{
|
||||
AppVersion: "Bridge_" + constants.Version,
|
||||
ClientID: "bridge",
|
||||
}
|
||||
}
|
||||
func (c *fakeConfig) GetDBDir() string {
|
||||
return c.dir
|
||||
}
|
||||
func (c *fakeConfig) GetVersion() string {
|
||||
return constants.Version
|
||||
}
|
||||
func (c *fakeConfig) GetLogDir() string {
|
||||
return c.dir
|
||||
}
|
||||
func (c *fakeConfig) GetLogPrefix() string {
|
||||
return "test"
|
||||
}
|
||||
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")
|
||||
}
|
||||
func (c *fakeConfig) GetTLSKeyPath() string {
|
||||
return filepath.Join(c.dir, "key.pem")
|
||||
}
|
||||
func (c *fakeConfig) GetEventsPath() string {
|
||||
return filepath.Join(c.dir, "events.json")
|
||||
}
|
||||
func (c *fakeConfig) GetIMAPCachePath() string {
|
||||
return filepath.Join(c.dir, "user_info.json")
|
||||
}
|
||||
func (c *fakeConfig) GetDefaultAPIPort() int {
|
||||
return 21042
|
||||
}
|
||||
func (c *fakeConfig) GetDefaultIMAPPort() int {
|
||||
return 21100 + rand.Intn(100)
|
||||
}
|
||||
func (c *fakeConfig) GetDefaultSMTPPort() int {
|
||||
return 21200 + rand.Intn(100)
|
||||
}
|
||||
@ -22,6 +22,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
@ -41,7 +42,9 @@ type server interface {
|
||||
type TestContext struct {
|
||||
// Base setup for the whole bridge (core & imap & smtp).
|
||||
t *bddT
|
||||
cfg *fakeConfig
|
||||
cache *fakeCache
|
||||
locations *fakeLocations
|
||||
settings *fakeSettings
|
||||
listener listener.Listener
|
||||
testAccounts *accounts.TestAccounts
|
||||
|
||||
@ -92,13 +95,17 @@ type TestContext struct {
|
||||
func New(app string) *TestContext {
|
||||
setLogrusVerbosityFromEnv()
|
||||
|
||||
cfg := newFakeConfig()
|
||||
|
||||
cm := pmapi.NewClientManager(cfg.GetAPIConfig())
|
||||
configName := app
|
||||
if app == "ie" {
|
||||
configName = "importExport"
|
||||
}
|
||||
cm := pmapi.NewClientManager(pmapi.GetAPIConfig(configName, constants.Version))
|
||||
|
||||
ctx := &TestContext{
|
||||
t: &bddT{},
|
||||
cfg: cfg,
|
||||
cache: newFakeCache(),
|
||||
locations: newFakeLocations(),
|
||||
settings: newFakeSettings(),
|
||||
listener: listener.New(),
|
||||
pmapiController: newPMAPIController(cm),
|
||||
clientManager: cm,
|
||||
@ -115,7 +122,7 @@ func New(app string) *TestContext {
|
||||
}
|
||||
|
||||
// Ensure that the config is cleaned up after the test is over.
|
||||
ctx.addCleanupChecked(cfg.ClearData, "Cleaning bridge config data")
|
||||
ctx.addCleanupChecked(ctx.locations.Clear, "Cleaning bridge config data")
|
||||
|
||||
// Create bridge or import-export instance under test.
|
||||
switch app {
|
||||
@ -144,6 +151,11 @@ func (ctx *TestContext) GetPMAPIController() PMAPIController {
|
||||
return ctx.pmapiController
|
||||
}
|
||||
|
||||
// GetClientManager returns client manager being used for testing.
|
||||
func (ctx *TestContext) GetClientManager() *pmapi.ClientManager {
|
||||
return ctx.clientManager
|
||||
}
|
||||
|
||||
// GetTestingT returns testing.T compatible struct.
|
||||
func (ctx *TestContext) GetTestingT() *bddT { //nolint[golint]
|
||||
return ctx.t
|
||||
|
||||
@ -22,9 +22,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/tls"
|
||||
"github.com/ProtonMail/proton-bridge/internal/imap"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
||||
"github.com/ProtonMail/proton-bridge/test/mocks"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -53,13 +53,13 @@ func (ctx *TestContext) withIMAPServer() {
|
||||
return
|
||||
}
|
||||
|
||||
settingsPath, _ := ctx.locations.ProvideSettingsPath()
|
||||
ph := newPanicHandler(ctx.t)
|
||||
pref := preferences.New(ctx.cfg)
|
||||
port := pref.GetInt(preferences.IMAPPortKey)
|
||||
tls, _ := config.GetTLSConfig(ctx.cfg)
|
||||
port := ctx.settings.GetInt(settings.IMAPPortKey)
|
||||
tls, _ := tls.New(settingsPath).GetConfig()
|
||||
|
||||
backend := imap.NewIMAPBackend(ph, ctx.listener, ctx.cfg, ctx.bridge)
|
||||
server := imap.NewIMAPServer(true, true, port, tls, backend, ctx.listener)
|
||||
backend := imap.NewIMAPBackend(ph, ctx.listener, ctx.cache, ctx.bridge)
|
||||
server := imap.NewIMAPServer(ph, true, true, port, tls, backend, ctx.listener)
|
||||
|
||||
go server.ListenAndServe()
|
||||
require.NoError(ctx.t, waitForPort(port, 5*time.Second))
|
||||
|
||||
@ -31,18 +31,19 @@ func (ctx *TestContext) GetImportExport() *importexport.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.importExport = newImportExportInstance(ctx.t, ctx.locations, ctx.cache, 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,
|
||||
locations importexport.Locator,
|
||||
cache importexport.Cacher,
|
||||
credStore users.CredentialsStorer,
|
||||
eventListener listener.Listener,
|
||||
clientManager users.ClientManager,
|
||||
) *importexport.ImportExport {
|
||||
panicHandler := &panicHandler{t: t}
|
||||
return importexport.New(cfg, panicHandler, eventListener, clientManager, credStore)
|
||||
return importexport.New(locations, cache, panicHandler, eventListener, clientManager, credStore)
|
||||
}
|
||||
|
||||
50
test/context/locations.go
Normal file
50
test/context/locations.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2021 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"
|
||||
"os"
|
||||
)
|
||||
|
||||
type fakeLocations struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
func newFakeLocations() *fakeLocations {
|
||||
dir, err := ioutil.TempDir("", "test-cache")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &fakeLocations{
|
||||
dir: dir,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *fakeLocations) ProvideLogsPath() (string, error) {
|
||||
return l.dir, nil
|
||||
}
|
||||
|
||||
func (l *fakeLocations) ProvideSettingsPath() (string, error) {
|
||||
return l.dir, nil
|
||||
}
|
||||
|
||||
func (l *fakeLocations) Clear() error {
|
||||
return os.RemoveAll(l.dir)
|
||||
}
|
||||
50
test/context/settings.go
Normal file
50
test/context/settings.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2021 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"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
)
|
||||
|
||||
type fakeSettings struct {
|
||||
*settings.Settings
|
||||
dir string
|
||||
}
|
||||
|
||||
// newFakeSettings creates a temporary folder for files.
|
||||
// It's expected the test calls `ClearData` before finish to remove it from the file system.
|
||||
func newFakeSettings() *fakeSettings {
|
||||
dir, err := ioutil.TempDir("", "test-settings")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s := &fakeSettings{
|
||||
Settings: settings.New(dir),
|
||||
dir: dir,
|
||||
}
|
||||
|
||||
// We should use nonstandard ports to not conflict with bridge.
|
||||
s.SetInt(settings.IMAPPortKey, 21100+rand.Intn(100))
|
||||
s.SetInt(settings.SMTPPortKey, 21200+rand.Intn(100))
|
||||
|
||||
return s
|
||||
}
|
||||
@ -22,9 +22,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/tls"
|
||||
"github.com/ProtonMail/proton-bridge/internal/smtp"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
||||
"github.com/ProtonMail/proton-bridge/test/mocks"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -53,13 +53,13 @@ func (ctx *TestContext) withSMTPServer() {
|
||||
return
|
||||
}
|
||||
|
||||
settingsPath, _ := ctx.locations.ProvideSettingsPath()
|
||||
ph := newPanicHandler(ctx.t)
|
||||
pref := preferences.New(ctx.cfg)
|
||||
tls, _ := config.GetTLSConfig(ctx.cfg)
|
||||
port := pref.GetInt(preferences.SMTPPortKey)
|
||||
useSSL := pref.GetBool(preferences.SMTPSSLKey)
|
||||
tls, _ := tls.New(settingsPath).GetConfig()
|
||||
port := ctx.settings.GetInt(settings.SMTPPortKey)
|
||||
useSSL := ctx.settings.GetBool(settings.SMTPSSLKey)
|
||||
|
||||
backend := smtp.NewSMTPBackend(ph, ctx.listener, pref, ctx.bridge)
|
||||
backend := smtp.NewSMTPBackend(ph, ctx.listener, ctx.settings, ctx.bridge)
|
||||
server := smtp.NewSMTPServer(true, port, useSSL, tls, backend, ctx.listener)
|
||||
|
||||
go server.ListenAndServe()
|
||||
|
||||
@ -96,7 +96,7 @@ func (ctx *TestContext) GetStoreMailbox(username, addressID, mailboxName string)
|
||||
func (ctx *TestContext) GetDatabaseFilePath(userID string) string {
|
||||
// We cannot use store to get information because we need to check db file also when user is deleted from bridge.
|
||||
fileName := fmt.Sprintf("mailbox-%v.db", userID)
|
||||
return filepath.Join(ctx.cfg.GetDBDir(), fileName)
|
||||
return filepath.Join(ctx.cache.GetDBDir(), fileName)
|
||||
}
|
||||
|
||||
// WaitForSync waits for sync to be done.
|
||||
|
||||
28
test/fakeapi/download.go
Normal file
28
test/fakeapi/download.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2021 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 fakeapi
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) DownloadAndVerify(string, string, *crypto.KeyRing) (io.Reader, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@ -216,24 +216,47 @@ func (api *FakePMAPI) generateMessageFromImportRequest(msgReq *pmapi.ImportMsgRe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
messageID := api.controller.messageIDGenerator.next("")
|
||||
existingMsg := api.findMessage(m)
|
||||
if existingMsg != nil {
|
||||
return existingMsg, nil
|
||||
}
|
||||
|
||||
messageID := api.controller.messageIDGenerator.next("")
|
||||
return &pmapi.Message{
|
||||
ID: messageID,
|
||||
AddressID: msgReq.AddressID,
|
||||
Sender: m.Sender,
|
||||
ToList: m.ToList,
|
||||
Subject: m.Subject,
|
||||
Unread: msgReq.Unread,
|
||||
LabelIDs: append(msgReq.LabelIDs, pmapi.AllMailLabel),
|
||||
Body: m.Body,
|
||||
Header: m.Header,
|
||||
Flags: msgReq.Flags,
|
||||
Time: msgReq.Time,
|
||||
ID: messageID,
|
||||
ExternalID: m.ExternalID,
|
||||
AddressID: msgReq.AddressID,
|
||||
Sender: m.Sender,
|
||||
ToList: m.ToList,
|
||||
Subject: m.Subject,
|
||||
Unread: msgReq.Unread,
|
||||
LabelIDs: append(msgReq.LabelIDs, pmapi.AllMailLabel),
|
||||
Body: m.Body,
|
||||
Header: m.Header,
|
||||
Flags: msgReq.Flags,
|
||||
Time: msgReq.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) findMessage(newMsg *pmapi.Message) *pmapi.Message {
|
||||
if newMsg.ExternalID == "" {
|
||||
return nil
|
||||
}
|
||||
for _, msg := range api.messages {
|
||||
// API surely has better algorithm, but this one is enough for us for now.
|
||||
if !msg.IsDraft() &&
|
||||
msg.Subject == newMsg.Subject &&
|
||||
msg.ExternalID == newMsg.ExternalID {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) addMessage(message *pmapi.Message) {
|
||||
if api.findMessage(message) != nil {
|
||||
return
|
||||
}
|
||||
api.messages = append(api.messages, message)
|
||||
api.addEventMessage(pmapi.EventCreate, message)
|
||||
}
|
||||
|
||||
@ -99,3 +99,16 @@ Feature: IMAP remove messages from mailbox
|
||||
And there is IMAP client selected in "All Mail"
|
||||
When IMAP client marks message seq "1" as deleted
|
||||
Then IMAP response is "IMAP error: NO operation not allowed for 'All Mail' folder"
|
||||
|
||||
Scenario: Expunge specific message only
|
||||
Given there are 5 messages in mailbox "INBOX" for "user"
|
||||
And there is IMAP client logged in as "user"
|
||||
And there is IMAP client selected in "INBOX"
|
||||
When IMAP client marks message seq "1" as deleted
|
||||
Then IMAP response is "OK"
|
||||
When IMAP client marks message seq "2" as deleted
|
||||
Then IMAP response is "OK"
|
||||
When IMAP client sends command "UID EXPUNGE 1"
|
||||
Then IMAP response is "OK"
|
||||
And mailbox "INBOX" for "user" has 4 messages
|
||||
And message "2" in "INBOX" for "user" is marked as deleted
|
||||
|
||||
76
test/features/bridge/imap/message/move_local_folder.feature
Normal file
76
test/features/bridge/imap/message/move_local_folder.feature
Normal file
@ -0,0 +1,76 @@
|
||||
# IMAP clients can move message to local folder (setting \Deleted flag)
|
||||
# and then move it back (IMAP client does not remember the message,
|
||||
# so instead removing the flag it imports duplicate message).
|
||||
# Regular IMAP server would keep the message twice and later EXPUNGE would
|
||||
# not delete the message (EXPUNGE would delete the original message and
|
||||
# the new duplicate one would stay). Both Bridge and API detects duplicates;
|
||||
# therefore we need to remove \Deleted flag if IMAP client re-imports.
|
||||
Feature: IMAP move message out to and back from local folder
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
Given there is IMAP client logged in as "user"
|
||||
And there is IMAP client selected in "INBOX"
|
||||
|
||||
Scenario: Mark message as deleted and re-append again
|
||||
When IMAP client imports message to "INBOX"
|
||||
"""
|
||||
From: <john.doe@mail.com>
|
||||
To: <user@pm.me>
|
||||
Subject: foo
|
||||
Date: Mon, 02 Jan 2006 15:04:05 +0000
|
||||
Message-Id: <msgID>
|
||||
|
||||
hello
|
||||
"""
|
||||
Then IMAP response is "OK"
|
||||
When IMAP client marks message seq "1" as deleted
|
||||
Then IMAP response is "OK"
|
||||
When IMAP client imports message to "INBOX"
|
||||
"""
|
||||
From: <john.doe@mail.com>
|
||||
To: <user@pm.me>
|
||||
Subject: foo
|
||||
Date: Mon, 02 Jan 2006 15:04:05 +0000
|
||||
Message-Id: <msgID>
|
||||
|
||||
hello
|
||||
"""
|
||||
Then IMAP response is "OK"
|
||||
And mailbox "INBOX" for "user" has 1 message
|
||||
And mailbox "INBOX" for "user" has messages
|
||||
| from | to | subject | deleted |
|
||||
| john.doe@mail.com | user@pm.me | foo | false |
|
||||
|
||||
# We cannot control ID generation on API.
|
||||
@ignore-live
|
||||
Scenario: Mark internal message as deleted and re-append again
|
||||
# Each message has different subject so if the ID generations on fake API
|
||||
# changes, test will fail because not even external ID mechanism will work.
|
||||
When IMAP client imports message to "INBOX"
|
||||
"""
|
||||
From: <john.doe@mail.com>
|
||||
To: <user@pm.me>
|
||||
Subject: foo
|
||||
Date: Mon, 02 Jan 2006 15:04:05 +0000
|
||||
|
||||
hello
|
||||
"""
|
||||
Then IMAP response is "OK"
|
||||
When IMAP client marks message seq "1" as deleted
|
||||
Then IMAP response is "OK"
|
||||
# Fake API generates for the first message simple ID 1.
|
||||
When IMAP client imports message to "INBOX"
|
||||
"""
|
||||
From: <john.doe@mail.com>
|
||||
To: <user@pm.me>
|
||||
Subject: bar
|
||||
Date: Mon, 02 Jan 2006 15:04:05 +0000
|
||||
X-Pm-Internal-Id: 1
|
||||
|
||||
hello
|
||||
"""
|
||||
Then IMAP response is "OK"
|
||||
And mailbox "INBOX" for "user" has 1 message
|
||||
And mailbox "INBOX" for "user" has messages
|
||||
| from | to | subject | deleted |
|
||||
| john.doe@mail.com | user@pm.me | foo | false |
|
||||
24
test/features/bridge/imap/user_agent.feature
Normal file
24
test/features/bridge/imap/user_agent.feature
Normal file
@ -0,0 +1,24 @@
|
||||
Feature: User agent
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
|
||||
Scenario: Get user agent
|
||||
Given there is IMAP client logged in as "user"
|
||||
When IMAP client sends ID with argument:
|
||||
"""
|
||||
"name" "Foo" "version" "1.4.0"
|
||||
"""
|
||||
Then API client manager user-agent is "Foo/1.4.0 ([GOOS])"
|
||||
|
||||
Scenario: Update user agent
|
||||
Given there is IMAP client logged in as "user"
|
||||
When IMAP client sends ID with argument:
|
||||
"""
|
||||
"name" "Foo" "version" "1.4.0"
|
||||
"""
|
||||
Then API client manager user-agent is "Foo/1.4.0 ([GOOS])"
|
||||
When IMAP client sends ID with argument:
|
||||
"""
|
||||
"name" "Bar" "version" "4.2.0"
|
||||
"""
|
||||
Then API client manager user-agent is "Bar/4.2.0 ([GOOS])"
|
||||
@ -58,6 +58,8 @@ func IMAPActionsMessagesFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^IMAP client "([^"]*)" starts IDLE-ing$`, imapClientNamedStartsIDLEing)
|
||||
s.Step(`^IMAP client sends expunge$`, imapClientExpunge)
|
||||
s.Step(`^IMAP client "([^"]*)" sends expunge$`, imapClientNamedExpunge)
|
||||
s.Step(`^IMAP client sends ID with argument:$`, imapClientSendsID)
|
||||
s.Step(`^IMAP client "([^"]*)" sends ID with argument:$`, imapClientNamedSendsID)
|
||||
}
|
||||
|
||||
func imapClientSendsCommand(command string) error {
|
||||
@ -284,3 +286,13 @@ func imapClientNamedExpunge(imapClient string) error {
|
||||
ctx.SetIMAPLastResponse(imapClient, res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func imapClientSendsID(data *gherkin.DocString) error {
|
||||
return imapClientNamedSendsID("imap", data)
|
||||
}
|
||||
|
||||
func imapClientNamedSendsID(imapClient string, data *gherkin.DocString) error {
|
||||
res := ctx.GetIMAPClient(imapClient).ID(data.Content)
|
||||
ctx.SetIMAPLastResponse(imapClient, res)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/colors"
|
||||
)
|
||||
|
||||
@ -21,8 +21,10 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -163,7 +165,7 @@ func (c *IMAPClient) Search(query string) *IMAPResponse {
|
||||
// Message
|
||||
|
||||
func (c *IMAPClient) Append(mailboxName, msg string) *IMAPResponse {
|
||||
cmd := fmt.Sprintf("APPEND \"%s\" (\\Seen) \"25-Mar-2021 00:30:00 +0100\" {%d}\r\n%s", mailboxName, len(msg), msg)
|
||||
cmd := fmt.Sprintf("APPEND \"%s\" (\\Seen) \"%s\" {%d}\r\n%s", mailboxName, parseAppendDate(msg), len(msg), msg)
|
||||
return c.SendCommand(cmd)
|
||||
}
|
||||
|
||||
@ -175,10 +177,20 @@ func (c *IMAPClient) AppendBody(mailboxName, subject, from, to, body string) *IM
|
||||
msg += body
|
||||
msg += "\r\n"
|
||||
|
||||
cmd := fmt.Sprintf("APPEND \"%s\" (\\Seen) \"25-Mar-2021 00:30:00 +0100\" {%d}\r\n%s", mailboxName, len(msg), msg)
|
||||
cmd := fmt.Sprintf("APPEND \"%s\" (\\Seen) \"%s\" {%d}\r\n%s", mailboxName, parseAppendDate(msg), len(msg), msg)
|
||||
return c.SendCommand(cmd)
|
||||
}
|
||||
|
||||
func parseAppendDate(msg string) string {
|
||||
date := "25-Mar-2021 00:30:00 +0100"
|
||||
if m, _, _, _, err := message.Parse(strings.NewReader(msg)); err == nil {
|
||||
if t, err := m.Header.Date(); err == nil {
|
||||
date = t.Format("02-Jan-2006 15:04:05 -0700")
|
||||
}
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func (c *IMAPClient) Copy(ids, newMailboxName string) *IMAPResponse {
|
||||
return c.SendCommand(fmt.Sprintf("COPY %s \"%s\"", ids, newMailboxName))
|
||||
}
|
||||
@ -231,7 +243,8 @@ func (c *IMAPClient) Expunge() *IMAPResponse {
|
||||
return c.SendCommand("EXPUNGE")
|
||||
}
|
||||
|
||||
// IDLE
|
||||
// Extennsions
|
||||
// Extennsions: IDLE
|
||||
|
||||
func (c *IMAPClient) StartIDLE() *IMAPResponse {
|
||||
c.idling = true
|
||||
@ -242,3 +255,9 @@ func (c *IMAPClient) StopIDLE() {
|
||||
c.idling = false
|
||||
fmt.Fprintf(c.conn, "%s\r\n", "DONE")
|
||||
}
|
||||
|
||||
// Extennsions: ID
|
||||
|
||||
func (c *IMAPClient) ID(request string) *IMAPResponse {
|
||||
return c.SendCommand(fmt.Sprintf("ID (%v)", request))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user