Merge release/golden-gate into devel

This commit is contained in:
Jakub
2021-01-27 11:06:10 +01:00
254 changed files with 9588 additions and 5963 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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
View 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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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))

View File

@ -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
View 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
View 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
}

View File

@ -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()

View File

@ -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
View 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
}

View File

@ -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)
}

View File

@ -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

View 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 |

View 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])"

View File

@ -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
}

View File

@ -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"
)

View File

@ -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))
}