package objecttree import ( "context" "github.com/anytypeio/any-sync/commonspace/object/acl/list" "github.com/anytypeio/any-sync/commonspace/object/acl/testutils/acllistbuilder" "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto" "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" ) type mockChangeCreator struct{} func (c *mockChangeCreator) createRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId { aclChange := &treechangeproto.RootChange{ AclHeadId: aclId, } res, _ := aclChange.Marshal() raw := &treechangeproto.RawTreeChange{ Payload: res, Signature: nil, } rawMarshalled, _ := raw.Marshal() return &treechangeproto.RawTreeChangeWithId{ RawChange: rawMarshalled, Id: id, } } func (c *mockChangeCreator) createRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId { aclChange := &treechangeproto.TreeChange{ TreeHeadIds: prevIds, AclHeadId: aclId, SnapshotBaseId: snapshotId, ChangesData: nil, IsSnapshot: isSnapshot, } res, _ := aclChange.Marshal() raw := &treechangeproto.RawTreeChange{ Payload: res, Signature: nil, } rawMarshalled, _ := raw.Marshal() return &treechangeproto.RawTreeChangeWithId{ RawChange: rawMarshalled, Id: id, } } func (c *mockChangeCreator) createNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage { root := c.createRoot(treeId, aclHeadId) treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root}) return treeStorage } type mockChangeBuilder struct { originalBuilder ChangeBuilder } func (c *mockChangeBuilder) BuildInitialContent(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) { panic("implement me") } func (c *mockChangeBuilder) SetRootRawChange(rawIdChange *treechangeproto.RawTreeChangeWithId) { c.originalBuilder.SetRootRawChange(rawIdChange) } func (c *mockChangeBuilder) ConvertFromRaw(rawChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) { return c.originalBuilder.ConvertFromRaw(rawChange, false) } func (c *mockChangeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) { panic("implement me") } func (c *mockChangeBuilder) BuildRaw(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) { return c.originalBuilder.BuildRaw(ch) } type mockChangeValidator struct{} func (m *mockChangeValidator) ValidateNewChanges(tree *Tree, aclList list.AclList, newChanges []*Change) error { return nil } func (m *mockChangeValidator) ValidateFullTree(tree *Tree, aclList list.AclList) error { return nil } type testTreeContext struct { aclList list.AclList treeStorage treestorage.TreeStorage changeBuilder *mockChangeBuilder changeCreator *mockChangeCreator objTree ObjectTree } func prepareAclList(t *testing.T) list.AclList { st, err := acllistbuilder.NewListStorageWithTestName("userjoinexample.yml") require.NoError(t, err, "building storage should not result in error") aclList, err := list.BuildAclList(st) require.NoError(t, err, "building acl list should be without error") return aclList } func prepareTreeContext(t *testing.T, aclList list.AclList) testTreeContext { changeCreator := &mockChangeCreator{} treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id) root, _ := treeStorage.Root() changeBuilder := &mockChangeBuilder{ originalBuilder: NewChangeBuilder(nil, root), } deps := objectTreeDeps{ changeBuilder: changeBuilder, treeBuilder: newTreeBuilder(treeStorage, changeBuilder), treeStorage: treeStorage, rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder), validator: &mockChangeValidator{}, aclList: aclList, } // check build objTree, err := buildObjectTree(deps) require.NoError(t, err, "building tree should be without error") // check tree iterate var iterChangesId []string err = objTree.IterateRoot(nil, func(change *Change) bool { iterChangesId = append(iterChangesId, change.Id) return true }) require.NoError(t, err, "iterate should be without error") assert.Equal(t, []string{"0"}, iterChangesId) return testTreeContext{ aclList: aclList, treeStorage: treeStorage, changeBuilder: changeBuilder, changeCreator: changeCreator, objTree: objTree, } } func TestObjectTree(t *testing.T) { aclList := prepareAclList(t) t.Run("add simple", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) treeStorage := ctx.treeStorage changeCreator := ctx.changeCreator objTree := ctx.objTree rawChanges := []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"), changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"), } payload := RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } res, err := objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") // check result assert.Equal(t, []string{"0"}, res.OldHeads) assert.Equal(t, []string{"2"}, res.Heads) assert.Equal(t, len(rawChanges), len(res.Added)) assert.Equal(t, Append, res.Mode) // check tree heads assert.Equal(t, []string{"2"}, objTree.Heads()) // check tree iterate var iterChangesId []string err = objTree.IterateRoot(nil, func(change *Change) bool { iterChangesId = append(iterChangesId, change.Id) return true }) require.NoError(t, err, "iterate should be without error") assert.Equal(t, []string{"0", "1", "2"}, iterChangesId) // check storage heads, _ := treeStorage.Heads() assert.Equal(t, []string{"2"}, heads) for _, ch := range rawChanges { raw, err := treeStorage.GetRawChange(context.Background(), ch.Id) assert.NoError(t, err, "storage should have all the changes") assert.Equal(t, ch, raw, "the changes in the storage should be the same") } }) t.Run("add no new changes", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) changeCreator := ctx.changeCreator objTree := ctx.objTree rawChanges := []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("0", aclList.Head().Id, "", true, ""), } payload := RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } res, err := objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") // check result assert.Equal(t, []string{"0"}, res.OldHeads) assert.Equal(t, []string{"0"}, res.Heads) assert.Equal(t, 0, len(res.Added)) // check tree heads assert.Equal(t, []string{"0"}, objTree.Heads()) }) t.Run("add unattachable changes", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) changeCreator := ctx.changeCreator objTree := ctx.objTree rawChanges := []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"), } payload := RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } res, err := objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") // check result assert.Equal(t, []string{"0"}, res.OldHeads) assert.Equal(t, []string{"0"}, res.Heads) assert.Equal(t, 0, len(res.Added)) assert.Equal(t, Nothing, res.Mode) // check tree heads assert.Equal(t, []string{"0"}, objTree.Heads()) }) t.Run("add new snapshot simple", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) treeStorage := ctx.treeStorage changeCreator := ctx.changeCreator objTree := ctx.objTree rawChanges := []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"), changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"), changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"), changeCreator.createRaw("4", aclList.Head().Id, "3", false, "3"), } payload := RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } res, err := objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") // check result assert.Equal(t, []string{"0"}, res.OldHeads) assert.Equal(t, []string{"4"}, res.Heads) assert.Equal(t, len(rawChanges), len(res.Added)) // here we have rebuild, because we reduced tree to new snapshot assert.Equal(t, Rebuild, res.Mode) // check tree heads assert.Equal(t, []string{"4"}, objTree.Heads()) // check tree iterate var iterChangesId []string err = objTree.IterateRoot(nil, func(change *Change) bool { iterChangesId = append(iterChangesId, change.Id) return true }) require.NoError(t, err, "iterate should be without error") assert.Equal(t, []string{"3", "4"}, iterChangesId) assert.Equal(t, "3", objTree.Root().Id) // check storage heads, _ := treeStorage.Heads() assert.Equal(t, []string{"4"}, heads) for _, ch := range rawChanges { raw, err := treeStorage.GetRawChange(context.Background(), ch.Id) assert.NoError(t, err, "storage should have all the changes") assert.Equal(t, ch, raw, "the changes in the storage should be the same") } }) t.Run("snapshot path", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) changeCreator := ctx.changeCreator objTree := ctx.objTree rawChanges := []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"), changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"), changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"), } payload := RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } _, err := objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") snapshotPath := objTree.SnapshotPath() assert.Equal(t, []string{"3", "0"}, snapshotPath) assert.Equal(t, true, objTree.(*objectTree).snapshotPathIsActual()) }) t.Run("changes from tree after common snapshot complex", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) changeCreator := ctx.changeCreator objTree := ctx.objTree rawChanges := []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"), changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"), changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"), changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"), changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"), changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"), } payload := RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } _, err := objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") require.Equal(t, "0", objTree.Root().Id) t.Run("all changes from tree", func(t *testing.T) { changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{}) require.NoError(t, err, "changes after common snapshot should be without error") changeIds := make(map[string]struct{}) for _, ch := range changes { changeIds[ch.Id] = struct{}{} } for _, raw := range rawChanges { _, ok := changeIds[raw.Id] assert.Equal(t, true, ok) } _, ok := changeIds["0"] assert.Equal(t, true, ok) }) t.Run("changes from tree after 1", func(t *testing.T) { changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{"1"}) require.NoError(t, err, "changes after common snapshot should be without error") changeIds := make(map[string]struct{}) for _, ch := range changes { changeIds[ch.Id] = struct{}{} } for _, id := range []string{"2", "3", "4", "5", "6"} { _, ok := changeIds[id] assert.Equal(t, true, ok) } for _, id := range []string{"0", "1"} { _, ok := changeIds[id] assert.Equal(t, false, ok) } }) t.Run("changes from tree after 5", func(t *testing.T) { changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{"5"}) require.NoError(t, err, "changes after common snapshot should be without error") changeIds := make(map[string]struct{}) for _, ch := range changes { changeIds[ch.Id] = struct{}{} } for _, id := range []string{"2", "3", "4", "6"} { _, ok := changeIds[id] assert.Equal(t, true, ok) } for _, id := range []string{"0", "1", "5"} { _, ok := changeIds[id] assert.Equal(t, false, ok) } }) }) t.Run("changes after common snapshot db complex", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) changeCreator := ctx.changeCreator objTree := ctx.objTree rawChanges := []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"), changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"), changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"), changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"), changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"), // main difference from tree example changeCreator.createRaw("6", aclList.Head().Id, "0", true, "3", "4", "5"), } payload := RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } _, err := objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") require.Equal(t, "6", objTree.Root().Id) t.Run("all changes from db", func(t *testing.T) { changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{}) require.NoError(t, err, "changes after common snapshot should be without error") changeIds := make(map[string]struct{}) for _, ch := range changes { changeIds[ch.Id] = struct{}{} } for _, raw := range rawChanges { _, ok := changeIds[raw.Id] assert.Equal(t, true, ok) } _, ok := changeIds["0"] assert.Equal(t, true, ok) }) t.Run("changes from tree db 1", func(t *testing.T) { changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{"1"}) require.NoError(t, err, "changes after common snapshot should be without error") changeIds := make(map[string]struct{}) for _, ch := range changes { changeIds[ch.Id] = struct{}{} } for _, id := range []string{"2", "3", "4", "5", "6"} { _, ok := changeIds[id] assert.Equal(t, true, ok) } for _, id := range []string{"0", "1"} { _, ok := changeIds[id] assert.Equal(t, false, ok) } }) t.Run("changes from tree db 5", func(t *testing.T) { changes, err := objTree.ChangesAfterCommonSnapshot([]string{"3", "0"}, []string{"5"}) require.NoError(t, err, "changes after common snapshot should be without error") changeIds := make(map[string]struct{}) for _, ch := range changes { changeIds[ch.Id] = struct{}{} } for _, id := range []string{"2", "3", "4", "6"} { _, ok := changeIds[id] assert.Equal(t, true, ok) } for _, id := range []string{"0", "1", "5"} { _, ok := changeIds[id] assert.Equal(t, false, ok) } }) }) t.Run("add new changes related to previous snapshot", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) treeStorage := ctx.treeStorage changeCreator := ctx.changeCreator objTree := ctx.objTree rawChanges := []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"), changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"), changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"), } payload := RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } res, err := objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") require.Equal(t, "3", objTree.Root().Id) rawChanges = []*treechangeproto.RawTreeChangeWithId{ changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"), changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"), changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"), } payload = RawChangesPayload{ NewHeads: []string{rawChanges[len(rawChanges)-1].Id}, RawChanges: rawChanges, } res, err = objTree.AddRawChanges(context.Background(), payload) require.NoError(t, err, "adding changes should be without error") // check result assert.Equal(t, []string{"3"}, res.OldHeads) assert.Equal(t, []string{"6"}, res.Heads) assert.Equal(t, len(rawChanges), len(res.Added)) assert.Equal(t, Rebuild, res.Mode) // check tree heads assert.Equal(t, []string{"6"}, objTree.Heads()) // check tree iterate var iterChangesId []string err = objTree.IterateRoot(nil, func(change *Change) bool { iterChangesId = append(iterChangesId, change.Id) return true }) require.NoError(t, err, "iterate should be without error") assert.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6"}, iterChangesId) assert.Equal(t, "0", objTree.Root().Id) // check storage heads, _ := treeStorage.Heads() assert.Equal(t, []string{"6"}, heads) for _, ch := range rawChanges { raw, err := treeStorage.GetRawChange(context.Background(), ch.Id) assert.NoError(t, err, "storage should have all the changes") assert.Equal(t, ch, raw, "the changes in the storage should be the same") } }) }