mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-20 17:16:46 +00:00
We build too many walls and not enough bridges
This commit is contained in:
57
test/context/accounts.go
Normal file
57
test/context/accounts.go
Normal file
@ -0,0 +1,57 @@
|
||||
// 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 (
|
||||
"os"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/test/accounts"
|
||||
)
|
||||
|
||||
func newTestAccounts() *accounts.TestAccounts {
|
||||
envFile := os.Getenv("TEST_ACCOUNTS")
|
||||
data, err := accounts.Load(envFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (ctx *TestContext) GetTestAccount(bddUserID string) *accounts.TestAccount {
|
||||
return ctx.testAccounts.GetTestAccount(bddUserID)
|
||||
}
|
||||
|
||||
func (ctx *TestContext) GetTestAccountWithAddress(bddUserID, addressID string) *accounts.TestAccount {
|
||||
return ctx.testAccounts.GetTestAccountWithAddress(bddUserID, addressID)
|
||||
}
|
||||
|
||||
func (ctx *TestContext) EnsureAddressID(bddUserID, addressOrAddressTestID string) string {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return addressOrAddressTestID
|
||||
}
|
||||
return account.EnsureAddressID(addressOrAddressTestID)
|
||||
}
|
||||
|
||||
func (ctx *TestContext) EnsureAddress(bddUserID, addressOrAddressTestID string) string {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
return addressOrAddressTestID
|
||||
}
|
||||
return account.EnsureAddress(addressOrAddressTestID)
|
||||
}
|
||||
42
test/context/bddt.go
Normal file
42
test/context/bddt.go
Normal file
@ -0,0 +1,42 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type bddT struct {
|
||||
err *multierror.Error
|
||||
}
|
||||
|
||||
func (t *bddT) Errorf(msg string, args ...interface{}) {
|
||||
err := fmt.Errorf(msg, args...)
|
||||
t.err = multierror.Append(t.err, err)
|
||||
}
|
||||
|
||||
func (t *bddT) FailNow() {
|
||||
t.err = multierror.Append(t.err, errors.New("failed by calling FailNow"))
|
||||
}
|
||||
|
||||
func (t *bddT) getErrors() error {
|
||||
return t.err.ErrorOrNil()
|
||||
}
|
||||
84
test/context/bridge.go
Normal file
84
test/context/bridge.go
Normal file
@ -0,0 +1,84 @@
|
||||
// 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 (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
)
|
||||
|
||||
// GetBridge returns bridge instance.
|
||||
func (ctx *TestContext) GetBridge() *bridge.Bridge {
|
||||
return ctx.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.
|
||||
func (ctx *TestContext) withBridgeInstance() {
|
||||
pmapiFactory := func(userID string) bridge.PMAPIProvider {
|
||||
return ctx.pmapiController.GetClient(userID)
|
||||
}
|
||||
ctx.bridge = newBridgeInstance(ctx.t, ctx.cfg, ctx.credStore, ctx.listener, pmapiFactory)
|
||||
ctx.addCleanupChecked(ctx.bridge.ClearData, "Cleaning bridge data")
|
||||
}
|
||||
|
||||
// RestartBridge closes store for each user and recreates a bridge instance the same way as `withBridgeInstance`.
|
||||
// NOTE: This is a very problematic method. It doesn't stop the goroutines doing the event loop and the sync.
|
||||
// These goroutines can continue to run and can cause problems or unexpected behaviour (especially
|
||||
// regarding authorization, because if an auth fails, it will log out the user).
|
||||
// To truly emulate bridge restart, we need a way to immediately stop those goroutines.
|
||||
// I have added a channel that waits up to one second for the event loop to stop, but that isn't great.
|
||||
func (ctx *TestContext) RestartBridge() error {
|
||||
for _, user := range ctx.bridge.GetUsers() {
|
||||
_ = user.GetStore().Close()
|
||||
}
|
||||
|
||||
ctx.withBridgeInstance()
|
||||
return nil
|
||||
}
|
||||
|
||||
// newBridgeInstance creates a new bridge instance configured to use the given config/credstore.
|
||||
func newBridgeInstance(
|
||||
t *bddT,
|
||||
cfg *fakeConfig,
|
||||
credStore bridge.CredentialsStorer,
|
||||
eventListener listener.Listener,
|
||||
pmapiFactory bridge.PMAPIProviderFactory,
|
||||
) *bridge.Bridge {
|
||||
version := os.Getenv("VERSION")
|
||||
bridge.UpdateCurrentUserAgent(version, runtime.GOOS, "", "")
|
||||
|
||||
panicHandler := &panicHandler{t: t}
|
||||
pref := preferences.New(cfg)
|
||||
|
||||
return bridge.New(cfg, pref, panicHandler, eventListener, version, pmapiFactory, 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
|
||||
}
|
||||
49
test/context/bridge_panic_handler.go
Normal file
49
test/context/bridge_panic_handler.go
Normal file
@ -0,0 +1,49 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"runtime/pprof"
|
||||
)
|
||||
|
||||
type panicHandler struct {
|
||||
t *bddT
|
||||
}
|
||||
|
||||
func newPanicHandler(t *bddT) *panicHandler {
|
||||
return &panicHandler{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// HandlePanic makes the panicHandler implement the panicHandler interface for bridge.
|
||||
func (ph *panicHandler) HandlePanic() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
ph.t.Errorf("panic: %s", r)
|
||||
|
||||
r := bytes.NewBufferString("")
|
||||
_ = pprof.Lookup("goroutine").WriteTo(r, 2)
|
||||
b, err := ioutil.ReadAll(r)
|
||||
ph.t.Errorf("pprof details: %s %s", err, b)
|
||||
|
||||
ph.t.FailNow()
|
||||
}
|
||||
}
|
||||
148
test/context/bridge_user.go
Normal file
148
test/context/bridge_user.go
Normal file
@ -0,0 +1,148 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/srp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to login")
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
if _, err := client.Auth2FA("2fa code", auth); err != nil {
|
||||
return errors.Wrap(err, "failed to login with 2FA")
|
||||
}
|
||||
}
|
||||
|
||||
user, err := ctx.bridge.FinishLogin(client, auth, mailboxPassword)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to finish login")
|
||||
}
|
||||
|
||||
ctx.addCleanupChecked(user.Logout, "Logging out user")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUser retrieves the bridge user matching the given query string.
|
||||
func (ctx *TestContext) GetUser(username string) (*bridge.User, error) {
|
||||
return ctx.bridge.GetUser(username)
|
||||
}
|
||||
|
||||
// GetStore retrieves the store for given username.
|
||||
func (ctx *TestContext) GetStore(username string) (*store.Store, error) {
|
||||
user, err := ctx.GetUser(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user.GetStore(), nil
|
||||
}
|
||||
|
||||
// GetStoreAddress retrieves the store address for given username and addressID.
|
||||
func (ctx *TestContext) GetStoreAddress(username, addressID string) (*store.Address, error) {
|
||||
store, err := ctx.GetStore(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return store.GetAddress(addressID)
|
||||
}
|
||||
|
||||
// GetStoreMailbox retrieves the store mailbox for given username, address ID and mailbox name.
|
||||
func (ctx *TestContext) GetStoreMailbox(username, addressID, mailboxName string) (*store.Mailbox, error) {
|
||||
address, err := ctx.GetStoreAddress(username, addressID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return address.GetMailbox(mailboxName)
|
||||
}
|
||||
|
||||
// GetDatabaseFilePath returns the file path of the user's store file.
|
||||
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)
|
||||
}
|
||||
|
||||
// WaitForSync waits for sync to be done.
|
||||
func (ctx *TestContext) WaitForSync(username string) error {
|
||||
store, err := ctx.GetStore(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// First wait for ongoing sync to be done before starting and waiting for new one.
|
||||
ctx.eventuallySyncIsFinished(store)
|
||||
store.TestSync()
|
||||
ctx.eventuallySyncIsFinished(store)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *TestContext) eventuallySyncIsFinished(store *store.Store) {
|
||||
assert.Eventually(ctx.t, func() bool { return !store.TestIsSyncRunning() }, 30*time.Second, 10*time.Millisecond)
|
||||
}
|
||||
|
||||
// EventuallySyncIsFinishedForUsername will wait until sync is finished or
|
||||
// deadline is reached see eventuallySyncIsFinished for timing
|
||||
func (ctx *TestContext) EventuallySyncIsFinishedForUsername(username string) {
|
||||
store, err := ctx.GetStore(username)
|
||||
assert.Nil(ctx.t, err)
|
||||
ctx.eventuallySyncIsFinished(store)
|
||||
}
|
||||
|
||||
// LogoutUser logs out the given user.
|
||||
func (ctx *TestContext) LogoutUser(query string) (err error) {
|
||||
user, err := ctx.bridge.GetUser(query)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get user")
|
||||
}
|
||||
|
||||
if err = user.Logout(); err != nil {
|
||||
return errors.Wrap(err, "failed to logout user")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteUser deletes the given user.
|
||||
func (ctx *TestContext) DeleteUser(query string, deleteStore bool) (err error) {
|
||||
user, err := ctx.bridge.GetUser(query)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get user")
|
||||
}
|
||||
|
||||
if err = ctx.bridge.DeleteUser(user.ID(), deleteStore); err != nil {
|
||||
err = errors.Wrap(err, "failed to delete user")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
87
test/context/cleaner.go
Normal file
87
test/context/cleaner.go
Normal file
@ -0,0 +1,87 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Cleaner is a test step that cleans up some stuff post-test.
|
||||
type Cleaner struct {
|
||||
// file is the filename of the caller.
|
||||
file string
|
||||
// lineNumber is the line number of the caller.
|
||||
lineNumber int
|
||||
// label is a descriptive label of the step being performed.
|
||||
label string
|
||||
// ctx is the TestContext on which the step operates.
|
||||
ctx *TestContext
|
||||
// cleanup is callback doing clean up.
|
||||
cleanup func()
|
||||
}
|
||||
|
||||
// Execute runs the cleaner operation.
|
||||
func (c *Cleaner) Execute() {
|
||||
c.ctx.logger.WithField("from", c.From()).Info(c.label)
|
||||
c.cleanup()
|
||||
}
|
||||
|
||||
// From returns the filepath and line number of the place where this cleaner was created.
|
||||
func (c *Cleaner) From() string {
|
||||
return fmt.Sprintf("%v:%v", c.file, c.lineNumber)
|
||||
}
|
||||
|
||||
// addCleanup adds an operation to be performed at the end of the test.
|
||||
func (ctx *TestContext) addCleanup(c func(), label string) {
|
||||
cleaner := &Cleaner{
|
||||
cleanup: c,
|
||||
label: label,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
cleaner.file, cleaner.lineNumber = filepath.Base(file), line
|
||||
}
|
||||
|
||||
ctx.cleanupSteps = append(ctx.cleanupSteps, cleaner)
|
||||
}
|
||||
|
||||
// addCleanupChecked adds an operation that may return an error to be performed at the end of the test.
|
||||
// If the operation fails, the test is failed.
|
||||
func (ctx *TestContext) addCleanupChecked(f func() error, label string) {
|
||||
checkedFunction := func() {
|
||||
err := f()
|
||||
require.NoError(ctx.t, err)
|
||||
}
|
||||
|
||||
cleaner := &Cleaner{
|
||||
cleanup: checkedFunction,
|
||||
label: label,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
cleaner.file, cleaner.lineNumber = filepath.Base(file), line
|
||||
}
|
||||
|
||||
ctx.cleanupSteps = append(ctx.cleanupSteps, cleaner)
|
||||
}
|
||||
91
test/context/config.go
Normal file
91
test/context/config.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"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
type fakeConfig struct {
|
||||
dir string
|
||||
tm *pmapi.TokenManager
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
return &fakeConfig{
|
||||
dir: dir,
|
||||
tm: pmapi.NewTokenManager(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *fakeConfig) ClearData() error {
|
||||
return os.RemoveAll(c.dir)
|
||||
}
|
||||
func (c *fakeConfig) GetAPIConfig() *pmapi.ClientConfig {
|
||||
return &pmapi.ClientConfig{
|
||||
AppVersion: "Bridge_" + os.Getenv("VERSION"),
|
||||
ClientID: "bridge",
|
||||
// TokenManager should not be required, but PMAPI still doesn't handle not-set cases everywhere.
|
||||
TokenManager: c.tm,
|
||||
}
|
||||
}
|
||||
func (c *fakeConfig) GetDBDir() string {
|
||||
return c.dir
|
||||
}
|
||||
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) 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)
|
||||
}
|
||||
118
test/context/context.go
Normal file
118
test/context/context.go
Normal file
@ -0,0 +1,118 @@
|
||||
// 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 allows integration tests to be written in a fluent, english-like way.
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/test/accounts"
|
||||
"github.com/ProtonMail/proton-bridge/test/mocks"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type server interface {
|
||||
ListenAndServe()
|
||||
Close()
|
||||
}
|
||||
|
||||
// TestContext manages a bridge test (mocked API, bridge instance, IMAP/SMTP servers, setup steps).
|
||||
type TestContext struct {
|
||||
// Base setup for the whole bridge (core & imap & smtp).
|
||||
t *bddT
|
||||
cfg *fakeConfig
|
||||
listener listener.Listener
|
||||
pmapiController PMAPIController // pmapiController is used to create pmapi clients (either real or fake) and control server state.
|
||||
testAccounts *accounts.TestAccounts
|
||||
|
||||
// Bridge core related variables.
|
||||
bridge *bridge.Bridge
|
||||
bridgeLastError error
|
||||
credStore bridge.CredentialsStorer
|
||||
|
||||
// IMAP related variables.
|
||||
imapAddr string
|
||||
imapServer server
|
||||
imapClients map[string]*mocks.IMAPClient
|
||||
imapLastResponses map[string]*mocks.IMAPResponse
|
||||
|
||||
// SMTP related variables.
|
||||
smtpAddr string
|
||||
smtpServer server
|
||||
smtpClients map[string]*mocks.SMTPClient
|
||||
smtpLastResponses map[string]*mocks.SMTPResponse
|
||||
|
||||
// These are the cleanup steps executed when Cleanup() is called.
|
||||
cleanupSteps []*Cleaner
|
||||
|
||||
// logger allows logging of test labels to be handled differently (silenced/diverted/whatever).
|
||||
logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
// New returns a new test TestContext.
|
||||
func New() *TestContext {
|
||||
setLogrusVerbosityFromEnv()
|
||||
|
||||
cfg := newFakeConfig()
|
||||
|
||||
ctx := &TestContext{
|
||||
t: &bddT{},
|
||||
cfg: cfg,
|
||||
listener: listener.New(),
|
||||
pmapiController: newPMAPIController(),
|
||||
testAccounts: newTestAccounts(),
|
||||
credStore: newFakeCredStore(),
|
||||
imapClients: make(map[string]*mocks.IMAPClient),
|
||||
imapLastResponses: make(map[string]*mocks.IMAPResponse),
|
||||
smtpClients: make(map[string]*mocks.SMTPClient),
|
||||
smtpLastResponses: make(map[string]*mocks.SMTPResponse),
|
||||
logger: logrus.StandardLogger(),
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Cleanup runs through all cleanup steps.
|
||||
// This can be a deferred call so that it is run even if the test steps failed the test.
|
||||
func (ctx *TestContext) Cleanup() *TestContext {
|
||||
for _, cleanup := range ctx.cleanupSteps {
|
||||
cleanup.Execute()
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// GetPMAPIController returns API controller, either fake or live.
|
||||
func (ctx *TestContext) GetPMAPIController() PMAPIController {
|
||||
return ctx.pmapiController
|
||||
}
|
||||
|
||||
// GetTestingT returns testing.T compatible struct.
|
||||
func (ctx *TestContext) GetTestingT() *bddT { //nolint[golint]
|
||||
return ctx.t
|
||||
}
|
||||
|
||||
// GetTestingError returns error if test failed by using custom TestingT.
|
||||
func (ctx *TestContext) GetTestingError() error {
|
||||
return ctx.t.getErrors()
|
||||
}
|
||||
102
test/context/credentials.go
Normal file
102
test/context/credentials.go
Normal file
@ -0,0 +1,102 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
||||
)
|
||||
|
||||
// bridgePassword is password to be used for IMAP or SMTP under tests.
|
||||
const bridgePassword = "bridgepassword"
|
||||
|
||||
type fakeCredStore struct {
|
||||
credentials map[string]*credentials.Credentials
|
||||
}
|
||||
|
||||
// newFakeCredStore returns a fake credentials store (optionally configured with the given credentials).
|
||||
func newFakeCredStore(initCreds ...*credentials.Credentials) (c *fakeCredStore) {
|
||||
c = &fakeCredStore{credentials: map[string]*credentials.Credentials{}}
|
||||
for _, creds := range initCreds {
|
||||
if creds == nil {
|
||||
continue
|
||||
}
|
||||
c.credentials[creds.UserID] = &credentials.Credentials{}
|
||||
*c.credentials[creds.UserID] = *creds
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) List() (userIDs []string, err error) {
|
||||
keys := []string{}
|
||||
for key := range c.credentials {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) Add(userID, userName, apiToken, mailboxPassword string, emails []string) (*credentials.Credentials, error) {
|
||||
bridgePassword := bridgePassword
|
||||
if c, ok := c.credentials[userID]; ok {
|
||||
bridgePassword = c.BridgePassword
|
||||
}
|
||||
c.credentials[userID] = &credentials.Credentials{
|
||||
UserID: userID,
|
||||
Name: userName,
|
||||
Emails: strings.Join(emails, ";"),
|
||||
APIToken: apiToken,
|
||||
MailboxPassword: mailboxPassword,
|
||||
BridgePassword: bridgePassword,
|
||||
IsCombinedAddressMode: true, // otherwise by default starts in split mode
|
||||
}
|
||||
|
||||
return c.Get(userID)
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) Get(userID string) (*credentials.Credentials, error) {
|
||||
return c.credentials[userID], nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) SwitchAddressMode(userID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) UpdateEmails(userID string, emails []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) UpdateToken(userID, apiToken string) error {
|
||||
creds, err := c.Get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
creds.APIToken = apiToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) Logout(userID string) error {
|
||||
c.credentials[userID].APIToken = ""
|
||||
c.credentials[userID].MailboxPassword = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) Delete(userID string) error {
|
||||
delete(c.credentials, userID)
|
||||
return nil
|
||||
}
|
||||
40
test/context/environments.go
Normal file
40
test/context/environments.go
Normal file
@ -0,0 +1,40 @@
|
||||
// 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 (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
EnvName = "TEST_ENV"
|
||||
EnvFake = "fake"
|
||||
EnvLive = "live"
|
||||
)
|
||||
|
||||
func (ctx *TestContext) EventLoopTimeout() time.Duration {
|
||||
switch os.Getenv(EnvName) {
|
||||
case EnvFake:
|
||||
return 5 * time.Second
|
||||
case EnvLive:
|
||||
return 60 * time.Second
|
||||
default:
|
||||
panic("unknown env")
|
||||
}
|
||||
}
|
||||
80
test/context/imap.go
Normal file
80
test/context/imap.go
Normal file
@ -0,0 +1,80 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"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"
|
||||
)
|
||||
|
||||
// GetIMAPClient gets the imap client by name; if it doesn't exist yet it creates it.
|
||||
func (ctx *TestContext) GetIMAPClient(handle string) *mocks.IMAPClient {
|
||||
if client, ok := ctx.imapClients[handle]; ok {
|
||||
return client
|
||||
}
|
||||
return ctx.newIMAPClient(handle)
|
||||
}
|
||||
|
||||
func (ctx *TestContext) newIMAPClient(handle string) *mocks.IMAPClient {
|
||||
ctx.withIMAPServer()
|
||||
|
||||
client := mocks.NewIMAPClient(ctx.t, handle, ctx.imapAddr)
|
||||
ctx.imapClients[handle] = client
|
||||
ctx.addCleanup(client.Close, "Closing IMAP client")
|
||||
return client
|
||||
}
|
||||
|
||||
// withIMAPServer starts an imap server and connects it to the bridge instance.
|
||||
// Every TestContext has this by default and thus this doesn't need to be exported.
|
||||
func (ctx *TestContext) withIMAPServer() {
|
||||
if ctx.imapServer != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ph := newPanicHandler(ctx.t)
|
||||
pref := preferences.New(ctx.cfg)
|
||||
port := pref.GetInt(preferences.IMAPPortKey)
|
||||
tls, _ := config.GetTLSConfig(ctx.cfg)
|
||||
|
||||
backend := imap.NewIMAPBackend(ph, ctx.listener, ctx.cfg, ctx.bridge)
|
||||
server := imap.NewIMAPServer(true, true, port, tls, backend, ctx.listener)
|
||||
|
||||
go server.ListenAndServe()
|
||||
require.NoError(ctx.t, waitForPort(port, 5*time.Second))
|
||||
|
||||
ctx.imapServer = server
|
||||
ctx.imapAddr = fmt.Sprintf("%v:%v", bridge.Host, port)
|
||||
ctx.addCleanup(ctx.imapServer.Close, "Closing IMAP server")
|
||||
}
|
||||
|
||||
// SetIMAPLastResponse sets the last IMAP response that was received.
|
||||
func (ctx *TestContext) SetIMAPLastResponse(handle string, resp *mocks.IMAPResponse) {
|
||||
ctx.imapLastResponses[handle] = resp
|
||||
}
|
||||
|
||||
// GetIMAPLastResponse returns the last IMAP response that was received.
|
||||
func (ctx *TestContext) GetIMAPLastResponse(handle string) *mocks.IMAPResponse {
|
||||
return ctx.imapLastResponses[handle]
|
||||
}
|
||||
84
test/context/pmapi_controller.go
Normal file
84
test/context/pmapi_controller.go
Normal file
@ -0,0 +1,84 @@
|
||||
// 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 (
|
||||
"os"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/ProtonMail/proton-bridge/test/fakeapi"
|
||||
"github.com/ProtonMail/proton-bridge/test/liveapi"
|
||||
)
|
||||
|
||||
type PMAPIController interface {
|
||||
GetClient(userID string) bridge.PMAPIProvider
|
||||
TurnInternetConnectionOff()
|
||||
TurnInternetConnectionOn()
|
||||
AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error
|
||||
AddUserLabel(username string, label *pmapi.Label) error
|
||||
GetLabelIDs(username string, labelNames []string) ([]string, error)
|
||||
AddUserMessage(username string, message *pmapi.Message) error
|
||||
GetMessageID(username, messageIndex string) string
|
||||
PrintCalls()
|
||||
WasCalled(method, path string, expectedRequest []byte) bool
|
||||
GetCalls(method, path string) [][]byte
|
||||
}
|
||||
|
||||
func newPMAPIController() PMAPIController {
|
||||
switch os.Getenv(EnvName) {
|
||||
case EnvFake:
|
||||
return newFakePMAPIController()
|
||||
case EnvLive:
|
||||
return newLivePMAPIController()
|
||||
default:
|
||||
panic("unknown env")
|
||||
}
|
||||
}
|
||||
|
||||
func newFakePMAPIController() PMAPIController {
|
||||
return newFakePMAPIControllerWrap(fakeapi.NewController())
|
||||
}
|
||||
|
||||
type fakePMAPIControllerWrap struct {
|
||||
*fakeapi.Controller
|
||||
}
|
||||
|
||||
func newFakePMAPIControllerWrap(controller *fakeapi.Controller) PMAPIController {
|
||||
return &fakePMAPIControllerWrap{Controller: controller}
|
||||
}
|
||||
|
||||
func (s *fakePMAPIControllerWrap) GetClient(userID string) bridge.PMAPIProvider {
|
||||
return s.Controller.GetClient(userID)
|
||||
}
|
||||
|
||||
func newLivePMAPIController() PMAPIController {
|
||||
return newLiveAPIControllerWrap(liveapi.NewController())
|
||||
}
|
||||
|
||||
type liveAPIControllerWrap struct {
|
||||
*liveapi.Controller
|
||||
}
|
||||
|
||||
func newLiveAPIControllerWrap(controller *liveapi.Controller) PMAPIController {
|
||||
return &liveAPIControllerWrap{Controller: controller}
|
||||
}
|
||||
|
||||
func (s *liveAPIControllerWrap) GetClient(userID string) bridge.PMAPIProvider {
|
||||
return s.Controller.GetClient(userID)
|
||||
}
|
||||
81
test/context/smtp.go
Normal file
81
test/context/smtp.go
Normal file
@ -0,0 +1,81 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"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"
|
||||
)
|
||||
|
||||
// GetSMTPClient gets the smtp client by name; if it doesn't exist yet it creates it.
|
||||
func (ctx *TestContext) GetSMTPClient(handle string) *mocks.SMTPClient {
|
||||
if client, ok := ctx.smtpClients[handle]; ok {
|
||||
return client
|
||||
}
|
||||
return ctx.newSMTPClient(handle)
|
||||
}
|
||||
|
||||
func (ctx *TestContext) newSMTPClient(handle string) *mocks.SMTPClient {
|
||||
ctx.withSMTPServer()
|
||||
|
||||
client := mocks.NewSMTPClient(ctx.t, handle, ctx.smtpAddr)
|
||||
ctx.smtpClients[handle] = client
|
||||
ctx.addCleanup(client.Close, "Closing SMTP client")
|
||||
return client
|
||||
}
|
||||
|
||||
// withSMTPServer starts an smtp server and connects it to the bridge instance.
|
||||
// Every TestContext has this by default and thus this doesn't need to be exported.
|
||||
func (ctx *TestContext) withSMTPServer() {
|
||||
if ctx.smtpServer != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ph := newPanicHandler(ctx.t)
|
||||
pref := preferences.New(ctx.cfg)
|
||||
tls, _ := config.GetTLSConfig(ctx.cfg)
|
||||
port := pref.GetInt(preferences.SMTPPortKey)
|
||||
useSSL := pref.GetBool(preferences.SMTPSSLKey)
|
||||
|
||||
backend := smtp.NewSMTPBackend(ph, ctx.listener, pref, ctx.bridge)
|
||||
server := smtp.NewSMTPServer(true, port, useSSL, tls, backend, ctx.listener)
|
||||
|
||||
go server.ListenAndServe()
|
||||
require.NoError(ctx.t, waitForPort(port, 5*time.Second))
|
||||
|
||||
ctx.smtpServer = server
|
||||
ctx.smtpAddr = fmt.Sprintf("%v:%v", bridge.Host, port)
|
||||
ctx.addCleanup(ctx.smtpServer.Close, "Closing SMTP server")
|
||||
}
|
||||
|
||||
// SetSMTPLastResponse sets the last SMTP response that was received.
|
||||
func (ctx *TestContext) SetSMTPLastResponse(handle string, resp *mocks.SMTPResponse) {
|
||||
ctx.smtpLastResponses[handle] = resp
|
||||
}
|
||||
|
||||
// GetSMTPLastResponse returns the last IMAP response that was received.
|
||||
func (ctx *TestContext) GetSMTPLastResponse(handle string) *mocks.SMTPResponse {
|
||||
return ctx.smtpLastResponses[handle]
|
||||
}
|
||||
87
test/context/utils.go
Normal file
87
test/context/utils.go
Normal file
@ -0,0 +1,87 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func waitForPort(port int, timeout time.Duration) error {
|
||||
return waitUntilTrue(timeout, func() bool {
|
||||
conn, err := net.DialTimeout("tcp", "127.0.0.1:"+strconv.Itoa(port), timeout)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if conn != nil {
|
||||
if err := conn.Close(); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// waitUntilTrue can use Eventually from
|
||||
// https://godoc.org/github.com/stretchr/testify/require#Assertions.Eventually
|
||||
func waitUntilTrue(timeout time.Duration, callback func() bool) error {
|
||||
endTime := time.Now().Add(timeout)
|
||||
for {
|
||||
if time.Now().After(endTime) {
|
||||
return fmt.Errorf("Timeout")
|
||||
}
|
||||
if callback() {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func setLogrusVerbosityFromEnv() {
|
||||
verbosityName := os.Getenv("VERBOSITY")
|
||||
switch strings.ToLower(verbosityName) {
|
||||
case "panic":
|
||||
logrus.SetLevel(logrus.PanicLevel)
|
||||
case "fatal":
|
||||
logrus.SetLevel(logrus.FatalLevel)
|
||||
case "error":
|
||||
logrus.SetLevel(logrus.ErrorLevel)
|
||||
case "warning", "warn":
|
||||
logrus.SetLevel(logrus.WarnLevel)
|
||||
case "info":
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
case "debug":
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
case "trace":
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
default:
|
||||
logrus.SetLevel(logrus.FatalLevel)
|
||||
}
|
||||
|
||||
logrus.SetFormatter(&logrus.TextFormatter{
|
||||
ForceColors: true,
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: time.StampMilli,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user