From 9e6cbcb35eb1f9c32b127140bdf6647dab994f63 Mon Sep 17 00:00:00 2001 From: James Houlahan Date: Sun, 20 Nov 2022 20:27:58 +0100 Subject: [PATCH] GODT-2040: Bump UID validity when clearing sync status --- internal/bridge/refresh_test.go | 113 ++++++++++++++++++++++++++++++++ internal/user/imap.go | 11 +--- internal/vault/user.go | 18 +++-- 3 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 internal/bridge/refresh_test.go diff --git a/internal/bridge/refresh_test.go b/internal/bridge/refresh_test.go new file mode 100644 index 00000000..d4e0f0e0 --- /dev/null +++ b/internal/bridge/refresh_test.go @@ -0,0 +1,113 @@ +// Copyright (c) 2022 Proton AG +// +// 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 . + +package bridge_test + +import ( + "context" + "fmt" + "testing" + + "github.com/ProtonMail/proton-bridge/v2/internal/bridge" + "github.com/ProtonMail/proton-bridge/v2/internal/constants" + "github.com/ProtonMail/proton-bridge/v2/internal/events" + "github.com/bradenaw/juniper/iterator" + "github.com/emersion/go-imap/client" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "gitlab.protontech.ch/go/liteapi" + "gitlab.protontech.ch/go/liteapi/server" +) + +func TestBridge_Refresh(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { + userID, _, err := s.CreateUser("imap", "imap@pm.me", password) + require.NoError(t, err) + + names := iterator.Collect(iterator.Map(iterator.Counter(10), func(i int) string { + return fmt.Sprintf("folder%v", i) + })) + + for _, name := range names { + must(s.CreateLabel(userID, name, "", liteapi.LabelTypeFolder)) + } + + // The initial user should be fully synced. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) { + syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{})) + defer done() + + userID, err := b.LoginFull(ctx, "imap", password, nil, nil) + require.NoError(t, err) + + require.Equal(t, userID, (<-syncCh).UserID) + }) + + // If we then connect an IMAP client, it should see all the labels with UID validity of 1. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) { + mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes() + + info, err := b.GetUserInfo(userID) + require.NoError(t, err) + require.True(t, info.State == bridge.Connected) + + client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort())) + require.NoError(t, err) + require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass))) + defer func() { _ = client.Logout() }() + + for _, name := range names { + status, err := client.Select("Folders/"+name, false) + require.NoError(t, err) + require.Equal(t, uint32(1), status.UidValidity) + } + }) + + // Refresh the user; this will force a resync. + require.NoError(t, s.RefreshUser(userID, liteapi.RefreshAll)) + + // If we then connect an IMAP client, it should see all the labels with UID validity of 1. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) { + mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes() + + syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{})) + defer done() + + require.Equal(t, userID, (<-syncCh).UserID) + }) + + // After resync, the IMAP client should see all the labels with UID validity of 2. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) { + mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes() + + info, err := b.GetUserInfo(userID) + require.NoError(t, err) + require.True(t, info.State == bridge.Connected) + + client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort())) + require.NoError(t, err) + require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass))) + defer func() { _ = client.Logout() }() + + for _, name := range names { + status, err := client.Select("Folders/"+name, false) + require.NoError(t, err) + require.Equal(t, uint32(2), status.UidValidity) + } + }) + }) +} diff --git a/internal/user/imap.go b/internal/user/imap.go index 015bd329..146073fb 100644 --- a/internal/user/imap.go +++ b/internal/user/imap.go @@ -408,16 +408,7 @@ func (conn *imapConnector) GetUpdates() <-chan imap.Update { // GetUIDValidity returns the default UID validity for this user. func (conn *imapConnector) GetUIDValidity() imap.UID { - if validity, ok := conn.vault.GetUIDValidity(conn.addrID); ok { - return validity - } - - // Initialize to 1. - if err := conn.vault.SetUIDValidity(conn.addrID, imap.UID(1)); err != nil { - conn.log.WithError(err).Error("Failed to set UID validity") - } - - return imap.UID(1) + return conn.vault.GetUIDValidity(conn.addrID) } // SetUIDValidity sets the default UID validity for this user. diff --git a/internal/vault/user.go b/internal/vault/user.go index 28b1d424..38ee3ce9 100644 --- a/internal/vault/user.go +++ b/internal/vault/user.go @@ -67,13 +67,16 @@ func (user *User) RemoveGluonID(addrID, gluonID string) error { return err } -func (user *User) GetUIDValidity(addrID string) (imap.UID, bool) { - validity, ok := user.vault.getUser(user.userID).UIDValidity[addrID] - if !ok { - return imap.UID(0), false +func (user *User) GetUIDValidity(addrID string) imap.UID { + if validity, ok := user.vault.getUser(user.userID).UIDValidity[addrID]; ok { + return validity } - return validity, true + if err := user.SetUIDValidity(addrID, 1); err != nil { + panic(err) + } + + return user.GetUIDValidity(addrID) } func (user *User) SetUIDValidity(addrID string, validity imap.UID) error { @@ -159,7 +162,12 @@ func (user *User) SetLastMessageID(messageID string) error { func (user *User) ClearSyncStatus() error { return user.vault.modUser(user.userID, func(data *UserData) { data.SyncStatus = SyncStatus{} + data.EventID = "" + + for addrID := range data.UIDValidity { + data.UIDValidity[addrID]++ + } }) }