package commonspace import ( "context" "testing" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "github.com/anyproto/any-sync/app" "github.com/anyproto/any-sync/commonspace/config" "github.com/anyproto/any-sync/commonspace/credentialprovider" "github.com/anyproto/any-sync/commonspace/object/acl/list" "github.com/anyproto/any-sync/commonspace/object/acl/recordverifier" "github.com/anyproto/any-sync/commonspace/object/tree/objecttree" "github.com/anyproto/any-sync/commonspace/object/tree/treestorage" "github.com/anyproto/any-sync/commonspace/object/treemanager" "github.com/anyproto/any-sync/commonspace/spacepayloads" "github.com/anyproto/any-sync/commonspace/spacestorage" "github.com/anyproto/any-sync/commonspace/spacesyncproto" "github.com/anyproto/any-sync/commonspace/syncstatus" "github.com/anyproto/any-sync/net/peer" "github.com/anyproto/any-sync/net/rpc/rpctest" "github.com/anyproto/any-sync/net/streampool" "github.com/anyproto/any-sync/nodeconf/testconf" "github.com/anyproto/any-sync/testutil/accounttest" "github.com/anyproto/any-sync/util/crypto" "github.com/anyproto/any-sync/util/syncqueues" ) func TestSpaceService_SpacePull(t *testing.T) { var makeClientServer = func(t *testing.T) (fxC, fxS *spacePullFixture, peerId string) { fxC = newSpacePullFixture(t) fxS = newSpacePullFixture(t) peerId = "peer" mcS, mcC := rpctest.MultiConnPair(peerId, peerId+"client") pS, err := peer.NewPeer(mcS, fxC.ts) require.NoError(t, err) fxC.tp.AddPeer(ctx, pS) _, err = peer.NewPeer(mcC, fxS.ts) fxC.managerProvider.peer = pS require.NoError(t, err) return } t.Run("successful space pull", func(t *testing.T) { fxC, fxS, _ := makeClientServer(t) defer fxC.Finish(t) defer fxS.Finish(t) spaceId, payload := fxS.createTestSpace(t) space, err := fxC.spaceService.NewSpace(ctx, spaceId, Deps{ SyncStatus: syncstatus.NewNoOpSyncStatus(), TreeSyncer: &mockTreeSyncer{}, AccountService: fxC.account, }) require.NoError(t, err) require.NoError(t, space.Init(ctx)) require.NotNil(t, space) require.Equal(t, spaceId, space.Id()) storage := space.Storage() state, err := storage.StateStorage().GetState(ctx) require.NoError(t, err) require.Equal(t, spaceId, state.SpaceId) require.Equal(t, payload.SpaceHeaderWithId.Id, state.SpaceId) }) t.Run("space pull with acl records", func(t *testing.T) { fxC, fxS, _ := makeClientServer(t) defer fxC.Finish(t) defer fxS.Finish(t) spaceId, recLen, payload := fxS.createTestSpaceWithAclRecords(t) space, err := fxC.spaceService.NewSpace(ctx, spaceId, Deps{ SyncStatus: syncstatus.NewNoOpSyncStatus(), TreeSyncer: &mockTreeSyncer{}, AccountService: fxC.account, recordVerifier: recordverifier.NewValidateFull(), }) require.NoError(t, err) require.NotNil(t, space) require.NoError(t, space.Init(ctx)) records, err := space.Acl().RecordsAfter(ctx, "") require.NoError(t, err) require.Equal(t, recLen, len(records)) storage := space.Storage() state, err := storage.StateStorage().GetState(ctx) require.NoError(t, err) require.Equal(t, spaceId, state.SpaceId) require.Equal(t, payload.SpaceHeaderWithId.Id, state.SpaceId) }) } type spacePullFixture struct { *spaceService app *app.App ctrl *gomock.Controller ts *rpctest.TestServer tp *rpctest.TestPool tmpDir string account *accounttest.AccountTestService configService *testconf.StubConf storage spacestorage.SpaceStorageProvider managerProvider *mockPeerManagerProvider } func newSpacePullFixture(t *testing.T) (fx *spacePullFixture) { ts := rpctest.NewTestServer() fx = &spacePullFixture{ spaceService: New().(*spaceService), ctrl: gomock.NewController(t), app: new(app.App), ts: ts, tp: rpctest.NewTestPool(), tmpDir: t.TempDir(), account: &accounttest.AccountTestService{}, configService: &testconf.StubConf{}, storage: &spaceStorageProvider{rootPath: t.TempDir()}, managerProvider: &mockPeerManagerProvider{}, } configGetter := &mockSpaceConfigGetter{} fx.app.Register(configGetter). Register(mockCoordinatorClient{}). Register(mockNodeClient{}). Register(credentialprovider.NewNoOp()). Register(streampool.New()). Register(newStreamOpener("spaceId")). Register(fx.account). Register(fx.storage). Register(fx.managerProvider). Register(syncqueues.New()). Register(&mockTreeManager{}). Register(fx.spaceService). Register(fx.tp). Register(fx.ts). Register(fx.configService) require.NoError(t, spacesyncproto.DRPCRegisterSpaceSync(ts, &testSpaceSyncServer{spaceService: fx.spaceService})) require.NoError(t, fx.app.Start(ctx)) return fx } func (fx *spacePullFixture) Finish(t *testing.T) { fx.ctrl.Finish() } func (fx *spacePullFixture) createTestSpace(t *testing.T) (string, spacestorage.SpaceStorageCreatePayload) { keys := fx.account.Account() masterKey, _, err := crypto.GenerateRandomEd25519KeyPair() require.NoError(t, err) metaKey, _, err := crypto.GenerateRandomEd25519KeyPair() require.NoError(t, err) readKey := crypto.NewAES() metadata := []byte("metadata") payload := spacepayloads.SpaceCreatePayload{ SigningKey: keys.SignKey, SpaceType: "test", ReplicationKey: 1, SpacePayload: nil, MasterKey: masterKey, ReadKey: readKey, MetadataKey: metaKey, Metadata: metadata, } createPayload, err := spacepayloads.StoragePayloadForSpaceCreate(payload) require.NoError(t, err) storage, err := fx.storage.CreateSpaceStorage(ctx, createPayload) require.NoError(t, err) require.NoError(t, storage.Close(ctx)) return createPayload.SpaceHeaderWithId.Id, createPayload } func (fx *spacePullFixture) createTestSpaceWithAclRecords(t *testing.T) (string, int, spacestorage.SpaceStorageCreatePayload) { spaceId, payload := fx.createTestSpace(t) keys := fx.account.Account() executor := list.NewExternalKeysAclExecutor(spaceId, keys, []byte("metadata"), payload.AclWithId) cmds := []string{ "0.init::0", "0.invite::invId1", "0.invite::invId2", } for _, cmd := range cmds { err := executor.Execute(cmd) require.NoError(t, err) } allRecords, err := executor.ActualAccounts()["0"].Acl.RecordsAfter(ctx, "") require.NoError(t, err) storage, err := fx.storage.WaitSpaceStorage(ctx, spaceId) require.NoError(t, err) defer storage.Close(ctx) aclStorage, err := storage.AclStorage() require.NoError(t, err) for i, rec := range allRecords[1:] { // Skip first record as it's already there err := aclStorage.AddAll(ctx, []list.StorageRecord{ { RawRecord: rec.Payload, Id: rec.Id, PrevId: allRecords[i].Id, Order: i + 2, ChangeSize: len(rec.Payload), }, }) require.NoError(t, err) } return spaceId, len(allRecords), payload } type testSpaceSyncServer struct { spacesyncproto.DRPCSpaceSyncUnimplementedServer spaceService SpaceService } func (t *testSpaceSyncServer) SpacePull(ctx context.Context, req *spacesyncproto.SpacePullRequest) (*spacesyncproto.SpacePullResponse, error) { sp, err := t.spaceService.NewSpace(ctx, req.Id, Deps{ SyncStatus: syncstatus.NewNoOpSyncStatus(), TreeSyncer: mockTreeSyncer{}, recordVerifier: recordverifier.NewValidateFull(), }) if err != nil { return nil, err } err = sp.Init(ctx) if err != nil { return nil, err } spaceDesc, err := sp.Description(ctx) if err != nil { return nil, err } return &spacesyncproto.SpacePullResponse{ Payload: &spacesyncproto.SpacePayload{ SpaceHeader: spaceDesc.SpaceHeader, AclPayloadId: spaceDesc.AclId, AclPayload: spaceDesc.AclPayload, SpaceSettingsPayload: spaceDesc.SpaceSettingsPayload, SpaceSettingsPayloadId: spaceDesc.SpaceSettingsId, }, AclRecords: spaceDesc.AclRecords, }, nil } type mockSpaceConfigGetter struct{} func (m *mockSpaceConfigGetter) GetStreamConfig() streampool.StreamConfig { return streampool.StreamConfig{} } func (m *mockSpaceConfigGetter) Init(a *app.App) error { return nil } func (m *mockSpaceConfigGetter) Name() string { return "config" } func (m *mockSpaceConfigGetter) GetSpace() config.Config { return config.Config{ GCTTL: 60, SyncPeriod: 5, KeepTreeDataInMemory: true, } } type mockTreeManager struct{} func (m *mockTreeManager) Init(a *app.App) error { return nil } func (m *mockTreeManager) Name() string { return treemanager.CName } func (m *mockTreeManager) Run(ctx context.Context) error { return nil } func (m *mockTreeManager) Close(ctx context.Context) error { return nil } func (m *mockTreeManager) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) { return nil, nil } func (m *mockTreeManager) CreateTree(ctx context.Context, spaceId string) (objecttree.ObjectTree, error) { return nil, nil } func (m *mockTreeManager) PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload) (objecttree.ObjectTree, error) { return nil, nil } func (m *mockTreeManager) DeleteTree(ctx context.Context, spaceId, treeId string) error { return nil } func (m *mockTreeManager) MarkTreeDeleted(ctx context.Context, spaceId, treeId string) error { return nil } func (m *mockTreeManager) ValidateAndPutTree(ctx context.Context, spaceId string, payload treestorage.TreeStorageCreatePayload) error { return nil }