forked from Silverfish/proton-bridge
feat(GODT-2801): Identity Service
Identity Service contains all the information related to user state, addresses and keys. This patch also introduces the `State` type which can be used by other services to maintain their own copy of this state to avoid lock contention. Finally, there are currently no external facing methods via a CPC interface. Those will added as needed once the refactoring of the architecture is complete.
This commit is contained in:
447
internal/services/useridentity/service_test.go
Normal file
447
internal/services/useridentity/service_test.go
Normal file
@ -0,0 +1,447 @@
|
||||
// Copyright (c) 2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package useridentity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
mocks2 "github.com/ProtonMail/proton-bridge/v3/internal/events/mocks"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/useridentity/mocks"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const TestUserID = "MyUserID"
|
||||
|
||||
func TestService_OnUserEvent(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, eventPublisher, _ := newTestService(t, mockCtrl)
|
||||
|
||||
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserChanged{UserID: TestUserID})).Times(1)
|
||||
|
||||
service.onUserEvent(context.Background(), newTestUser())
|
||||
}
|
||||
|
||||
func TestService_OnUserSpaceChanged(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, eventPublisher, _ := newTestService(t, mockCtrl)
|
||||
|
||||
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UsedSpaceChanged{UserID: TestUserID, UsedSpace: 1024})).Times(1)
|
||||
|
||||
// Original value, no changes.
|
||||
service.onUserSpaceChanged(context.Background(), 0)
|
||||
|
||||
// New value, event should be published.
|
||||
service.onUserSpaceChanged(context.Background(), 1024)
|
||||
require.Equal(t, 1024, service.identity.User.UsedSpace)
|
||||
}
|
||||
|
||||
func TestService_OnRefreshEvent(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, eventPublisher, provider := newTestService(t, mockCtrl)
|
||||
|
||||
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserRefreshed{UserID: TestUserID, CancelEventPool: false})).Times(1)
|
||||
|
||||
newUser := newTestUserRefreshed()
|
||||
newAddresses := newTestAddressesRefreshed()
|
||||
|
||||
{
|
||||
getUserCall := provider.EXPECT().GetUser(gomock.Any()).Times(1).Return(newUser, nil)
|
||||
provider.EXPECT().GetAddresses(gomock.Any()).After(getUserCall).Times(1).Return(newAddresses, nil)
|
||||
}
|
||||
|
||||
// Original value, no changes.
|
||||
require.NoError(t, service.onRefreshEvent(context.Background()))
|
||||
|
||||
require.Equal(t, newUser, service.identity.User)
|
||||
require.Equal(t, newAddresses, service.identity.AddressesSorted)
|
||||
}
|
||||
|
||||
func TestService_OnAddressCreated(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, eventPublisher, _ := newTestService(t, mockCtrl)
|
||||
|
||||
newAddress := proton.Address{
|
||||
ID: "NewAddrID",
|
||||
Email: "new@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
}
|
||||
|
||||
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserAddressCreated{
|
||||
UserID: TestUserID,
|
||||
AddressID: newAddress.ID,
|
||||
Email: newAddress.Email,
|
||||
})).Times(1)
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: "",
|
||||
Action: proton.EventCreate,
|
||||
},
|
||||
Address: newAddress,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, service.identity.Addresses, newAddress.ID)
|
||||
}
|
||||
|
||||
func TestService_OnAddressCreatedDisabledDoesNotProduceEvent(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, _, _ := newTestService(t, mockCtrl)
|
||||
|
||||
newAddress := proton.Address{
|
||||
ID: "Address1",
|
||||
Email: "new@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
}
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: "",
|
||||
Action: proton.EventCreate,
|
||||
},
|
||||
Address: newAddress,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, service.identity.Addresses, newAddress.ID)
|
||||
}
|
||||
|
||||
func TestService_OnAddressCreatedDuplicateDoesNotProduceEvent(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, _, _ := newTestService(t, mockCtrl)
|
||||
|
||||
newAddress := proton.Address{
|
||||
ID: "NewAddrID",
|
||||
Email: "new@bar.com",
|
||||
Status: proton.AddressStatusDisabled,
|
||||
}
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: "",
|
||||
Action: proton.EventCreate,
|
||||
},
|
||||
Address: newAddress,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, service.identity.Addresses, newAddress.ID)
|
||||
}
|
||||
|
||||
func TestService_OnAddressUpdated(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, eventPublisher, _ := newTestService(t, mockCtrl)
|
||||
|
||||
newAddress := proton.Address{
|
||||
ID: "Address1",
|
||||
Email: "new@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
}
|
||||
|
||||
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserAddressUpdated{
|
||||
UserID: TestUserID,
|
||||
AddressID: newAddress.ID,
|
||||
Email: newAddress.Email,
|
||||
})).Times(1)
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: "",
|
||||
Action: proton.EventUpdate,
|
||||
},
|
||||
Address: newAddress,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, newAddress, service.identity.Addresses[newAddress.ID])
|
||||
}
|
||||
|
||||
func TestService_OnAddressUpdatedDisableFollowedByEnable(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, eventPublisher, _ := newTestService(t, mockCtrl)
|
||||
|
||||
newAddressDisabled := proton.Address{
|
||||
ID: "Address1",
|
||||
Email: "new@bar.com",
|
||||
Status: proton.AddressStatusDisabled,
|
||||
}
|
||||
newAddressEnabled := proton.Address{
|
||||
ID: "Address1",
|
||||
Email: "new@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
}
|
||||
|
||||
{
|
||||
disabledCall := eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserAddressDisabled{
|
||||
UserID: TestUserID,
|
||||
AddressID: newAddressDisabled.ID,
|
||||
Email: newAddressDisabled.Email,
|
||||
})).Times(1)
|
||||
|
||||
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserAddressEnabled{
|
||||
UserID: TestUserID,
|
||||
AddressID: newAddressEnabled.ID,
|
||||
Email: newAddressEnabled.Email,
|
||||
})).Times(1).After(disabledCall)
|
||||
}
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: "",
|
||||
Action: proton.EventUpdate,
|
||||
},
|
||||
Address: newAddressDisabled,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, newAddressDisabled, service.identity.Addresses[newAddressEnabled.ID])
|
||||
|
||||
err = service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: "",
|
||||
Action: proton.EventUpdate,
|
||||
},
|
||||
Address: newAddressEnabled,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, newAddressEnabled, service.identity.Addresses[newAddressEnabled.ID])
|
||||
}
|
||||
|
||||
func TestService_OnAddressUpdateCreatedIfNotExists(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, eventPublisher, _ := newTestService(t, mockCtrl)
|
||||
|
||||
newAddress := proton.Address{
|
||||
ID: "NewAddrID",
|
||||
Email: "new@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
}
|
||||
|
||||
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserAddressCreated{
|
||||
UserID: TestUserID,
|
||||
AddressID: newAddress.ID,
|
||||
Email: newAddress.Email,
|
||||
})).Times(1)
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: "",
|
||||
Action: proton.EventUpdate,
|
||||
},
|
||||
Address: newAddress,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, service.identity.Addresses, newAddress.ID)
|
||||
}
|
||||
|
||||
func TestService_OnAddressDeleted(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, eventPublisher, _ := newTestService(t, mockCtrl)
|
||||
|
||||
address := proton.Address{
|
||||
ID: "Address1",
|
||||
Email: "foo@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
}
|
||||
|
||||
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserAddressDeleted{
|
||||
UserID: TestUserID,
|
||||
AddressID: address.ID,
|
||||
Email: address.Email,
|
||||
})).Times(1)
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: address.ID,
|
||||
Action: proton.EventDelete,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotContains(t, service.identity.Addresses, address.ID)
|
||||
}
|
||||
|
||||
func TestService_OnAddressDeleteDisabledDoesNotProduceEvent(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, _, _ := newTestService(t, mockCtrl)
|
||||
|
||||
address := proton.Address{
|
||||
ID: "Address2",
|
||||
Email: "foo2@bar.com",
|
||||
Status: proton.AddressStatusDisabled,
|
||||
}
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: address.ID,
|
||||
Action: proton.EventDelete,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotContains(t, service.identity.Addresses, address.ID)
|
||||
}
|
||||
|
||||
func TestService_OnAddressDeletedUnknownDoesNotProduceEvent(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
service, _, _ := newTestService(t, mockCtrl)
|
||||
|
||||
address := proton.Address{
|
||||
ID: "UnknownID",
|
||||
Email: "new@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
}
|
||||
|
||||
err := service.onAddressEvent(context.Background(), []proton.AddressEvent{
|
||||
{
|
||||
EventItem: proton.EventItem{
|
||||
ID: address.ID,
|
||||
Action: proton.EventDelete,
|
||||
},
|
||||
Address: address,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func newTestService(t *testing.T, mockCtrl *gomock.Controller) (*Service, *mocks2.MockEventPublisher, *mocks.MockIdentityProvider) {
|
||||
subscribable := &userevents.NoOpSubscribable{}
|
||||
eventPublisher := mocks2.NewMockEventPublisher(mockCtrl)
|
||||
provider := mocks.NewMockIdentityProvider(mockCtrl)
|
||||
user := newTestUser()
|
||||
|
||||
provider.EXPECT().GetAddresses(gomock.Any()).Times(1).Return(newTestAddresses(), nil)
|
||||
|
||||
service, err := NewService(context.Background(), subscribable, user, eventPublisher, provider)
|
||||
require.NoError(t, err)
|
||||
|
||||
return service, eventPublisher, provider
|
||||
}
|
||||
|
||||
func newTestUser() proton.User {
|
||||
return proton.User{
|
||||
ID: TestUserID,
|
||||
Name: "Foo",
|
||||
DisplayName: "Foo",
|
||||
Email: "foo@bar",
|
||||
Keys: nil,
|
||||
UsedSpace: 0,
|
||||
MaxSpace: 0,
|
||||
MaxUpload: 0,
|
||||
Credit: 0,
|
||||
Currency: "",
|
||||
}
|
||||
}
|
||||
|
||||
func newTestUserRefreshed() proton.User {
|
||||
return proton.User{
|
||||
ID: TestUserID,
|
||||
Name: "Alternate",
|
||||
DisplayName: "Universe",
|
||||
Email: "foo2@bar",
|
||||
Keys: nil,
|
||||
UsedSpace: 0,
|
||||
MaxSpace: 0,
|
||||
MaxUpload: 0,
|
||||
Credit: 0,
|
||||
Currency: "USD",
|
||||
}
|
||||
}
|
||||
|
||||
func newTestAddresses() []proton.Address {
|
||||
return []proton.Address{
|
||||
{
|
||||
ID: "Address1",
|
||||
Email: "foo@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
Type: 0,
|
||||
Order: 0,
|
||||
DisplayName: "",
|
||||
Keys: nil,
|
||||
},
|
||||
{
|
||||
ID: "Address2",
|
||||
Email: "foo2@bar.com",
|
||||
Status: proton.AddressStatusDisabled,
|
||||
Type: 0,
|
||||
Order: 1,
|
||||
DisplayName: "",
|
||||
Keys: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTestAddressesRefreshed() []proton.Address {
|
||||
return []proton.Address{
|
||||
{
|
||||
ID: "Address1",
|
||||
Email: "foo@bar.com",
|
||||
Status: proton.AddressStatusEnabled,
|
||||
Type: 0,
|
||||
Order: 2,
|
||||
DisplayName: "FOo barish",
|
||||
Keys: nil,
|
||||
},
|
||||
{
|
||||
ID: "Address2",
|
||||
Email: "foo2@bar.com",
|
||||
Status: proton.AddressStatusDisabled,
|
||||
Type: 0,
|
||||
Order: 4,
|
||||
DisplayName: "New display name",
|
||||
Keys: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user