From 9cd73af53c582e150f36e1a06b45da43cceba41c Mon Sep 17 00:00:00 2001 From: mcrakhman Date: Mon, 22 May 2023 15:37:48 +0200 Subject: [PATCH] Implement test utils for space --- commonspace/spacestorage/inmemoryprovider.go | 54 ++++ commonspace/spacestorage/inmemorystorage.go | 153 +++++++++ commonspace/spaceutils_test.go | 317 +++++++++++++++++++ 3 files changed, 524 insertions(+) create mode 100644 commonspace/spacestorage/inmemoryprovider.go create mode 100644 commonspace/spacestorage/inmemorystorage.go create mode 100644 commonspace/spaceutils_test.go diff --git a/commonspace/spacestorage/inmemoryprovider.go b/commonspace/spacestorage/inmemoryprovider.go new file mode 100644 index 00000000..b1357962 --- /dev/null +++ b/commonspace/spacestorage/inmemoryprovider.go @@ -0,0 +1,54 @@ +package spacestorage + +import ( + "context" + "github.com/anytypeio/any-sync/app" + "sync" +) + +func NewInMemorySpaceStorageProvider() SpaceStorageProvider { + return &InMemorySpaceStorageProvider{ + storages: map[string]SpaceStorage{}, + } +} + +type InMemorySpaceStorageProvider struct { + storages map[string]SpaceStorage + sync.Mutex +} + +func (i *InMemorySpaceStorageProvider) Init(a *app.App) (err error) { + return nil +} + +func (i *InMemorySpaceStorageProvider) Name() (name string) { + return CName +} + +func (i *InMemorySpaceStorageProvider) WaitSpaceStorage(ctx context.Context, id string) (SpaceStorage, error) { + i.Lock() + defer i.Unlock() + storage, exists := i.storages[id] + if !exists { + return nil, ErrSpaceStorageMissing + } + return storage, nil +} + +func (i *InMemorySpaceStorageProvider) SpaceExists(id string) bool { + i.Lock() + defer i.Unlock() + _, exists := i.storages[id] + return exists +} + +func (i *InMemorySpaceStorageProvider) CreateSpaceStorage(payload SpaceStorageCreatePayload) (SpaceStorage, error) { + i.Lock() + defer i.Unlock() + spaceStorage, err := NewInMemorySpaceStorage(payload) + if err != nil { + return nil, err + } + i.storages[payload.SpaceHeaderWithId.Id] = spaceStorage + return spaceStorage, nil +} diff --git a/commonspace/spacestorage/inmemorystorage.go b/commonspace/spacestorage/inmemorystorage.go new file mode 100644 index 00000000..9f9be094 --- /dev/null +++ b/commonspace/spacestorage/inmemorystorage.go @@ -0,0 +1,153 @@ +package spacestorage + +import ( + "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto" + "github.com/anytypeio/any-sync/commonspace/object/acl/liststorage" + "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto" + "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage" + "github.com/anytypeio/any-sync/commonspace/spacesyncproto" + "sync" +) + +type InMemorySpaceStorage struct { + id string + isDeleted bool + spaceSettingsId string + treeDeleted map[string]string + trees map[string]treestorage.TreeStorage + aclStorage liststorage.ListStorage + spaceHeader *spacesyncproto.RawSpaceHeaderWithId + spaceHash string + sync.Mutex +} + +func NewInMemorySpaceStorage(payload SpaceStorageCreatePayload) (SpaceStorage, error) { + aclStorage, err := liststorage.NewInMemoryAclListStorage(payload.AclWithId.Id, []*aclrecordproto.RawAclRecordWithId{payload.AclWithId}) + if err != nil { + return nil, err + } + inMemory := &InMemorySpaceStorage{ + id: payload.SpaceHeaderWithId.Id, + spaceSettingsId: payload.SpaceSettingsWithId.Id, + treeDeleted: map[string]string{}, + trees: map[string]treestorage.TreeStorage{}, + aclStorage: aclStorage, + spaceHeader: payload.SpaceHeaderWithId, + } + _, err = inMemory.CreateTreeStorage(treestorage.TreeStorageCreatePayload{ + RootRawChange: payload.SpaceSettingsWithId, + Changes: []*treechangeproto.RawTreeChangeWithId{payload.SpaceSettingsWithId}, + Heads: []string{payload.SpaceSettingsWithId.Id}, + }) + if err != nil { + return nil, err + } + return inMemory, nil +} + +func (i *InMemorySpaceStorage) Id() string { + return i.id +} + +func (i *InMemorySpaceStorage) SetSpaceDeleted() error { + i.Lock() + defer i.Unlock() + i.isDeleted = true + return nil +} + +func (i *InMemorySpaceStorage) IsSpaceDeleted() (bool, error) { + i.Lock() + defer i.Unlock() + return i.isDeleted, nil +} + +func (i *InMemorySpaceStorage) SetTreeDeletedStatus(id, state string) error { + i.Lock() + defer i.Unlock() + i.treeDeleted[id] = state + return nil +} + +func (i *InMemorySpaceStorage) TreeDeletedStatus(id string) (string, error) { + i.Lock() + defer i.Unlock() + return i.treeDeleted[id], nil +} + +func (i *InMemorySpaceStorage) SpaceSettingsId() string { + return i.spaceSettingsId +} + +func (i *InMemorySpaceStorage) AclStorage() (liststorage.ListStorage, error) { + return i.aclStorage, nil +} + +func (i *InMemorySpaceStorage) SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error) { + return i.spaceHeader, nil +} + +func (i *InMemorySpaceStorage) StoredIds() ([]string, error) { + i.Lock() + defer i.Unlock() + var allIds []string + for id := range i.trees { + allIds = append(allIds, id) + } + return allIds, nil +} + +func (i *InMemorySpaceStorage) TreeRoot(id string) (*treechangeproto.RawTreeChangeWithId, error) { + i.Lock() + defer i.Unlock() + treeStorage, exists := i.trees[id] + if !exists { + return nil, treestorage.ErrUnknownTreeId + } + return treeStorage.Root() +} + +func (i *InMemorySpaceStorage) TreeStorage(id string) (treestorage.TreeStorage, error) { + i.Lock() + defer i.Unlock() + treeStorage, exists := i.trees[id] + if !exists { + return nil, treestorage.ErrUnknownTreeId + } + return treeStorage, nil +} + +func (i *InMemorySpaceStorage) HasTree(id string) (bool, error) { + i.Lock() + defer i.Unlock() + _, exists := i.trees[id] + return exists, nil +} + +func (i *InMemorySpaceStorage) CreateTreeStorage(payload treestorage.TreeStorageCreatePayload) (treestorage.TreeStorage, error) { + i.Lock() + defer i.Unlock() + storage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, payload.Heads, payload.Changes) + if err != nil { + return nil, err + } + i.trees[payload.RootRawChange.Id] = storage + return storage, nil +} + +func (i *InMemorySpaceStorage) WriteSpaceHash(hash string) error { + i.Lock() + defer i.Unlock() + i.spaceHash = hash + return nil +} + +func (i *InMemorySpaceStorage) ReadSpaceHash() (hash string, err error) { + i.Lock() + defer i.Unlock() + return i.spaceHash, nil +} + +func (i *InMemorySpaceStorage) Close() error { + return nil +} diff --git a/commonspace/spaceutils_test.go b/commonspace/spaceutils_test.go new file mode 100644 index 00000000..62403ad3 --- /dev/null +++ b/commonspace/spaceutils_test.go @@ -0,0 +1,317 @@ +package commonspace + +import ( + "context" + "fmt" + accountService "github.com/anytypeio/any-sync/accountservice" + "github.com/anytypeio/any-sync/app" + "github.com/anytypeio/any-sync/app/ocache" + "github.com/anytypeio/any-sync/commonspace/credentialprovider" + "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree" + "github.com/anytypeio/any-sync/commonspace/object/treemanager" + "github.com/anytypeio/any-sync/commonspace/peermanager" + "github.com/anytypeio/any-sync/commonspace/spacestorage" + "github.com/anytypeio/any-sync/commonspace/spacesyncproto" + "github.com/anytypeio/any-sync/net/peer" + "github.com/anytypeio/any-sync/net/pool" + "github.com/anytypeio/any-sync/nodeconf" + "github.com/anytypeio/any-sync/testutil/accounttest" + "github.com/anytypeio/go-chash" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +// +// Mock NodeConf implementation +// + +type mockConf struct { + id string + networkId string + configuration nodeconf.Configuration +} + +func (m *mockConf) Init(a *app.App) (err error) { + accountKeys := a.MustComponent(accountService.CName).(accountService.Service).Account() + networkId := accountKeys.SignKey.GetPublic().Network() + node := nodeconf.Node{ + PeerId: accountKeys.PeerId, + Addresses: []string{"127.0.0.1:4430"}, + Types: []nodeconf.NodeType{nodeconf.NodeTypeTree}, + } + m.id = networkId + m.networkId = networkId + m.configuration = nodeconf.Configuration{ + Id: networkId, + NetworkId: networkId, + Nodes: []nodeconf.Node{node}, + CreationTime: time.Now(), + } + return nil +} + +func (m *mockConf) Name() (name string) { + return nodeconf.CName +} + +func (m *mockConf) Run(ctx context.Context) (err error) { + return nil +} + +func (m *mockConf) Close(ctx context.Context) (err error) { + return nil +} + +func newMockConf() *mockConf { + return &mockConf{} +} + +func (m *mockConf) Id() string { + return m.id +} + +func (m *mockConf) Configuration() nodeconf.Configuration { + return m.configuration +} + +func (m *mockConf) NodeIds(spaceId string) []string { + var nodeIds []string + for _, node := range m.configuration.Nodes { + nodeIds = append(nodeIds, node.PeerId) + } + return nodeIds +} + +func (m *mockConf) IsResponsible(spaceId string) bool { + return true +} + +func (m *mockConf) FilePeers() []string { + return nil +} + +func (m *mockConf) ConsensusPeers() []string { + return nil +} + +func (m *mockConf) CoordinatorPeers() []string { + return nil +} + +func (m *mockConf) PeerAddresses(peerId string) (addrs []string, ok bool) { + if peerId == m.configuration.Nodes[0].PeerId { + return m.configuration.Nodes[0].Addresses, true + } + return nil, false +} + +func (m *mockConf) CHash() chash.CHash { + return nil +} + +func (m *mockConf) Partition(spaceId string) (part int) { + return 0 +} + +func (m *mockConf) NodeTypes(nodeId string) []nodeconf.NodeType { + if nodeId == m.configuration.Nodes[0].PeerId { + return m.configuration.Nodes[0].Types + } + return nil +} + +// +// Mock PeerManager implementation +// + +type mockPeerManager struct { +} + +func (p *mockPeerManager) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) { + return nil +} + +func (p *mockPeerManager) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) { + return nil +} + +func (p *mockPeerManager) GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error) { + return nil, nil +} + +// +// Mock PeerManagerProvider implementation +// + +type mockPeerManagerProvider struct { +} + +func (m *mockPeerManagerProvider) Init(a *app.App) (err error) { + return nil +} + +func (m *mockPeerManagerProvider) Name() (name string) { + return peermanager.CName +} + +func (m *mockPeerManagerProvider) NewPeerManager(ctx context.Context, spaceId string) (sm peermanager.PeerManager, err error) { + return &mockPeerManager{}, nil +} + +// +// Mock Pool implementation +// + +type mockPool struct { +} + +func (m *mockPool) Init(a *app.App) (err error) { + return nil +} + +func (m *mockPool) Name() (name string) { + return pool.CName +} + +func (m *mockPool) Get(ctx context.Context, id string) (peer.Peer, error) { + return nil, fmt.Errorf("no such peer") +} + +func (m *mockPool) Dial(ctx context.Context, id string) (peer.Peer, error) { + return nil, fmt.Errorf("can't dial peer") +} + +func (m *mockPool) GetOneOf(ctx context.Context, peerIds []string) (peer.Peer, error) { + return nil, fmt.Errorf("can't dial peer") +} + +func (m *mockPool) DialOneOf(ctx context.Context, peerIds []string) (peer.Peer, error) { + return nil, fmt.Errorf("can't dial peer") +} + +// +// Mock Config implementation +// + +type mockConfig struct { +} + +func (m *mockConfig) Init(a *app.App) (err error) { + return nil +} + +func (m *mockConfig) Name() (name string) { + return "config" +} + +func (m *mockConfig) GetSpace() Config { + return Config{ + GCTTL: 60, + SyncPeriod: 20, + KeepTreeDataInMemory: true, + } +} + +// +// Mock TreeManager implementation +// + +type mockTreeManager struct { + space Space + cache ocache.OCache +} + +func (t *mockTreeManager) Init(a *app.App) (err error) { + t.cache = ocache.New(func(ctx context.Context, id string) (value ocache.Object, err error) { + return t.space.BuildTree(ctx, id, BuildTreeOpts{}) + }, + ocache.WithGCPeriod(time.Minute), + ocache.WithTTL(time.Duration(60)*time.Second)) + return nil +} + +func (t *mockTreeManager) Name() (name string) { + return treemanager.CName +} + +func (t *mockTreeManager) Run(ctx context.Context) (err error) { + return nil +} + +func (t *mockTreeManager) Close(ctx context.Context) (err error) { + return t.cache.Close() +} + +func (t *mockTreeManager) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) { + val, err := t.cache.Get(ctx, treeId) + if err != nil { + return nil, err + } + return val.(objecttree.ObjectTree), nil +} + +func (t *mockTreeManager) DeleteTree(ctx context.Context, spaceId, treeId string) (err error) { + tr, err := t.GetTree(ctx, spaceId, treeId) + if err != nil { + return + } + err = tr.Delete() + if err != nil { + return + } + _, err = t.cache.Remove(ctx, treeId) + return nil +} + +// +// Space fixture +// + +type spaceFixture struct { + app *app.App + config *mockConfig + account accountService.Service + configurationService nodeconf.Service + storageProvider spacestorage.SpaceStorageProvider + peermanagerProvider peermanager.PeerManagerProvider + credentialProvider credentialprovider.CredentialProvider + treeManager *mockTreeManager + pool *mockPool + spaceService SpaceService + cancelFunc context.CancelFunc +} + +func newFixture(t *testing.T) *spaceFixture { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + fx := &spaceFixture{ + cancelFunc: cancel, + config: &mockConfig{}, + app: &app.App{}, + account: &accounttest.AccountTestService{}, + configurationService: newMockConf(), + storageProvider: spacestorage.NewInMemorySpaceStorageProvider(), + peermanagerProvider: &mockPeerManagerProvider{}, + treeManager: &mockTreeManager{}, + pool: &mockPool{}, + spaceService: New(), + } + fx.app.Register(fx.account). + Register(fx.config). + Register(fx.configurationService). + Register(fx.storageProvider). + Register(fx.peermanagerProvider). + Register(fx.treeManager). + Register(fx.pool). + Register(fx.spaceService) + err := fx.app.Start(ctx) + if err != nil { + fx.cancelFunc() + } + require.NoError(t, err) + return fx +} + +func TestSpace(t *testing.T) { + _ = newFixture(t) +}