summaryrefslogtreecommitdiffstats
path: root/tests/integration/git_test.go
diff options
context:
space:
mode:
authorKyle D <kdumontnu@gmail.com>2022-09-02 15:18:23 -0400
committerGitHub <noreply@github.com>2022-09-02 15:18:23 -0400
commitc8ded77680db7344c8dc1ccee76bce0b4e02e103 (patch)
treebc63678ef62dc71ce68b29eeaf019c45cdb12034 /tests/integration/git_test.go
parent5710ff343c9f16119ddbff06044e5d61388baa22 (diff)
downloadgitea-c8ded77680db7344c8dc1ccee76bce0b4e02e103.tar.gz
gitea-c8ded77680db7344c8dc1ccee76bce0b4e02e103.zip
Kd/ci playwright go test (#20123)
* Add initial playwright config * Simplify Makefile * Simplify Makefile * Use correct config files * Update playwright settings * Fix package-lock file * Don't use test logger for e2e tests * fix frontend lint * Allow passing TEST_LOGGER variable * Init postgres database * use standard gitea env variables * Update playwright * update drone * Move empty env var to commands * Cleanup * Move integrations to subfolder * tests integrations to tests integraton * Run e2e tests with go test * Fix linting * install CI deps * Add files to ESlint * Fix drone typo * Don't log to console in CI * Use go test http server * Add build step before tests * Move shared init function to common package * fix drone * Clean up tests * Fix linting * Better mocking for page + version string * Cleanup test generation * Remove dependency on gitea binary * Fix linting * add initial support for running specific tests * Add ACCEPT_VISUAL variable * don't require git-lfs * Add initial documentation * Review feedback * Add logged in session test * Attempt fixing drone race * Cleanup and bump version * Bump deps * Review feedback * simplify installation * Fix ci * Update install docs
Diffstat (limited to 'tests/integration/git_test.go')
-rw-r--r--tests/integration/git_test.go851
1 files changed, 851 insertions, 0 deletions
diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go
new file mode 100644
index 0000000000..caeb5db8b3
--- /dev/null
+++ b/tests/integration/git_test.go
@@ -0,0 +1,851 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package integration
+
+import (
+ "encoding/hex"
+ "fmt"
+ "math/rand"
+ "net/http"
+ "net/url"
+ "os"
+ "path"
+ "path/filepath"
+ "strconv"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/models/db"
+ issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/perm"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/lfs"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const (
+ littleSize = 1024 // 1ko
+ bigSize = 128 * 1024 * 1024 // 128Mo
+)
+
+func TestGit(t *testing.T) {
+ onGiteaRun(t, testGit)
+}
+
+func testGit(t *testing.T, u *url.URL) {
+ username := "user2"
+ baseAPITestContext := NewAPITestContext(t, username, "repo1")
+
+ u.Path = baseAPITestContext.GitPath()
+
+ forkedUserCtx := NewAPITestContext(t, "user4", "repo1")
+
+ t.Run("HTTP", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ ensureAnonymousClone(t, u)
+ httpContext := baseAPITestContext
+ httpContext.Reponame = "repo-tmp-17"
+ forkedUserCtx.Reponame = httpContext.Reponame
+
+ dstPath, err := os.MkdirTemp("", httpContext.Reponame)
+ assert.NoError(t, err)
+ defer util.RemoveAll(dstPath)
+
+ t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false))
+ t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, perm.AccessModeRead))
+
+ t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username))
+
+ u.Path = httpContext.GitPath()
+ u.User = url.UserPassword(username, userPassword)
+
+ t.Run("Clone", doGitClone(dstPath, u))
+
+ dstPath2, err := os.MkdirTemp("", httpContext.Reponame)
+ assert.NoError(t, err)
+ defer util.RemoveAll(dstPath2)
+
+ t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
+
+ little, big := standardCommitAndPushTest(t, dstPath)
+ littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
+ rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
+ mediaTest(t, &httpContext, little, big, littleLFS, bigLFS)
+
+ t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "master", "test/head"))
+ t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
+ t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath))
+ t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge"))
+ t.Run("MergeFork", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ t.Run("CreatePRAndMerge", doMergeFork(httpContext, forkedUserCtx, "master", httpContext.Username+":master"))
+ rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
+ mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
+ })
+
+ t.Run("PushCreate", doPushCreate(httpContext, u))
+ })
+ t.Run("SSH", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ sshContext := baseAPITestContext
+ sshContext.Reponame = "repo-tmp-18"
+ keyname := "my-testing-key"
+ forkedUserCtx.Reponame = sshContext.Reponame
+ t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false))
+ t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, perm.AccessModeRead))
+ t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username))
+
+ // Setup key the user ssh key
+ withKeyFile(t, keyname, func(keyFile string) {
+ t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile))
+
+ // Setup remote link
+ // TODO: get url from api
+ sshURL := createSSHUrl(sshContext.GitPath(), u)
+
+ // Setup clone folder
+ dstPath, err := os.MkdirTemp("", sshContext.Reponame)
+ assert.NoError(t, err)
+ defer util.RemoveAll(dstPath)
+
+ t.Run("Clone", doGitClone(dstPath, sshURL))
+
+ little, big := standardCommitAndPushTest(t, dstPath)
+ littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
+ rawTest(t, &sshContext, little, big, littleLFS, bigLFS)
+ mediaTest(t, &sshContext, little, big, littleLFS, bigLFS)
+
+ t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "master", "test/head2"))
+ t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
+ t.Run("MergeFork", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ t.Run("CreatePRAndMerge", doMergeFork(sshContext, forkedUserCtx, "master", sshContext.Username+":master"))
+ rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
+ mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
+ })
+
+ t.Run("PushCreate", doPushCreate(sshContext, sshURL))
+ })
+ })
+}
+
+func ensureAnonymousClone(t *testing.T, u *url.URL) {
+ dstLocalPath, err := os.MkdirTemp("", "repo1")
+ assert.NoError(t, err)
+ defer util.RemoveAll(dstLocalPath)
+ t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
+}
+
+func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string) {
+ t.Run("Standard", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ little, big = commitAndPushTest(t, dstPath, "data-file-")
+ })
+ return little, big
+}
+
+func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) {
+ t.Run("LFS", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ prefix := "lfs-data-file-"
+ err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath})
+ assert.NoError(t, err)
+ _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track", prefix+"*").RunStdString(&git.RunOpts{Dir: dstPath})
+ assert.NoError(t, err)
+ err = git.AddChanges(dstPath, false, ".gitattributes")
+ assert.NoError(t, err)
+
+ err = git.CommitChangesWithArgs(dstPath, git.AllowLFSFiltersArgs(), git.CommitChangesOptions{
+ Committer: &git.Signature{
+ Email: "user2@example.com",
+ Name: "User Two",
+ When: time.Now(),
+ },
+ Author: &git.Signature{
+ Email: "user2@example.com",
+ Name: "User Two",
+ When: time.Now(),
+ },
+ Message: fmt.Sprintf("Testing commit @ %v", time.Now()),
+ })
+ assert.NoError(t, err)
+
+ littleLFS, bigLFS = commitAndPushTest(t, dstPath, prefix)
+
+ t.Run("Locks", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ lockTest(t, dstPath)
+ })
+ })
+ return littleLFS, bigLFS
+}
+
+func commitAndPushTest(t *testing.T, dstPath, prefix string) (little, big string) {
+ t.Run("PushCommit", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ t.Run("Little", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ little = doCommitAndPush(t, littleSize, dstPath, prefix)
+ })
+ t.Run("Big", func(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping test in short mode.")
+ return
+ }
+ defer tests.PrintCurrentTest(t)()
+ big = doCommitAndPush(t, bigSize, dstPath, prefix)
+ })
+ })
+ return little, big
+}
+
+func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) {
+ t.Run("Raw", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ username := ctx.Username
+ reponame := ctx.Reponame
+
+ session := loginUser(t, username)
+
+ // Request raw paths
+ req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little))
+ resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
+ assert.Equal(t, littleSize, resp.Length)
+
+ if setting.LFS.StartServer {
+ req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS))
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ assert.NotEqual(t, littleSize, resp.Body.Len())
+ assert.LessOrEqual(t, resp.Body.Len(), 1024)
+ if resp.Body.Len() != littleSize && resp.Body.Len() <= 1024 {
+ assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier)
+ }
+ }
+
+ if !testing.Short() {
+ req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big))
+ resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
+ assert.Equal(t, bigSize, resp.Length)
+
+ if setting.LFS.StartServer {
+ req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS))
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ assert.NotEqual(t, bigSize, resp.Body.Len())
+ if resp.Body.Len() != bigSize && resp.Body.Len() <= 1024 {
+ assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier)
+ }
+ }
+ }
+ })
+}
+
+func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) {
+ t.Run("Media", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ username := ctx.Username
+ reponame := ctx.Reponame
+
+ session := loginUser(t, username)
+
+ // Request media paths
+ req := NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", little))
+ resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
+ assert.Equal(t, littleSize, resp.Length)
+
+ req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS))
+ resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
+ assert.Equal(t, littleSize, resp.Length)
+
+ if !testing.Short() {
+ req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big))
+ resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
+ assert.Equal(t, bigSize, resp.Length)
+
+ if setting.LFS.StartServer {
+ req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS))
+ resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
+ assert.Equal(t, bigSize, resp.Length)
+ }
+ }
+ })
+}
+
+func lockTest(t *testing.T, repoPath string) {
+ lockFileTest(t, "README.md", repoPath)
+}
+
+func lockFileTest(t *testing.T, filename, repoPath string) {
+ _, _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath})
+ assert.NoError(t, err)
+ _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock", filename).RunStdString(&git.RunOpts{Dir: repoPath})
+ assert.NoError(t, err)
+ _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath})
+ assert.NoError(t, err)
+ _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock", filename).RunStdString(&git.RunOpts{Dir: repoPath})
+ assert.NoError(t, err)
+}
+
+func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string {
+ name, err := generateCommitWithNewData(size, repoPath, "user2@example.com", "User Two", prefix)
+ assert.NoError(t, err)
+ _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push
+ assert.NoError(t, err)
+ return name
+}
+
+func generateCommitWithNewData(size int, repoPath, email, fullName, prefix string) (string, error) {
+ // Generate random file
+ bufSize := 4 * 1024
+ if bufSize > size {
+ bufSize = size
+ }
+
+ buffer := make([]byte, bufSize)
+
+ tmpFile, err := os.CreateTemp(repoPath, prefix)
+ if err != nil {
+ return "", err
+ }
+ defer tmpFile.Close()
+ written := 0
+ for written < size {
+ n := size - written
+ if n > bufSize {
+ n = bufSize
+ }
+ _, err := rand.Read(buffer[:n])
+ if err != nil {
+ return "", err
+ }
+ n, err = tmpFile.Write(buffer[:n])
+ if err != nil {
+ return "", err
+ }
+ written += n
+ }
+ if err != nil {
+ return "", err
+ }
+
+ // Commit
+ // Now here we should explicitly allow lfs filters to run
+ globalArgs := git.AllowLFSFiltersArgs()
+ err = git.AddChangesWithArgs(repoPath, globalArgs, false, filepath.Base(tmpFile.Name()))
+ if err != nil {
+ return "", err
+ }
+ err = git.CommitChangesWithArgs(repoPath, globalArgs, git.CommitChangesOptions{
+ Committer: &git.Signature{
+ Email: email,
+ Name: fullName,
+ When: time.Now(),
+ },
+ Author: &git.Signature{
+ Email: email,
+ Name: fullName,
+ When: time.Now(),
+ },
+ Message: fmt.Sprintf("Testing commit @ %v", time.Now()),
+ })
+ return filepath.Base(tmpFile.Name()), err
+}
+
+func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
+ return func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "protected"))
+ t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected"))
+
+ ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame)
+ t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", ""))
+ t.Run("GenerateCommit", func(t *testing.T) {
+ _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
+ assert.NoError(t, err)
+ })
+ t.Run("FailToPushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "origin", "protected"))
+ t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected"))
+ var pr api.PullRequest
+ var err error
+ t.Run("CreatePullRequest", func(t *testing.T) {
+ pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected")(t)
+ assert.NoError(t, err)
+ })
+ t.Run("GenerateCommit", func(t *testing.T) {
+ _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
+ assert.NoError(t, err)
+ })
+ t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected-2"))
+ var pr2 api.PullRequest
+ t.Run("CreatePullRequest", func(t *testing.T) {
+ pr2, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "unprotected", "unprotected-2")(t)
+ assert.NoError(t, err)
+ })
+ t.Run("MergePR2", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr2.Index))
+ t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
+ t.Run("PullProtected", doGitPull(dstPath, "origin", "protected"))
+
+ t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "unprotected-file-*"))
+ t.Run("GenerateCommit", func(t *testing.T) {
+ _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "unprotected-file-")
+ assert.NoError(t, err)
+ })
+ t.Run("PushUnprotectedFilesToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected"))
+
+ t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, ""))
+
+ t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master"))
+ t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce"))
+ t.Run("GenerateCommit", func(t *testing.T) {
+ _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
+ assert.NoError(t, err)
+ })
+ t.Run("FailToForcePushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected"))
+ t.Run("MergeProtectedToToforce", doGitMerge(dstPath, "protected"))
+ t.Run("PushToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "toforce:protected"))
+ t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master"))
+ }
+}
+
+func doProtectBranch(ctx APITestContext, branch, userToWhitelist, unprotectedFilePatterns string) func(t *testing.T) {
+ // We are going to just use the owner to set the protection.
+ return func(t *testing.T) {
+ csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings/branches", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)))
+
+ if userToWhitelist == "" {
+ // Change branch to protected
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{
+ "_csrf": csrf,
+ "protected": "on",
+ "unprotected_file_patterns": unprotectedFilePatterns,
+ })
+ ctx.Session.MakeRequest(t, req, http.StatusSeeOther)
+ } else {
+ user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelist)
+ assert.NoError(t, err)
+ // Change branch to protected
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{
+ "_csrf": csrf,
+ "protected": "on",
+ "enable_push": "whitelist",
+ "enable_whitelist": "on",
+ "whitelist_users": strconv.FormatInt(user.ID, 10),
+ "unprotected_file_patterns": unprotectedFilePatterns,
+ })
+ ctx.Session.MakeRequest(t, req, http.StatusSeeOther)
+ }
+ // Check if master branch has been locked successfully
+ flashCookie := ctx.Session.GetCookie("macaron_flash")
+ assert.NotNil(t, flashCookie)
+ assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
+ }
+}
+
+func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) {
+ return func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ var pr api.PullRequest
+ var err error
+
+ // Create a test pullrequest
+ t.Run("CreatePullRequest", func(t *testing.T) {
+ pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t)
+ assert.NoError(t, err)
+ })
+
+ // Ensure the PR page works
+ t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
+
+ // Then get the diff string
+ var diffHash string
+ var diffLength int
+ t.Run("GetDiff", func(t *testing.T) {
+ req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index))
+ resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
+ diffHash = string(resp.Hash.Sum(nil))
+ diffLength = resp.Length
+ })
+
+ // Now: Merge the PR & make sure that doesn't break the PR page or change its diff
+ t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
+ t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
+ t.Run("CheckPR", func(t *testing.T) {
+ oldMergeBase := pr.MergeBase
+ pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
+ assert.NoError(t, err)
+ assert.Equal(t, oldMergeBase, pr2.MergeBase)
+ })
+ t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
+
+ // Then: Delete the head branch & make sure that doesn't break the PR page or change its diff
+ t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch))
+ t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
+ t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
+
+ // Delete the head repository & make sure that doesn't break the PR page or change its diff
+ t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx))
+ t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
+ t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
+ }
+}
+
+func doCreatePRAndSetManuallyMerged(ctx, baseCtx APITestContext, dstPath, baseBranch, headBranch string) func(t *testing.T) {
+ return func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ var (
+ pr api.PullRequest
+ err error
+ lastCommitID string
+ )
+
+ trueBool := true
+ falseBool := false
+
+ t.Run("AllowSetManuallyMergedAndSwitchOffAutodetectManualMerge", doAPIEditRepository(baseCtx, &api.EditRepoOption{
+ HasPullRequests: &trueBool,
+ AllowManualMerge: &trueBool,
+ AutodetectManualMerge: &falseBool,
+ }))
+
+ t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch))
+ t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch))
+ t.Run("CreateEmptyPullRequest", func(t *testing.T) {
+ pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t)
+ assert.NoError(t, err)
+ })
+ lastCommitID = pr.Base.Sha
+ t.Run("ManuallyMergePR", doAPIManuallyMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, lastCommitID, pr.Index))
+ }
+}
+
+func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.T) {
+ return func(t *testing.T) {
+ req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
+ ctx.Session.MakeRequest(t, req, http.StatusOK)
+ req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/files", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
+ ctx.Session.MakeRequest(t, req, http.StatusOK)
+ req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
+ ctx.Session.MakeRequest(t, req, http.StatusOK)
+ }
+}
+
+func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) {
+ return func(t *testing.T) {
+ req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
+ resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
+ actual := string(resp.Hash.Sum(nil))
+ actualLength := resp.Length
+
+ equal := diffHash == actual
+ assert.True(t, equal, "Unexpected change in the diff string: expected hash: %s size: %d but was actually: %s size: %d", hex.EncodeToString([]byte(diffHash)), diffLength, hex.EncodeToString([]byte(actual)), actualLength)
+ }
+}
+
+func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) {
+ return func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ // create a context for a currently non-existent repository
+ ctx.Reponame = fmt.Sprintf("repo-tmp-push-create-%s", u.Scheme)
+ u.Path = ctx.GitPath()
+
+ // Create a temporary directory
+ tmpDir, err := os.MkdirTemp("", ctx.Reponame)
+ assert.NoError(t, err)
+ defer util.RemoveAll(tmpDir)
+
+ // Now create local repository to push as our test and set its origin
+ t.Run("InitTestRepository", doGitInitTestRepository(tmpDir))
+ t.Run("AddRemote", doGitAddRemote(tmpDir, "origin", u))
+
+ // Disable "Push To Create" and attempt to push
+ setting.Repository.EnablePushCreateUser = false
+ t.Run("FailToPushAndCreateTestRepository", doGitPushTestRepositoryFail(tmpDir, "origin", "master"))
+
+ // Enable "Push To Create"
+ setting.Repository.EnablePushCreateUser = true
+
+ // Assert that cloning from a non-existent repository does not create it and that it definitely wasn't create above
+ t.Run("FailToCloneFromNonExistentRepository", doGitCloneFail(u))
+
+ // Then "Push To Create"x
+ t.Run("SuccessfullyPushAndCreateTestRepository", doGitPushTestRepository(tmpDir, "origin", "master"))
+
+ // Finally, fetch repo from database and ensure the correct repository has been created
+ repo, err := repo_model.GetRepositoryByOwnerAndName(ctx.Username, ctx.Reponame)
+ assert.NoError(t, err)
+ assert.False(t, repo.IsEmpty)
+ assert.True(t, repo.IsPrivate)
+
+ // Now add a remote that is invalid to "Push To Create"
+ invalidCtx := ctx
+ invalidCtx.Reponame = fmt.Sprintf("invalid/repo-tmp-push-create-%s", u.Scheme)
+ u.Path = invalidCtx.GitPath()
+ t.Run("AddInvalidRemote", doGitAddRemote(tmpDir, "invalid", u))
+
+ // Fail to "Push To Create" the invalid
+ t.Run("FailToPushAndCreateInvalidTestRepository", doGitPushTestRepositoryFail(tmpDir, "invalid", "master"))
+ }
+}
+
+func doBranchDelete(ctx APITestContext, owner, repo, branch string) func(*testing.T) {
+ return func(t *testing.T) {
+ csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/branches", url.PathEscape(owner), url.PathEscape(repo)))
+
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)), map[string]string{
+ "_csrf": csrf,
+ })
+ ctx.Session.MakeRequest(t, req, http.StatusOK)
+ }
+}
+
+func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
+ return func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame)
+
+ t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected"))
+ t.Run("PullProtected", doGitPull(dstPath, "origin", "protected"))
+ t.Run("GenerateCommit", func(t *testing.T) {
+ _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
+ assert.NoError(t, err)
+ })
+ t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected3"))
+ var pr api.PullRequest
+ var err error
+ t.Run("CreatePullRequest", func(t *testing.T) {
+ pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected3")(t)
+ assert.NoError(t, err)
+ })
+
+ // Request repository commits page
+ req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", baseCtx.Username, baseCtx.Reponame, pr.Index))
+ resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+
+ // Get first commit URL
+ commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
+ assert.True(t, exists)
+ assert.NotEmpty(t, commitURL)
+
+ commitID := path.Base(commitURL)
+
+ // Call API to add Pending status for commit
+ t.Run("CreateStatus", doAPICreateCommitStatus(ctx, commitID, api.CommitStatusPending))
+
+ // Cancel not existing auto merge
+ ctx.ExpectedCode = http.StatusNotFound
+ t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
+
+ // Add auto merge request
+ ctx.ExpectedCode = http.StatusCreated
+ t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
+
+ // Can not create schedule twice
+ ctx.ExpectedCode = http.StatusConflict
+ t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
+
+ // Cancel auto merge request
+ ctx.ExpectedCode = http.StatusNoContent
+ t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
+
+ // Add auto merge request
+ ctx.ExpectedCode = http.StatusCreated
+ t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
+
+ // Check pr status
+ ctx.ExpectedCode = 0
+ pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
+ assert.NoError(t, err)
+ assert.False(t, pr.HasMerged)
+
+ // Call API to add Failure status for commit
+ t.Run("CreateStatus", doAPICreateCommitStatus(ctx, commitID, api.CommitStatusFailure))
+
+ // Check pr status
+ pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
+ assert.NoError(t, err)
+ assert.False(t, pr.HasMerged)
+
+ // Call API to add Success status for commit
+ t.Run("CreateStatus", doAPICreateCommitStatus(ctx, commitID, api.CommitStatusSuccess))
+
+ // wait to let gitea merge stuff
+ time.Sleep(time.Second)
+
+ // test pr status
+ pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
+ assert.NoError(t, err)
+ assert.True(t, pr.HasMerged)
+ }
+}
+
+func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) {
+ return func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ // skip this test if git version is low
+ if git.CheckGitVersionAtLeast("2.29") != nil {
+ return
+ }
+
+ gitRepo, err := git.OpenRepository(git.DefaultContext, dstPath)
+ if !assert.NoError(t, err) {
+ return
+ }
+ defer gitRepo.Close()
+
+ var (
+ pr1, pr2 *issues_model.PullRequest
+ commit string
+ )
+ repo, err := repo_model.GetRepositoryByOwnerAndName(ctx.Username, ctx.Reponame)
+ if !assert.NoError(t, err) {
+ return
+ }
+
+ pullNum := unittest.GetCount(t, &issues_model.PullRequest{})
+
+ t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch))
+
+ t.Run("AddCommit", func(t *testing.T) {
+ err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
+ if !assert.NoError(t, err) {
+ return
+ }
+
+ err = git.AddChanges(dstPath, true)
+ assert.NoError(t, err)
+
+ err = git.CommitChanges(dstPath, git.CommitChangesOptions{
+ Committer: &git.Signature{
+ Email: "user2@example.com",
+ Name: "user2",
+ When: time.Now(),
+ },
+ Author: &git.Signature{
+ Email: "user2@example.com",
+ Name: "user2",
+ When: time.Now(),
+ },
+ Message: "Testing commit 1",
+ })
+ assert.NoError(t, err)
+ commit, err = gitRepo.GetRefCommitID("HEAD")
+ assert.NoError(t, err)
+ })
+
+ t.Run("Push", func(t *testing.T) {
+ err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).Run(&git.RunOpts{Dir: dstPath})
+ if !assert.NoError(t, err) {
+ return
+ }
+ unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1)
+ pr1 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
+ HeadRepoID: repo.ID,
+ Flow: issues_model.PullRequestFlowAGit,
+ })
+ if !assert.NotEmpty(t, pr1) {
+ return
+ }
+ prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
+ if !assert.NoError(t, err) {
+ return
+ }
+ assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch)
+ assert.Equal(t, false, prMsg.HasMerged)
+ assert.Contains(t, "Testing commit 1", prMsg.Body)
+ assert.Equal(t, commit, prMsg.Head.Sha)
+
+ _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunStdString(&git.RunOpts{Dir: dstPath})
+ if !assert.NoError(t, err) {
+ return
+ }
+ unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
+ pr2 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
+ HeadRepoID: repo.ID,
+ Index: pr1.Index + 1,
+ Flow: issues_model.PullRequestFlowAGit,
+ })
+ if !assert.NotEmpty(t, pr2) {
+ return
+ }
+ prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t)
+ if !assert.NoError(t, err) {
+ return
+ }
+ assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch)
+ assert.Equal(t, false, prMsg.HasMerged)
+ })
+
+ if pr1 == nil || pr2 == nil {
+ return
+ }
+
+ t.Run("AddCommit2", func(t *testing.T) {
+ err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0o666)
+ if !assert.NoError(t, err) {
+ return
+ }
+
+ err = git.AddChanges(dstPath, true)
+ assert.NoError(t, err)
+
+ err = git.CommitChanges(dstPath, git.CommitChangesOptions{
+ Committer: &git.Signature{
+ Email: "user2@example.com",
+ Name: "user2",
+ When: time.Now(),
+ },
+ Author: &git.Signature{
+ Email: "user2@example.com",
+ Name: "user2",
+ When: time.Now(),
+ },
+ Message: "Testing commit 2",
+ })
+ assert.NoError(t, err)
+ commit, err = gitRepo.GetRefCommitID("HEAD")
+ assert.NoError(t, err)
+ })
+
+ t.Run("Push2", func(t *testing.T) {
+ err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).Run(&git.RunOpts{Dir: dstPath})
+ if !assert.NoError(t, err) {
+ return
+ }
+ unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
+ prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
+ if !assert.NoError(t, err) {
+ return
+ }
+ assert.Equal(t, false, prMsg.HasMerged)
+ assert.Equal(t, commit, prMsg.Head.Sha)
+
+ _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunStdString(&git.RunOpts{Dir: dstPath})
+ if !assert.NoError(t, err) {
+ return
+ }
+ unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
+ prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t)
+ if !assert.NoError(t, err) {
+ return
+ }
+ assert.Equal(t, false, prMsg.HasMerged)
+ assert.Equal(t, commit, prMsg.Head.Sha)
+ })
+ t.Run("Merge", doAPIMergePullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index))
+ t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master"))
+ }
+}