mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-07 21:37:04 +09:00
440 lines
17 KiB
Go
440 lines
17 KiB
Go
package space
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/anyproto/any-sync/app"
|
|
"github.com/anyproto/any-sync/commonspace/mock_commonspace"
|
|
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
"github.com/anyproto/any-sync/coordinator/coordinatorclient/mock_coordinatorclient"
|
|
"github.com/anyproto/any-sync/testutil/accounttest"
|
|
"github.com/anyproto/any-sync/util/crypto"
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"github.com/anyproto/anytype-heart/core/anytype/config"
|
|
"github.com/anyproto/anytype-heart/core/domain"
|
|
"github.com/anyproto/anytype-heart/core/notifications/mock_notifications"
|
|
"github.com/anyproto/anytype-heart/core/wallet/mock_wallet"
|
|
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
|
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
|
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
|
"github.com/anyproto/anytype-heart/space/clientspace"
|
|
"github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace"
|
|
"github.com/anyproto/anytype-heart/space/internal/components/aclobjectmanager"
|
|
"github.com/anyproto/anytype-heart/space/internal/spacecontroller"
|
|
"github.com/anyproto/anytype-heart/space/internal/spacecontroller/mock_spacecontroller"
|
|
"github.com/anyproto/anytype-heart/space/mock_space"
|
|
"github.com/anyproto/anytype-heart/space/spacecore"
|
|
"github.com/anyproto/anytype-heart/space/spacecore/mock_spacecore"
|
|
"github.com/anyproto/anytype-heart/space/spacefactory/mock_spacefactory"
|
|
"github.com/anyproto/anytype-heart/space/spaceinfo"
|
|
"github.com/anyproto/anytype-heart/space/techspace"
|
|
"github.com/anyproto/anytype-heart/space/techspace/mock_techspace"
|
|
"github.com/anyproto/anytype-heart/tests/testutil"
|
|
)
|
|
|
|
var ctx = context.Background()
|
|
|
|
const (
|
|
testPersonalSpaceID = "personal.12345"
|
|
)
|
|
|
|
type mockAclJoiner struct {
|
|
}
|
|
|
|
func (m *mockAclJoiner) Join(ctx context.Context, spaceId, networkId string, inviteCid cid.Cid, inviteFileKey crypto.SymKey) error {
|
|
return nil
|
|
}
|
|
func (m *mockAclJoiner) Init(a *app.App) error {
|
|
return nil
|
|
}
|
|
func (m *mockAclJoiner) Name() string {
|
|
return "aclJoiner"
|
|
}
|
|
|
|
func TestService_Init(t *testing.T) {
|
|
t.Run("tech space getter", func(t *testing.T) {
|
|
serv := New().(*service)
|
|
serv.techSpaceId = "tech.space"
|
|
factory := mock_spacefactory.NewMockSpaceFactory(t)
|
|
serv.factory = factory
|
|
serv.techSpaceReady = make(chan struct{})
|
|
|
|
// not initialized - expect context deadline
|
|
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Millisecond)
|
|
defer ctxCancel()
|
|
_, err := serv.Get(ctx, serv.techSpaceId)
|
|
require.ErrorIs(t, err, context.DeadlineExceeded)
|
|
|
|
// initialized - expect space
|
|
ctx2, ctxCancel2 := context.WithTimeout(context.Background(), 2*time.Millisecond)
|
|
defer ctxCancel2()
|
|
|
|
factory.EXPECT().LoadAndSetTechSpace(ctx2).Return(&clientspace.TechSpace{}, nil)
|
|
require.NoError(t, serv.loadTechSpace(ctx2))
|
|
|
|
s, err := serv.Get(ctx2, serv.techSpaceId)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, s)
|
|
})
|
|
t.Run("new account", func(t *testing.T) {
|
|
newFixture(t, nil)
|
|
})
|
|
t.Run("old account", func(t *testing.T) {
|
|
newFixture(t, func(t *testing.T, fx *fixture) {
|
|
fx.factory.EXPECT().LoadAndSetTechSpace(mock.Anything).Return(&clientspace.TechSpace{TechSpace: fx.techSpace}, nil)
|
|
fx.techSpace.EXPECT().WakeUpViews()
|
|
})
|
|
})
|
|
t.Run("old account, no internet, then internet appeared", func(t *testing.T) {
|
|
newFixture(t, func(t *testing.T, fx *fixture) {
|
|
fx.factory.EXPECT().LoadAndSetTechSpace(mock.Anything).Return(nil, context.DeadlineExceeded).Times(1)
|
|
fx.spaceCore.EXPECT().StorageExistsLocally(mock.Anything, fx.spaceId).Return(false, nil)
|
|
fx.factory.EXPECT().LoadAndSetTechSpace(mock.Anything).Return(&clientspace.TechSpace{TechSpace: fx.techSpace}, nil)
|
|
fx.techSpace.EXPECT().WakeUpViews()
|
|
})
|
|
})
|
|
t.Run("old account, no internet, but personal space exists", func(t *testing.T) {
|
|
newFixture(t, func(t *testing.T, fx *fixture) {
|
|
fx.factory.EXPECT().LoadAndSetTechSpace(mock.Anything).Return(nil, context.DeadlineExceeded).Times(1)
|
|
fx.spaceCore.EXPECT().StorageExistsLocally(mock.Anything, fx.spaceId).Return(true, nil)
|
|
fx.spaceCore.EXPECT().Get(mock.Anything, fx.spaceId).Return(nil, nil)
|
|
fx.factory.EXPECT().CreateAndSetTechSpace(mock.Anything).Return(&clientspace.TechSpace{TechSpace: fx.techSpace}, nil)
|
|
prCtrl := mock_spacecontroller.NewMockSpaceController(t)
|
|
fx.factory.EXPECT().NewPersonalSpace(mock.Anything, mock.Anything).Return(prCtrl, nil)
|
|
prCtrl.EXPECT().Close(mock.Anything).Return(nil)
|
|
fx.techSpace.EXPECT().WakeUpViews()
|
|
})
|
|
})
|
|
t.Run("very old account without tech space", func(t *testing.T) {
|
|
newFixture(t, func(t *testing.T, fx *fixture) {
|
|
fx.factory.EXPECT().LoadAndSetTechSpace(mock.Anything).Return(nil, spacesyncproto.ErrSpaceMissing)
|
|
fx.spaceCore.EXPECT().Get(mock.Anything, fx.spaceId).Return(nil, nil)
|
|
fx.factory.EXPECT().CreateAndSetTechSpace(mock.Anything).Return(&clientspace.TechSpace{TechSpace: fx.techSpace}, nil)
|
|
prCtrl := mock_spacecontroller.NewMockSpaceController(t)
|
|
fx.factory.EXPECT().NewPersonalSpace(mock.Anything, mock.Anything).Return(prCtrl, nil)
|
|
prCtrl.EXPECT().Close(mock.Anything).Return(nil)
|
|
fx.techSpace.EXPECT().WakeUpViews()
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestService_UpdateRemoteStatus(t *testing.T) {
|
|
spaceID := "id"
|
|
t.Run("don't send notification, because account status deleted", func(t *testing.T) {
|
|
// given
|
|
controller := mock_spacecontroller.NewMockSpaceController(t)
|
|
statusInfo := makeRemoteInfo(spaceID, false, spaceinfo.RemoteStatusDeleted)
|
|
controller.EXPECT().SetLocalInfo(context.Background(), statusInfo.LocalInfo).Return(nil)
|
|
controller.EXPECT().GetStatus().Return(spaceinfo.AccountStatusDeleted)
|
|
notifications := mock_notifications.NewMockNotifications(t)
|
|
s := service{
|
|
spaceControllers: map[string]spacecontroller.SpaceController{spaceID: controller},
|
|
notificationService: notifications,
|
|
}
|
|
|
|
// when
|
|
err := s.UpdateRemoteStatus(ctx, statusInfo)
|
|
|
|
// then
|
|
notifications.AssertNotCalled(t, "CreateAndSend")
|
|
assert.Nil(t, err)
|
|
})
|
|
t.Run("don't send notification, because account status removing", func(t *testing.T) {
|
|
// given
|
|
controller := mock_spacecontroller.NewMockSpaceController(t)
|
|
statusInfo := makeRemoteInfo(spaceID, false, spaceinfo.RemoteStatusDeleted)
|
|
controller.EXPECT().SetLocalInfo(context.Background(), statusInfo.LocalInfo).Return(nil)
|
|
controller.EXPECT().GetStatus().Return(spaceinfo.AccountStatusRemoving)
|
|
notifications := mock_notifications.NewMockNotifications(t)
|
|
s := service{
|
|
spaceControllers: map[string]spacecontroller.SpaceController{spaceID: controller},
|
|
notificationService: notifications,
|
|
}
|
|
|
|
// when
|
|
err := s.UpdateRemoteStatus(ctx, statusInfo)
|
|
|
|
// then
|
|
notifications.AssertNotCalled(t, "CreateAndSend")
|
|
assert.Nil(t, err)
|
|
})
|
|
t.Run("don't send notification, because space status - not deleted", func(t *testing.T) {
|
|
// given
|
|
controller := mock_spacecontroller.NewMockSpaceController(t)
|
|
statusInfo := makeRemoteInfo(spaceID, false, spaceinfo.RemoteStatusOk)
|
|
controller.EXPECT().SetLocalInfo(context.Background(), statusInfo.LocalInfo).Return(nil)
|
|
notifications := mock_notifications.NewMockNotifications(t)
|
|
s := service{
|
|
spaceControllers: map[string]spacecontroller.SpaceController{spaceID: controller},
|
|
notificationService: notifications,
|
|
}
|
|
|
|
// when
|
|
err := s.UpdateRemoteStatus(ctx, statusInfo)
|
|
|
|
// then
|
|
notifications.AssertNotCalled(t, "CreateAndSend")
|
|
assert.Nil(t, err)
|
|
})
|
|
t.Run("send notification, because space status - deleted, but we can't get space name", func(t *testing.T) {
|
|
// given
|
|
controller := mock_spacecontroller.NewMockSpaceController(t)
|
|
statusInfo := makeRemoteInfo(spaceID, false, spaceinfo.RemoteStatusDeleted)
|
|
controller.EXPECT().SetLocalInfo(context.Background(), statusInfo.LocalInfo).Return(nil)
|
|
controller.EXPECT().GetStatus().Return(spaceinfo.AccountStatusActive)
|
|
controller.EXPECT().GetLocalStatus().Return(spaceinfo.LocalStatusOk)
|
|
controller.EXPECT().SetPersistentInfo(context.Background(), makePersistentInfo(spaceID, spaceinfo.AccountStatusRemoving)).Return(nil)
|
|
|
|
accountKeys, err := accountdata.NewRandom()
|
|
assert.Nil(t, err)
|
|
wallet := mock_wallet.NewMockWallet(t)
|
|
wallet.EXPECT().Account().Return(accountKeys)
|
|
identity := accountKeys.SignKey.GetPublic().Account()
|
|
|
|
notifications := mock_notifications.NewMockNotifications(t)
|
|
notifications.EXPECT().CreateAndSend(&model.Notification{
|
|
Id: strings.Join([]string{spaceID, identity}, "_"),
|
|
Payload: &model.NotificationPayloadOfParticipantRemove{
|
|
ParticipantRemove: &model.NotificationParticipantRemove{
|
|
SpaceId: spaceID,
|
|
SpaceName: "",
|
|
},
|
|
},
|
|
Space: spaceID,
|
|
}).Return(nil)
|
|
|
|
storeFixture := objectstore.NewStoreFixture(t)
|
|
s := service{
|
|
spaceControllers: map[string]spacecontroller.SpaceController{spaceID: controller},
|
|
notificationService: notifications,
|
|
accountService: wallet,
|
|
spaceNameGetter: storeFixture,
|
|
}
|
|
|
|
// when
|
|
err = s.UpdateRemoteStatus(ctx, statusInfo)
|
|
|
|
// then
|
|
assert.Nil(t, err)
|
|
})
|
|
t.Run("send notification, because space remote status - deleted, but we get space name with name Test", func(t *testing.T) {
|
|
// given
|
|
controller := mock_spacecontroller.NewMockSpaceController(t)
|
|
statusInfo := makeRemoteInfo(spaceID, false, spaceinfo.RemoteStatusDeleted)
|
|
controller.EXPECT().SetLocalInfo(context.Background(), statusInfo.LocalInfo).Return(nil)
|
|
controller.EXPECT().GetStatus().Return(spaceinfo.AccountStatusActive)
|
|
controller.EXPECT().GetLocalStatus().Return(spaceinfo.LocalStatusOk)
|
|
controller.EXPECT().SetPersistentInfo(context.Background(), makePersistentInfo(spaceID, spaceinfo.AccountStatusRemoving)).Return(nil)
|
|
|
|
accountKeys, err := accountdata.NewRandom()
|
|
assert.Nil(t, err)
|
|
wallet := mock_wallet.NewMockWallet(t)
|
|
wallet.EXPECT().Account().Return(accountKeys)
|
|
identity := accountKeys.SignKey.GetPublic().Account()
|
|
|
|
notifications := mock_notifications.NewMockNotifications(t)
|
|
notifications.EXPECT().CreateAndSend(&model.Notification{
|
|
Id: strings.Join([]string{spaceID, identity}, "_"),
|
|
Payload: &model.NotificationPayloadOfParticipantRemove{
|
|
ParticipantRemove: &model.NotificationParticipantRemove{
|
|
SpaceId: spaceID,
|
|
SpaceName: "Test",
|
|
},
|
|
},
|
|
Space: spaceID,
|
|
}).Return(nil)
|
|
|
|
storeFixture := objectstore.NewStoreFixture(t)
|
|
storeFixture.AddObjects(t, storeFixture.TechSpaceId(), []objectstore.TestObject{{
|
|
bundle.RelationKeyResolvedLayout: domain.Int64(int64(model.ObjectType_spaceView)),
|
|
bundle.RelationKeyId: domain.String("spaceViewId"),
|
|
bundle.RelationKeyTargetSpaceId: domain.String(spaceID),
|
|
bundle.RelationKeyName: domain.String("Test"),
|
|
}})
|
|
|
|
s := service{
|
|
spaceControllers: map[string]spacecontroller.SpaceController{spaceID: controller},
|
|
notificationService: notifications,
|
|
accountService: wallet,
|
|
spaceNameGetter: storeFixture,
|
|
}
|
|
|
|
// when
|
|
err = s.UpdateRemoteStatus(ctx, statusInfo)
|
|
|
|
// then
|
|
assert.Nil(t, err)
|
|
})
|
|
}
|
|
|
|
func TestService_UpdateSharedLimits(t *testing.T) {
|
|
t.Run("update shared limits", func(t *testing.T) {
|
|
// given
|
|
mockTechSpace := mock_techspace.NewMockTechSpace(t)
|
|
s := service{
|
|
personalSpaceId: "spaceId",
|
|
techSpace: &clientspace.TechSpace{TechSpace: mockTechSpace},
|
|
}
|
|
mockAccountObject := mock_techspace.NewMockAccountObject(t)
|
|
mockTechSpace.EXPECT().DoAccountObject(ctx, mock.Anything).RunAndReturn(
|
|
func(ctx context.Context, f func(view techspace.AccountObject) error) error {
|
|
return f(mockAccountObject)
|
|
})
|
|
mockAccountObject.EXPECT().SetSharedSpacesLimit(10).Return(nil)
|
|
|
|
// when
|
|
err := s.UpdateSharedLimits(ctx, 10)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func newFixture(t *testing.T, expectOldAccount func(t *testing.T, fx *fixture)) *fixture {
|
|
ctrl := gomock.NewController(t)
|
|
fx := &fixture{
|
|
spaceId: "bafyreifhyhdwrhwc23yi52w42osr4erqhiu2domqd3vwnngdee23kulpre.3aop5yrnf383q",
|
|
service: New().(*service),
|
|
a: new(app.App),
|
|
ctrl: ctrl,
|
|
spaceCore: mock_spacecore.NewMockSpaceCoreService(t),
|
|
coordClient: mock_coordinatorclient.NewMockCoordinatorClient(ctrl),
|
|
factory: mock_spacefactory.NewMockSpaceFactory(t),
|
|
notificationSender: mock_space.NewMockNotificationSender(t),
|
|
objectStore: objectstore.NewStoreFixture(t),
|
|
updater: mock_space.NewMockcoordinatorStatusUpdater(t),
|
|
config: config.New(config.WithNewAccount(expectOldAccount == nil)),
|
|
}
|
|
keys, err := accountdata.NewRandom()
|
|
require.NoError(t, err)
|
|
fx.config.PeferYamuxTransport = true
|
|
wallet := mock_wallet.NewMockWallet(t)
|
|
path, err := os.MkdirTemp("", "repo")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(path)
|
|
wallet.EXPECT().Account().Return(keys)
|
|
wallet.EXPECT().RepoPath().Return(path)
|
|
|
|
fx.a.
|
|
Register(testutil.PrepareMock(ctx, fx.a, wallet)).
|
|
Register(fx.config).
|
|
Register(&mockAclJoiner{}).
|
|
Register(testutil.PrepareMock(ctx, fx.a, fx.notificationSender)).
|
|
Register(testutil.PrepareMock(ctx, fx.a, fx.updater)).
|
|
Register(testutil.PrepareMock(ctx, fx.a, fx.spaceCore)).
|
|
Register(testutil.PrepareMock(ctx, fx.a, fx.coordClient)).
|
|
Register(testutil.PrepareMock(ctx, fx.a, fx.factory)).
|
|
Register(testutil.PrepareMock(ctx, fx.a, mock_notifications.NewMockNotifications(t))).
|
|
Register(fx.objectStore).
|
|
Register(&testSpaceLoaderListener{}).
|
|
Register(fx.service)
|
|
fx.expectRun(t, expectOldAccount)
|
|
|
|
require.NoError(t, fx.a.Start(ctx))
|
|
t.Cleanup(func() {
|
|
require.NoError(t, fx.a.Close(ctx))
|
|
})
|
|
return fx
|
|
}
|
|
|
|
type fixture struct {
|
|
*service
|
|
spaceId string
|
|
a *app.App
|
|
config *config.Config
|
|
factory *mock_spacefactory.MockSpaceFactory
|
|
spaceCore *mock_spacecore.MockSpaceCoreService
|
|
updater *mock_space.MockcoordinatorStatusUpdater
|
|
notificationSender *mock_space.MockNotificationSender
|
|
accountService *accounttest.AccountTestService
|
|
coordClient *mock_coordinatorclient.MockCoordinatorClient
|
|
ctrl *gomock.Controller
|
|
techSpace *mock_techspace.MockTechSpace
|
|
clientSpace *mock_clientspace.MockSpace
|
|
objectStore *objectstore.StoreFixture
|
|
}
|
|
|
|
type lwMock struct {
|
|
sp clientspace.Space
|
|
}
|
|
|
|
func (l lwMock) WaitLoad(ctx context.Context) (sp clientspace.Space, err error) {
|
|
return l.sp, nil
|
|
}
|
|
|
|
func (fx *fixture) expectRun(t *testing.T, expectOldAccount func(t *testing.T, fx *fixture)) {
|
|
fx.spaceCore.EXPECT().DeriveID(mock.Anything, spacecore.SpaceType).Return(fx.spaceId, nil).Times(1)
|
|
fx.spaceCore.EXPECT().DeriveID(mock.Anything, spacecore.TechSpaceType).Return("techSpaceId", nil).Times(1)
|
|
fx.updater.EXPECT().UpdateCoordinatorStatus()
|
|
clientSpace := mock_clientspace.NewMockSpace(t)
|
|
mpCtrl := mock_spacecontroller.NewMockSpaceController(t)
|
|
fx.factory.EXPECT().CreateMarketplaceSpace(mock.Anything).Return(mpCtrl, nil)
|
|
mpCtrl.EXPECT().Start(mock.Anything).Return(nil)
|
|
mpCtrl.EXPECT().Close(mock.Anything).Return(nil)
|
|
ts := mock_techspace.NewMockTechSpace(t)
|
|
fx.techSpace = ts
|
|
fx.clientSpace = clientSpace
|
|
if expectOldAccount == nil {
|
|
fx.factory.EXPECT().CreateAndSetTechSpace(mock.Anything).Return(&clientspace.TechSpace{TechSpace: ts}, nil)
|
|
prCtrl := mock_spacecontroller.NewMockSpaceController(t)
|
|
prCtrl.EXPECT().SpaceId().Return(fx.spaceId)
|
|
commonSpace := mock_commonspace.NewMockSpace(fx.ctrl)
|
|
commonSpace.EXPECT().Id().Return(fx.spaceId).AnyTimes()
|
|
fx.spaceCore.EXPECT().Create(mock.Anything, mock.Anything, mock.Anything).Return(&spacecore.AnySpace{Space: commonSpace}, nil)
|
|
fx.factory.EXPECT().CreateShareableSpace(mock.Anything, mock.Anything).Return(prCtrl, nil)
|
|
lw := lwMock{clientSpace}
|
|
clientSpace.EXPECT().Id().Return(fx.spaceId)
|
|
prCtrl.EXPECT().Current().Return(lw)
|
|
prCtrl.EXPECT().Close(mock.Anything).Return(nil)
|
|
ts.EXPECT().WakeUpViews()
|
|
} else {
|
|
expectOldAccount(t, fx)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (fx *fixture) finish(t *testing.T) {
|
|
require.NoError(t, fx.a.Close(ctx))
|
|
fx.ctrl.Finish()
|
|
}
|
|
|
|
func makePersistentInfo(spaceId string, status spaceinfo.AccountStatus) spaceinfo.SpacePersistentInfo {
|
|
info := spaceinfo.NewSpacePersistentInfo(spaceId)
|
|
info.SetAccountStatus(status)
|
|
return info
|
|
}
|
|
|
|
func makeRemoteInfo(spaceId string, isOwned bool, status spaceinfo.RemoteStatus) spaceinfo.SpaceRemoteStatusInfo {
|
|
info := spaceinfo.SpaceRemoteStatusInfo{
|
|
IsOwned: isOwned,
|
|
LocalInfo: makeLocalInfo(spaceId, status),
|
|
}
|
|
return info
|
|
}
|
|
|
|
func makeLocalInfo(spaceId string, remoteStatus spaceinfo.RemoteStatus) spaceinfo.SpaceLocalInfo {
|
|
info := spaceinfo.NewSpaceLocalInfo(spaceId)
|
|
info.SetRemoteStatus(remoteStatus)
|
|
return info
|
|
}
|
|
|
|
type testSpaceLoaderListener struct {
|
|
aclobjectmanager.SpaceLoaderListener
|
|
app.Component
|
|
}
|
|
|
|
func (s *testSpaceLoaderListener) OnSpaceLoad(_ string) {}
|
|
func (s *testSpaceLoaderListener) OnSpaceUnload(_ string) {}
|
|
|
|
func (s *testSpaceLoaderListener) Init(a *app.App) (err error) { return nil }
|
|
func (s *testSpaceLoaderListener) Name() (name string) { return "spaceLoaderListener" }
|