2023-01-02 10:02:26 +00:00
|
|
|
// Copyright (c) 2023 Proton AG
|
2022-10-13 08:58:11 +00:00
|
|
|
//
|
|
|
|
// This file is part of Proton Mail Bridge.
|
|
|
|
//
|
|
|
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
package bridge_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-10-09 21:05:52 +00:00
|
|
|
"crypto/tls"
|
2022-11-10 22:28:08 +00:00
|
|
|
"fmt"
|
2022-10-02 11:28:41 +00:00
|
|
|
"net/http"
|
2022-09-27 11:22:07 +00:00
|
|
|
"os"
|
2023-01-12 13:23:09 +00:00
|
|
|
"path/filepath"
|
2022-10-25 08:26:34 +00:00
|
|
|
"sync"
|
2022-08-26 15:00:21 +00:00
|
|
|
"testing"
|
2022-09-28 09:29:33 +00:00
|
|
|
"time"
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
"github.com/Masterminds/semver/v3"
|
2022-11-23 14:17:56 +00:00
|
|
|
"github.com/ProtonMail/go-proton-api"
|
|
|
|
"github.com/ProtonMail/go-proton-api/server"
|
|
|
|
"github.com/ProtonMail/go-proton-api/server/backend"
|
2022-08-26 15:00:21 +00:00
|
|
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
2022-11-23 14:25:41 +00:00
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
2023-01-12 13:23:09 +00:00
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
2022-11-23 14:25:41 +00:00
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v3/tests"
|
2022-08-26 15:00:21 +00:00
|
|
|
"github.com/bradenaw/juniper/xslices"
|
2023-01-12 13:23:09 +00:00
|
|
|
"github.com/emersion/go-imap/client"
|
2022-08-26 15:00:21 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
2022-10-01 21:14:42 +00:00
|
|
|
var (
|
2022-08-26 15:00:21 +00:00
|
|
|
username = "username"
|
2022-10-01 21:14:42 +00:00
|
|
|
password = []byte("password")
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
v2_3_0 = semver.MustParse("2.3.0")
|
|
|
|
v2_4_0 = semver.MustParse("2.4.0")
|
|
|
|
)
|
|
|
|
|
2022-09-28 09:29:33 +00:00
|
|
|
func init() {
|
2022-10-01 21:14:42 +00:00
|
|
|
user.EventPeriod = 100 * time.Millisecond
|
|
|
|
user.EventJitter = 0
|
2022-12-12 12:49:34 +00:00
|
|
|
backend.GenerateKey = backend.FastGenerateKey
|
2022-09-28 09:29:33 +00:00
|
|
|
certs.GenerateCert = tests.FastGenerateCert
|
|
|
|
}
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
func TestBridge_ConnStatus(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
// Get a stream of connection status events.
|
2022-09-27 10:02:28 +00:00
|
|
|
eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{})
|
2022-08-26 15:00:21 +00:00
|
|
|
defer done()
|
|
|
|
|
|
|
|
// Simulate network disconnect.
|
2022-10-09 21:05:52 +00:00
|
|
|
netCtl.Disable()
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
// Trigger some operation that will fail due to the network disconnect.
|
2022-10-12 10:44:06 +00:00
|
|
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
2022-08-26 15:00:21 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
|
|
|
|
// Wait for the event.
|
2022-09-27 10:02:28 +00:00
|
|
|
require.Equal(t, events.ConnStatusDown{}, <-eventCh)
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
// Simulate network reconnect.
|
2022-10-09 21:05:52 +00:00
|
|
|
netCtl.Enable()
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
// Trigger some operation that will succeed due to the network reconnect.
|
2022-10-12 10:44:06 +00:00
|
|
|
userID, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
2022-08-26 15:00:21 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, userID)
|
|
|
|
|
|
|
|
// Wait for the event.
|
2022-09-27 10:02:28 +00:00
|
|
|
require.Equal(t, events.ConnStatusUp{}, <-eventCh)
|
2022-08-26 15:00:21 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_TLSIssue(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
// Get a stream of TLS issue events.
|
|
|
|
tlsEventCh, done := bridge.GetEvents(events.TLSIssue{})
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
// Simulate a TLS issue.
|
|
|
|
go func() {
|
|
|
|
mocks.TLSIssueCh <- struct{}{}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait for the event.
|
|
|
|
require.IsType(t, events.TLSIssue{}, <-tlsEventCh)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_Focus(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
// Get a stream of TLS issue events.
|
|
|
|
raiseCh, done := bridge.GetEvents(events.Raise{})
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
// Simulate a focus event.
|
|
|
|
focus.TryRaise()
|
|
|
|
|
|
|
|
// Wait for the event.
|
|
|
|
require.IsType(t, events.Raise{}, <-raiseCh)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_UserAgent(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-25 08:26:34 +00:00
|
|
|
var (
|
|
|
|
calls []server.Call
|
|
|
|
lock sync.Mutex
|
|
|
|
)
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
s.AddCallWatcher(func(call server.Call) {
|
2022-10-25 08:26:34 +00:00
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
calls = append(calls, call)
|
|
|
|
})
|
|
|
|
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
// Set the platform to something other than the default.
|
|
|
|
bridge.SetCurrentPlatform("platform")
|
|
|
|
|
|
|
|
// Assert that the user agent then contains the platform.
|
|
|
|
require.Contains(t, bridge.GetCurrentUserAgent(), "platform")
|
|
|
|
|
|
|
|
// Login the user.
|
2022-10-12 10:44:06 +00:00
|
|
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
2022-08-26 15:00:21 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-10-25 08:26:34 +00:00
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
// Assert that the user agent was sent to the API.
|
2022-10-11 17:40:28 +00:00
|
|
|
require.Contains(t, calls[len(calls)-1].RequestHeader.Get("User-Agent"), bridge.GetCurrentUserAgent())
|
2022-08-26 15:00:21 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_Cookies(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-26 22:52:20 +00:00
|
|
|
var (
|
|
|
|
sessionIDs []string
|
|
|
|
sessionIDsLock sync.RWMutex
|
|
|
|
)
|
2022-08-26 15:00:21 +00:00
|
|
|
|
2022-10-11 17:40:28 +00:00
|
|
|
// Save any session IDs we use.
|
2022-08-26 15:00:21 +00:00
|
|
|
s.AddCallWatcher(func(call server.Call) {
|
2022-10-11 17:40:28 +00:00
|
|
|
cookie, err := (&http.Request{Header: call.RequestHeader}).Cookie("Session-Id")
|
2022-10-11 16:18:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2022-08-26 15:00:21 +00:00
|
|
|
|
2022-10-26 22:52:20 +00:00
|
|
|
sessionIDsLock.Lock()
|
|
|
|
defer sessionIDsLock.Unlock()
|
|
|
|
|
|
|
|
sessionIDs = append(sessionIDs, cookie.Value)
|
2022-10-11 16:18:08 +00:00
|
|
|
})
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
// Start bridge and add a user so that API assigns us a session ID via cookie.
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-10-12 10:44:06 +00:00
|
|
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
2022-08-26 15:00:21 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Start bridge again and check that it uses the same session ID.
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-10-11 16:18:08 +00:00
|
|
|
// ...
|
|
|
|
})
|
2022-08-26 15:00:21 +00:00
|
|
|
|
2022-10-11 16:18:08 +00:00
|
|
|
// We should have used just one session ID.
|
2022-10-26 22:52:20 +00:00
|
|
|
sessionIDsLock.Lock()
|
|
|
|
defer sessionIDsLock.Unlock()
|
|
|
|
|
|
|
|
require.Len(t, xslices.Unique(sessionIDs), 1)
|
2022-08-26 15:00:21 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_CheckUpdate(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
// Disable autoupdate for this test.
|
|
|
|
require.NoError(t, bridge.SetAutoUpdate(false))
|
|
|
|
|
2022-09-28 09:29:33 +00:00
|
|
|
// Get a stream of update not available events.
|
|
|
|
noUpdateCh, done := bridge.GetEvents(events.UpdateNotAvailable{})
|
2022-08-26 15:00:21 +00:00
|
|
|
defer done()
|
|
|
|
|
|
|
|
// We are currently on the latest version.
|
|
|
|
bridge.CheckForUpdates()
|
2022-09-28 09:29:33 +00:00
|
|
|
|
|
|
|
// we should receive an event indicating that no update is available.
|
|
|
|
require.Equal(t, events.UpdateNotAvailable{}, <-noUpdateCh)
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
// Simulate a new version being available.
|
|
|
|
mocks.Updater.SetLatestVersion(v2_4_0, v2_3_0)
|
|
|
|
|
2022-09-28 09:29:33 +00:00
|
|
|
// Get a stream of update available events.
|
|
|
|
updateCh, done := bridge.GetEvents(events.UpdateAvailable{})
|
|
|
|
defer done()
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
// Check for updates.
|
|
|
|
bridge.CheckForUpdates()
|
2022-09-28 09:29:33 +00:00
|
|
|
|
|
|
|
// We should receive an event indicating that an update is available.
|
2022-08-26 15:00:21 +00:00
|
|
|
require.Equal(t, events.UpdateAvailable{
|
|
|
|
Version: updater.VersionInfo{
|
|
|
|
Version: v2_4_0,
|
|
|
|
MinAuto: v2_3_0,
|
|
|
|
RolloutProportion: 1.0,
|
|
|
|
},
|
2022-11-01 20:44:09 +00:00
|
|
|
Silent: false,
|
|
|
|
Compatible: true,
|
2022-08-26 15:00:21 +00:00
|
|
|
}, <-updateCh)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_AutoUpdate(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
// Enable autoupdate for this test.
|
|
|
|
require.NoError(t, bridge.SetAutoUpdate(true))
|
|
|
|
|
|
|
|
// Get a stream of update events.
|
2022-09-28 09:29:33 +00:00
|
|
|
updateCh, done := bridge.GetEvents(events.UpdateInstalled{})
|
2022-08-26 15:00:21 +00:00
|
|
|
defer done()
|
|
|
|
|
|
|
|
// Simulate a new version being available.
|
|
|
|
mocks.Updater.SetLatestVersion(v2_4_0, v2_3_0)
|
|
|
|
|
|
|
|
// Check for updates.
|
|
|
|
bridge.CheckForUpdates()
|
2022-09-28 09:29:33 +00:00
|
|
|
|
2022-11-01 20:44:09 +00:00
|
|
|
// We should receive an event indicating that the update was silently installed.
|
2022-08-26 15:00:21 +00:00
|
|
|
require.Equal(t, events.UpdateInstalled{
|
|
|
|
Version: updater.VersionInfo{
|
|
|
|
Version: v2_4_0,
|
|
|
|
MinAuto: v2_3_0,
|
|
|
|
RolloutProportion: 1.0,
|
|
|
|
},
|
2022-11-01 20:44:09 +00:00
|
|
|
Silent: true,
|
2022-08-26 15:00:21 +00:00
|
|
|
}, <-updateCh)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_ManualUpdate(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
// Disable autoupdate for this test.
|
|
|
|
require.NoError(t, bridge.SetAutoUpdate(false))
|
|
|
|
|
2022-09-28 09:29:33 +00:00
|
|
|
// Get a stream of update available events.
|
|
|
|
updateCh, done := bridge.GetEvents(events.UpdateAvailable{})
|
2022-08-26 15:00:21 +00:00
|
|
|
defer done()
|
|
|
|
|
|
|
|
// Simulate a new version being available, but it's too new for us.
|
|
|
|
mocks.Updater.SetLatestVersion(v2_4_0, v2_4_0)
|
|
|
|
|
|
|
|
// Check for updates.
|
|
|
|
bridge.CheckForUpdates()
|
2022-09-28 09:29:33 +00:00
|
|
|
|
|
|
|
// We should receive an event indicating an update is available, but we can't install it.
|
2022-08-26 15:00:21 +00:00
|
|
|
require.Equal(t, events.UpdateAvailable{
|
|
|
|
Version: updater.VersionInfo{
|
|
|
|
Version: v2_4_0,
|
|
|
|
MinAuto: v2_4_0,
|
|
|
|
RolloutProportion: 1.0,
|
|
|
|
},
|
2022-11-01 20:44:09 +00:00
|
|
|
Silent: false,
|
|
|
|
Compatible: false,
|
2022-08-26 15:00:21 +00:00
|
|
|
}, <-updateCh)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_ForceUpdate(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
// Get a stream of update events.
|
|
|
|
updateCh, done := bridge.GetEvents(events.UpdateForced{})
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
// Set the minimum accepted app version to something newer than the current version.
|
|
|
|
s.SetMinAppVersion(v2_4_0)
|
|
|
|
|
|
|
|
// Try to login the user. It will fail because the bridge is too old.
|
2022-10-12 10:44:06 +00:00
|
|
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
2022-08-26 15:00:21 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
|
|
|
|
// We should get an update required event.
|
|
|
|
require.Equal(t, events.UpdateForced{}, <-updateCh)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_BadVaultKey(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-08-26 15:00:21 +00:00
|
|
|
var userID string
|
|
|
|
|
|
|
|
// Login a user.
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-10-12 10:44:06 +00:00
|
|
|
newUserID, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
2022-08-26 15:00:21 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
userID = newUserID
|
|
|
|
})
|
|
|
|
|
|
|
|
// Start bridge with the correct vault key -- it should load the users correctly.
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs())
|
|
|
|
})
|
|
|
|
|
|
|
|
// Start bridge with a bad vault key, the vault will be wiped and bridge will show no users.
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
require.Empty(t, bridge.GetUserIDs())
|
|
|
|
})
|
|
|
|
|
|
|
|
// Start bridge with a nil vault key, the vault will be wiped and bridge will show no users.
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-08-26 15:00:21 +00:00
|
|
|
require.Empty(t, bridge.GetUserIDs())
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-12 13:23:09 +00:00
|
|
|
func TestBridge_MissingGluonStore(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-09-27 11:22:07 +00:00
|
|
|
var gluonDir string
|
|
|
|
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-10-12 10:44:06 +00:00
|
|
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
2022-09-27 11:22:07 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Move the gluon dir.
|
2022-10-11 16:04:39 +00:00
|
|
|
require.NoError(t, bridge.SetGluonDir(ctx, t.TempDir()))
|
2022-09-27 11:22:07 +00:00
|
|
|
|
|
|
|
// Get the gluon dir.
|
2023-01-12 13:23:09 +00:00
|
|
|
gluonDir = bridge.GetGluonCacheDir()
|
2022-09-27 11:22:07 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// The user removes the gluon dir while bridge is not running.
|
|
|
|
require.NoError(t, os.RemoveAll(gluonDir))
|
|
|
|
|
2023-01-12 13:23:09 +00:00
|
|
|
// Bridge starts but can't find the gluon store dir; there should be no error.
|
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
|
|
|
// ...
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBridge_MissingGluonDatabase(t *testing.T) {
|
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
|
|
|
var gluonDir string
|
|
|
|
|
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
|
|
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Get the gluon dir.
|
2023-01-24 13:35:45 +00:00
|
|
|
gluonDir, err = bridge.GetGluonDataDir()
|
2023-01-12 13:23:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// The user removes the gluon dir while bridge is not running.
|
|
|
|
require.NoError(t, os.RemoveAll(gluonDir))
|
|
|
|
|
|
|
|
// Bridge starts but can't find the gluon database dir; there should be no error.
|
2022-10-11 16:04:39 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-09-27 11:22:07 +00:00
|
|
|
// ...
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-10-27 03:12:13 +00:00
|
|
|
func TestBridge_AddressWithoutKeys(t *testing.T) {
|
2022-11-23 14:17:56 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
|
|
|
m := proton.New(
|
|
|
|
proton.WithHostURL(s.GetHostURL()),
|
|
|
|
proton.WithTransport(proton.InsecureTransport()),
|
2022-11-21 17:51:00 +00:00
|
|
|
)
|
|
|
|
defer m.Close()
|
|
|
|
|
2022-10-27 03:12:13 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
|
|
|
// Create a user which will have an address without keys.
|
2022-12-12 12:49:34 +00:00
|
|
|
userID, _, err := s.CreateUser("nokeys", []byte("password"))
|
2022-10-27 03:12:13 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Create an additional address for the user; it will not have keys.
|
|
|
|
aliasAddrID, err := s.CreateAddress(userID, "alias@pm.me", []byte("password"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-11-21 17:51:00 +00:00
|
|
|
// Create an API client so we can remove the address keys.
|
|
|
|
c, _, err := m.NewClientWithLogin(ctx, "nokeys", []byte("password"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer c.Close()
|
|
|
|
|
|
|
|
// Get the alias address.
|
|
|
|
aliasAddr, err := c.GetAddress(ctx, aliasAddrID)
|
2022-10-27 03:12:13 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Remove the address keys.
|
2022-11-21 17:51:00 +00:00
|
|
|
require.NoError(t, s.RemoveAddressKey(userID, aliasAddrID, aliasAddr.Keys[0].ID))
|
2022-10-27 03:12:13 +00:00
|
|
|
|
|
|
|
// Watch for sync finished event.
|
|
|
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
// We should be able to log the user in.
|
|
|
|
require.NoError(t, getErr(bridge.LoginFull(context.Background(), "nokeys", []byte("password"), nil, nil)))
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// The sync should eventually finish for the user without keys.
|
|
|
|
require.Equal(t, userID, (<-syncCh).UserID)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-28 18:58:10 +00:00
|
|
|
func TestBridge_FactoryReset(t *testing.T) {
|
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2022-11-29 08:59:30 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
|
|
|
// The settings should be their default values.
|
|
|
|
require.True(t, bridge.GetAutoUpdate())
|
|
|
|
require.Equal(t, updater.StableChannel, bridge.GetUpdateChannel())
|
|
|
|
|
2022-11-28 18:58:10 +00:00
|
|
|
// Login the user.
|
|
|
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-11-29 08:59:30 +00:00
|
|
|
// Change some settings.
|
|
|
|
require.NoError(t, bridge.SetAutoUpdate(false))
|
|
|
|
require.NoError(t, bridge.SetUpdateChannel(updater.EarlyChannel))
|
|
|
|
|
2022-11-28 18:58:10 +00:00
|
|
|
// The user is now connected.
|
|
|
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
|
|
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
|
|
|
|
2022-11-29 08:59:30 +00:00
|
|
|
// The settings should be changed.
|
|
|
|
require.False(t, bridge.GetAutoUpdate())
|
|
|
|
require.Equal(t, updater.EarlyChannel, bridge.GetUpdateChannel())
|
|
|
|
|
2022-11-28 18:58:10 +00:00
|
|
|
// Perform a factory reset.
|
|
|
|
bridge.FactoryReset(ctx)
|
|
|
|
|
|
|
|
// The user is gone.
|
|
|
|
require.Equal(t, []string{}, bridge.GetUserIDs())
|
|
|
|
require.Equal(t, []string{}, getConnectedUserIDs(t, bridge))
|
|
|
|
})
|
2022-11-29 08:59:30 +00:00
|
|
|
|
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
|
|
|
// The settings should be reset.
|
|
|
|
require.True(t, bridge.GetAutoUpdate())
|
|
|
|
require.Equal(t, updater.StableChannel, bridge.GetUpdateChannel())
|
|
|
|
})
|
2022-11-28 18:58:10 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-12 13:23:09 +00:00
|
|
|
func TestBridge_InitGluonDirectory(t *testing.T) {
|
2023-01-11 09:19:38 +00:00
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2023-01-12 13:23:09 +00:00
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
2023-01-24 13:35:45 +00:00
|
|
|
configDir, err := b.GetGluonDataDir()
|
2023-01-12 13:23:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(b.GetGluonCacheDir()))
|
|
|
|
require.False(t, os.IsNotExist(err))
|
|
|
|
|
2023-01-16 15:27:41 +00:00
|
|
|
_, err = os.ReadDir(bridge.ApplyGluonConfigPathSuffix(configDir))
|
2023-01-12 13:23:09 +00:00
|
|
|
require.False(t, os.IsNotExist(err))
|
2023-01-11 09:19:38 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-29 07:38:21 +00:00
|
|
|
func TestBridge_ChangeCacheDirectory(t *testing.T) {
|
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
2023-01-12 13:23:09 +00:00
|
|
|
userID, addrID, err := s.CreateUser("imap", password)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
withClient(ctx, t, s, "imap", password, func(ctx context.Context, c *proton.Client) {
|
|
|
|
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
|
|
|
})
|
|
|
|
|
|
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
2022-11-29 07:38:21 +00:00
|
|
|
newCacheDir := t.TempDir()
|
2023-01-12 13:23:09 +00:00
|
|
|
currentCacheDir := b.GetGluonCacheDir()
|
2023-01-24 13:35:45 +00:00
|
|
|
configDir, err := b.GetGluonDataDir()
|
2023-01-12 13:23:09 +00:00
|
|
|
require.NoError(t, err)
|
2022-11-29 07:38:21 +00:00
|
|
|
|
|
|
|
// Login the user.
|
2023-01-12 13:23:09 +00:00
|
|
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
|
|
|
defer done()
|
|
|
|
userID, err := b.LoginFull(ctx, "imap", password, nil, nil)
|
2022-11-29 07:38:21 +00:00
|
|
|
require.NoError(t, err)
|
2023-01-12 13:23:09 +00:00
|
|
|
require.Equal(t, userID, (<-syncCh).UserID)
|
2022-11-29 07:38:21 +00:00
|
|
|
|
|
|
|
// The user is now connected.
|
2023-01-12 13:23:09 +00:00
|
|
|
require.Equal(t, []string{userID}, b.GetUserIDs())
|
|
|
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, b))
|
2022-11-29 07:38:21 +00:00
|
|
|
|
|
|
|
// Change directory
|
2023-01-12 13:23:09 +00:00
|
|
|
err = b.SetGluonDir(ctx, newCacheDir)
|
2022-11-29 15:28:09 +00:00
|
|
|
require.NoError(t, err)
|
2022-11-29 07:38:21 +00:00
|
|
|
|
2023-01-12 13:23:09 +00:00
|
|
|
// Old store should no more exists.
|
|
|
|
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(currentCacheDir))
|
2022-11-29 15:28:09 +00:00
|
|
|
require.True(t, os.IsNotExist(err))
|
2023-01-12 13:23:09 +00:00
|
|
|
// Database should not have changed.
|
2023-01-16 15:27:41 +00:00
|
|
|
_, err = os.ReadDir(bridge.ApplyGluonConfigPathSuffix(configDir))
|
2023-01-12 13:23:09 +00:00
|
|
|
require.False(t, os.IsNotExist(err))
|
|
|
|
|
|
|
|
// New path should have Gluon sub-folder.
|
|
|
|
require.Equal(t, filepath.Join(newCacheDir, "gluon"), b.GetGluonCacheDir())
|
|
|
|
// And store should be inside it.
|
|
|
|
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(b.GetGluonCacheDir()))
|
|
|
|
require.False(t, os.IsNotExist(err))
|
|
|
|
|
|
|
|
// We should be able to fetch.
|
|
|
|
info, err := b.GetUserInfo(userID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, info.State == bridge.Connected)
|
2022-11-29 15:28:09 +00:00
|
|
|
|
2023-01-12 13:23:09 +00:00
|
|
|
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
|
|
|
|
defer func() { _ = client.Logout() }()
|
|
|
|
|
|
|
|
status, err := client.Select(`Folders/folder`, false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, uint32(10), status.Messages)
|
2022-11-29 07:38:21 +00:00
|
|
|
})
|
|
|
|
})
|
2022-11-29 15:28:09 +00:00
|
|
|
}
|
2022-11-29 07:38:21 +00:00
|
|
|
|
2022-10-20 10:16:00 +00:00
|
|
|
// withEnv creates the full test environment and runs the tests.
|
2022-11-23 14:17:56 +00:00
|
|
|
func withEnv(t *testing.T, tests func(context.Context, *server.Server, *proton.NetCtl, bridge.Locator, []byte), opts ...server.Option) {
|
2022-10-24 11:25:11 +00:00
|
|
|
server := server.New(opts...)
|
2022-08-26 15:00:21 +00:00
|
|
|
defer server.Close()
|
|
|
|
|
2022-10-09 21:05:52 +00:00
|
|
|
// Add test user.
|
2022-12-12 12:49:34 +00:00
|
|
|
_, _, err := server.CreateUser(username, password)
|
2022-10-09 21:05:52 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Generate a random vault key.
|
|
|
|
vaultKey, err := crypto.RandomToken(32)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Create a context used for the test.
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
// Create a net controller so we can simulate network connectivity issues.
|
2022-11-23 14:17:56 +00:00
|
|
|
netCtl := proton.NewNetCtl()
|
2022-10-09 21:05:52 +00:00
|
|
|
|
|
|
|
// Create a locations object to provide temporary locations for bridge data during the test.
|
|
|
|
locations := locations.New(bridge.NewTestLocationsProvider(t.TempDir()), "config-name")
|
|
|
|
|
|
|
|
// Run the tests.
|
2022-10-24 11:25:11 +00:00
|
|
|
tests(ctx, server, netCtl, locations, vaultKey)
|
2022-08-26 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
2023-01-18 10:02:36 +00:00
|
|
|
// withMocks creates the mock objects used in the tests.
|
|
|
|
func withMocks(t *testing.T, tests func(*bridge.Mocks)) {
|
|
|
|
mocks := bridge.NewMocks(t, v2_3_0, v2_3_0)
|
|
|
|
defer mocks.Close()
|
|
|
|
|
|
|
|
tests(mocks)
|
|
|
|
}
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
// withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done.
|
2023-01-18 10:02:36 +00:00
|
|
|
func withBridgeNoMocks(
|
2022-10-09 21:05:52 +00:00
|
|
|
ctx context.Context,
|
2022-10-11 16:04:39 +00:00
|
|
|
t *testing.T,
|
2023-01-18 10:02:36 +00:00
|
|
|
mocks *bridge.Mocks,
|
2022-10-09 21:05:52 +00:00
|
|
|
apiURL string,
|
2022-11-23 14:17:56 +00:00
|
|
|
netCtl *proton.NetCtl,
|
2022-10-09 21:05:52 +00:00
|
|
|
locator bridge.Locator,
|
|
|
|
vaultKey []byte,
|
2023-01-18 10:02:36 +00:00
|
|
|
tests func(*bridge.Bridge),
|
2022-10-09 21:05:52 +00:00
|
|
|
) {
|
2023-01-04 12:08:06 +00:00
|
|
|
// Bridge will disable the proxy by default at startup.
|
|
|
|
mocks.ProxyCtl.EXPECT().DisallowProxy()
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
// Get the path to the vault.
|
|
|
|
vaultDir, err := locator.ProvideSettingsPath()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Create the vault.
|
|
|
|
vault, _, err := vault.New(vaultDir, t.TempDir(), vaultKey)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-10-11 17:40:28 +00:00
|
|
|
// Create a new cookie jar.
|
|
|
|
cookieJar, err := cookies.NewCookieJar(bridge.NewTestCookieJar(), vault)
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() { require.NoError(t, cookieJar.PersistCookies()) }()
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
// Create a new bridge.
|
2022-10-11 22:20:04 +00:00
|
|
|
bridge, eventCh, err := bridge.New(
|
2022-10-11 17:40:28 +00:00
|
|
|
// The app stuff.
|
2022-10-09 21:05:52 +00:00
|
|
|
locator,
|
|
|
|
vault,
|
2022-10-11 17:40:28 +00:00
|
|
|
mocks.Autostarter,
|
|
|
|
mocks.Updater,
|
|
|
|
v2_3_0,
|
|
|
|
|
|
|
|
// The API stuff.
|
|
|
|
apiURL,
|
|
|
|
cookieJar,
|
2022-10-09 21:05:52 +00:00
|
|
|
useragent.New(),
|
|
|
|
mocks.TLSReporter,
|
2022-12-12 12:49:34 +00:00
|
|
|
netCtl.NewRoundTripper(&tls.Config{InsecureSkipVerify: true}),
|
2022-10-09 21:05:52 +00:00
|
|
|
mocks.ProxyCtl,
|
2022-10-21 16:41:31 +00:00
|
|
|
mocks.CrashHandler,
|
|
|
|
mocks.Reporter,
|
2022-10-11 17:40:28 +00:00
|
|
|
|
|
|
|
// The logging stuff.
|
2022-11-29 11:59:00 +00:00
|
|
|
os.Getenv("BRIDGE_LOG_IMAP_CLIENT") == "1",
|
|
|
|
os.Getenv("BRIDGE_LOG_IMAP_SERVER") == "1",
|
|
|
|
os.Getenv("BRIDGE_LOG_SMTP") == "1",
|
2022-10-09 21:05:52 +00:00
|
|
|
)
|
2022-08-26 15:00:21 +00:00
|
|
|
require.NoError(t, err)
|
2022-10-24 11:25:11 +00:00
|
|
|
require.Empty(t, bridge.GetErrors())
|
2022-08-26 15:00:21 +00:00
|
|
|
|
2022-10-11 22:20:04 +00:00
|
|
|
// Wait for bridge to finish loading users.
|
|
|
|
waitForEvent(t, eventCh, events.AllUsersLoaded{})
|
|
|
|
|
2022-10-24 11:25:11 +00:00
|
|
|
// Set random IMAP and SMTP ports for the tests.
|
|
|
|
require.NoError(t, bridge.SetIMAPPort(0))
|
|
|
|
require.NoError(t, bridge.SetSMTPPort(0))
|
|
|
|
|
2022-09-27 10:02:28 +00:00
|
|
|
// Close the bridge when done.
|
2022-10-26 21:09:05 +00:00
|
|
|
defer bridge.Close(ctx)
|
2022-09-27 10:02:28 +00:00
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
// Use the bridge.
|
2023-01-18 10:02:36 +00:00
|
|
|
tests(bridge)
|
|
|
|
}
|
|
|
|
|
|
|
|
// withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done.
|
|
|
|
func withBridge(
|
|
|
|
ctx context.Context,
|
|
|
|
t *testing.T,
|
|
|
|
apiURL string,
|
|
|
|
netCtl *proton.NetCtl,
|
|
|
|
locator bridge.Locator,
|
|
|
|
vaultKey []byte,
|
|
|
|
tests func(*bridge.Bridge, *bridge.Mocks),
|
|
|
|
) {
|
|
|
|
withMocks(t, func(mocks *bridge.Mocks) {
|
|
|
|
withBridgeNoMocks(ctx, t, mocks, apiURL, netCtl, locator, vaultKey, func(bridge *bridge.Bridge) {
|
|
|
|
tests(bridge, mocks)
|
|
|
|
})
|
|
|
|
})
|
2022-08-26 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
2022-10-11 22:20:04 +00:00
|
|
|
func waitForEvent[T any](t *testing.T, eventCh <-chan events.Event, wantEvent T) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
for event := range eventCh {
|
2022-10-18 11:54:12 +00:00
|
|
|
switch event.(type) { // nolint:gocritic
|
2022-10-11 22:20:04 +00:00
|
|
|
case T:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
// must is a helper function that panics on error.
|
|
|
|
func must[T any](val T, err error) T {
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
|
2022-11-15 16:24:54 +00:00
|
|
|
func getConnectedUserIDs(t *testing.T, b *bridge.Bridge) []string {
|
2022-08-26 15:00:21 +00:00
|
|
|
t.Helper()
|
|
|
|
|
2022-11-15 16:24:54 +00:00
|
|
|
return xslices.Filter(b.GetUserIDs(), func(userID string) bool {
|
|
|
|
info, err := b.GetUserInfo(userID)
|
2022-08-26 15:00:21 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-11-15 16:24:54 +00:00
|
|
|
return info.State == bridge.Connected
|
2022-08-26 15:00:21 +00:00
|
|
|
})
|
|
|
|
}
|
2022-11-10 22:28:08 +00:00
|
|
|
|
|
|
|
func chToType[In, Out any](inCh <-chan In, done func()) (<-chan Out, func()) {
|
|
|
|
outCh := make(chan Out)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer close(outCh)
|
|
|
|
|
|
|
|
for in := range inCh {
|
|
|
|
out, ok := any(in).(Out)
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("unexpected type %T", in))
|
|
|
|
}
|
|
|
|
|
|
|
|
outCh <- out
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return outCh, done
|
|
|
|
}
|