// Copyright Earl Warren // Copyright Loïc Dachary // SPDX-License-Identifier: MIT package f3 import ( "context" "slices" "testing" "time" "code.forgejo.org/f3/gof3/v3/f3" filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options" tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository" "code.forgejo.org/f3/gof3/v3/id" "code.forgejo.org/f3/gof3/v3/kind" "code.forgejo.org/f3/gof3/v3/path" f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" "code.forgejo.org/f3/gof3/v3/tree/generic" tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type remapper func(path string) path.Path func ForgeCompliance(t *testing.T, name string) { testForge := tests_forge.GetFactory(name)() ctx := context.Background() forgeOptions := testForge.NewOptions(t) forgeTree := generic.GetFactory("f3")(ctx, forgeOptions) forgeTree.Trace("======= build fixture") fixtureTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t)) TreeBuildPartial(t, name, testForge.GetKindExceptions(), forgeOptions, fixtureTree) forgeTree.Trace("======= mirror fixture to forge") generic.TreeMirror(ctx, fixtureTree, forgeTree, generic.NewPathFromString(""), generic.NewMirrorOptions()) forgeTree.Trace("======= run compliance tests") remap := func(p string) path.Path { return generic.TreePathRemap(ctx, fixtureTree, generic.NewPathFromString(p), forgeTree) } kindToForgePath := make(map[kind.Kind]string, len(KindToFixturePath)) for kind, path := range KindToFixturePath { remappedPath := remap(path) if remappedPath.Empty() { forgeTree.Trace("%s was not mirrored, ignored", path) continue } kindToForgePath[kind] = remappedPath.String() } ComplianceKindTests(t, name, forgeTree.(f3_tree.TreeInterface), kindToForgePath, testForge.GetKindExceptions()) ComplianceNameTests(t, name, forgeTree.(f3_tree.TreeInterface), remap, kindToForgePath, testForge.GetNameExceptions()) if testForge.DeleteAfterCompliance() { TreeDelete(t, testForge.GetNonTestUsers(), forgeOptions, forgeTree) } } func ComplianceNameTests(t *testing.T, name string, tree f3_tree.TreeInterface, remap remapper, kindToForgePath map[kind.Kind]string, exceptions []string) { t.Helper() ctx := context.Background() if !slices.Contains(exceptions, tests_forge.ComplianceNameForkedPullRequest) { t.Run(tests_forge.ComplianceNameForkedPullRequest, func(t *testing.T) { kind := kind.Kind(f3_tree.KindPullRequests) p := kindToForgePath[kind] parent := tree.Find(generic.NewPathFromString(p)) require.NotEqualValues(t, generic.NilNode, parent, p) child := tree.Factory(ctx, tree.GetChildrenKind(kind)) childFormat := ComplianceForkedPullRequest(t, tree, remap, child.NewFormat().(*f3.PullRequest), parent.GetCurrentPath()) child.FromFormat(childFormat) tree.Trace("'Upsert' the new forked pull request in the parent and store it in the forge") child.SetParent(parent) child.Upsert(ctx) tree.Trace("'Delete' child forked pull request") child.Delete(ctx) }) } } func ComplianceForkedPullRequest(t *testing.T, tree f3_tree.TreeInterface, remap remapper, pullRequest *f3.PullRequest, parent path.Path) *f3.PullRequest { user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface)) projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject) repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs")) repositoryNode.Get(context.Background()) repositoryHelper := tests_repository.NewTestHelper(t, "", repositoryNode) mainRef := "master" mainSha := repositoryHelper.GetRepositorySha(mainRef) tree.Trace("create a feature branch in the /forge/users/20222/projects/99099 fork") forkedRepositoryPath := remap("/forge/users/20222/projects/99099/repositories/vcs") forkedRepositoryNode := tree.Find(forkedRepositoryPath) require.NotEqual(t, forkedRepositoryNode, generic.NilNode) forkedRepositoryHelper := tests_repository.NewTestHelper(t, "", forkedRepositoryNode) featureRef := "generatedforkfeature" forkedRepositoryHelper.InternalBranchRepositoryFeature(featureRef, featureRef+" content") featureSha := forkedRepositoryHelper.GetRepositorySha(featureRef) forkedRepositoryHelper.PushMirror() now := now() prCreated := tick(&now) prUpdated := tick(&now) pullRequest.PosterID = f3_tree.NewUserReference(user.GetID()) pullRequest.Title = featureRef + " pr title" pullRequest.Content = featureRef + " pr content" pullRequest.State = f3.PullRequestStateOpen pullRequest.IsLocked = false pullRequest.Created = prCreated pullRequest.Updated = prUpdated pullRequest.Closed = nil pullRequest.Merged = false pullRequest.MergedTime = nil pullRequest.MergeCommitSHA = "" pullRequest.Head = f3.PullRequestBranch{ Ref: featureRef, SHA: featureSha, Repository: f3.NewReference(forkedRepositoryPath.String()), } pullRequest.Base = f3.PullRequestBranch{ Ref: mainRef, SHA: mainSha, Repository: f3.NewReference("../../repository/vcs"), } return pullRequest } func ComplianceKindTests(t *testing.T, name string, tree f3_tree.TreeInterface, kindToForgePath map[kind.Kind]string, exceptions []kind.Kind) { t.Helper() exceptions = append(exceptions, f3_tree.KindRepositories) for _, kind := range KindWithFixturePath { path := kindToForgePath[kind] if !tree.IsContainer(kind) { continue } if slices.Contains(exceptions, kind) { continue } t.Run(string(kind), func(t *testing.T) { Compliance(t, name, tree, path, kind, GeneratorSetRandom, GeneratorModify) }) } } func Compliance(t *testing.T, name string, tree f3_tree.TreeInterface, p string, kind kind.Kind, generator GeneratorFunc, modificator ModificatorFunc) { t.Helper() ctx := context.Background() tree.Trace("%s", p) parent := tree.Find(generic.NewPathFromString(p)) require.NotEqualValues(t, generic.NilNode, parent, p) tree.Trace("create a new child in memory") child := tree.Factory(ctx, tree.GetChildrenKind(kind)) childFormat := generator(t, name, child.NewFormat(), parent.GetCurrentPath()) child.FromFormat(childFormat) if i := child.GetID(); i != id.NilID { tree.Trace("about to insert child %s", i) assert.EqualValues(t, generic.NilNode, parent.GetChild(child.GetID())) } else { tree.Trace("about to insert child with nil ID") } if child.GetDriver().IsNull() { t.Skip("no driver, skipping") } tree.Trace("'Upsert' the new child in the parent and store it in the forge") child.SetParent(parent) child.Upsert(ctx) tree.Trace("done inserting child '%s'", child.GetID()) before := child.ToFormat() require.EqualValues(t, before.GetID(), child.GetID().String()) tree.Trace("'Get' the child '%s' from the forge", child.GetID()) child.Get(ctx) after := child.ToFormat() tree.Trace("check the F3 representations Upsert & Get to/from the forge are equivalent") require.True(t, cmp.Equal(before, after), cmp.Diff(before, after)) tree.Trace("check the F3 representation FromFormat/ToFormat are identical") { saved := after a := childFormat.Clone() if tree.AllocateID() { a.SetID("123456") } child.FromFormat(a) b := child.ToFormat() require.True(t, cmp.Equal(a, b), cmp.Diff(a, b)) child.FromFormat(saved) } if childFormat.GetName() != childFormat.GetID() { tree.Trace("'GetIDFromName' %s %s", kind, childFormat.GetName()) id := parent.GetIDFromName(ctx, childFormat.GetName()) assert.EqualValues(t, child.GetID(), id) } for i, modified := range modificator(t, after, parent.GetCurrentPath()) { tree.Trace("%d: %s 'Upsert' a modified child %v", i, kind, modified) child.FromFormat(modified) child.Upsert(ctx) tree.Trace("%d: 'Get' the modified child '%s' from the forge", i, child.GetID()) child.Get(ctx) after = child.ToFormat() tree.Trace("%d: check the F3 representations Upsert & Get to/from the forge of the modified child are equivalent", i) require.True(t, cmp.Equal(modified, after), cmp.Diff(modified, after)) } nodeChildren := parent.GetNodeChildren() tree.Trace("'ListPage' and only 'Get' known %d children of %s", len(nodeChildren), parent.GetKind()) if len(nodeChildren) > 0 { parent.List(ctx) for _, child := range parent.GetChildren() { if _, ok := nodeChildren[child.GetID()]; ok { tree.Trace("'WalkAndGet' %s child %s %s", parent.GetCurrentPath().ReadableString(), child.GetKind(), child.GetID()) child.WalkAndGet(ctx, parent.GetCurrentPath(), generic.NewWalkOptions(nil)) } } } tree.Trace("'Delete' child '%s' from the forge", child.GetID()) child.Delete(ctx) assert.EqualValues(t, generic.NilNode, parent.GetChild(child.GetID())) assert.True(t, child.GetIsSync()) loop := 100 for i := 0; i < loop; i++ { child.SetIsSync(false) child.Get(ctx) if !child.GetIsSync() { break } tree.Trace("waiting for asynchronous child deletion (%d/%d)", i, loop) time.Sleep(5 * time.Second) } assert.False(t, child.GetIsSync()) tree.Trace("%s did something %s", kind, child) }