gitea/tests/integration/git_general_test.go
wxiaoguang ecbb03dc6d
Improve testing and try to fix MySQL hanging (#32515)
By some CI fine tunes (`run tests`), SQLite & MSSQL could complete
in about 12~13 minutes (before > 14), MySQL could complete in 18 minutes
(before: about 23 or even > 30)

Major changes:

1. use tmpfs for MySQL storage
1. run `make test-mysql` instead of `make integration-test-coverage`
because the code coverage is not really used at the moment.
1. refactor testlogger to make it more reliable and be able to report
stuck stacktrace
1. do not requeue failed items when a queue is being flushed (failed
items would keep failing and make flush uncompleted)
1. reduce the file sizes for testing
1. use math ChaCha20 random data instead of crypot/rand (for testing
purpose only)
1. no need to `DeleteRepository` in `TestLinguist`
1. other related refactoring to make code easier to maintain
2024-11-15 23:45:07 +08:00

877 lines
34 KiB
Go

// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"encoding/hex"
"fmt"
"io"
mathRand "math/rand/v2"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strconv"
"testing"
"time"
auth_model "code.gitea.io/gitea/models/auth"
"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"
gitea_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
const (
testFileSizeSmall = 10
testFileSizeLarge = 10 * 1024 * 1024 // 10M
)
func TestGitGeneral(t *testing.T) {
onGiteaRun(t, testGitGeneral)
}
func testGitGeneral(t *testing.T, u *url.URL) {
username := "user2"
baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
u.Path = baseAPITestContext.GitPath()
forkedUserCtx := NewAPITestContext(t, "user4", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
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 := t.TempDir()
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 := t.TempDir()
t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
pushedFilesStandard := standardCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge)
pushedFilesLFS := lfsCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge)
rawTest(t, &httpContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
mediaTest(t, &httpContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "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, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
mediaTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
})
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 := t.TempDir()
t.Run("Clone", doGitClone(dstPath, sshURL))
pushedFilesStandard := standardCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge)
pushedFilesLFS := lfsCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge)
rawTest(t, &sshContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
mediaTest(t, &sshContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "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, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
mediaTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
})
t.Run("PushCreate", doPushCreate(sshContext, sshURL))
})
})
}
func ensureAnonymousClone(t *testing.T, u *url.URL) {
dstLocalPath := t.TempDir()
t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
}
func standardCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFiles []string) {
t.Run("CommitAndPushStandard", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
pushedFiles = commitAndPushTest(t, dstPath, "data-file-", sizes...)
})
return pushedFiles
}
func lfsCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFiles []string) {
t.Run("CommitAndPushLFS", 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").AddDynamicArguments(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)
pushedFiles = commitAndPushTest(t, dstPath, prefix, sizes...)
t.Run("Locks", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
lockTest(t, dstPath)
})
})
return pushedFiles
}
func commitAndPushTest(t *testing.T, dstPath, prefix string, sizes ...int) (pushedFiles []string) {
for _, size := range sizes {
t.Run("PushCommit Size-"+strconv.Itoa(size), func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
pushedFiles = append(pushedFiles, doCommitAndPush(t, size, dstPath, prefix))
})
}
return pushedFiles
}
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, testFileSizeSmall, 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, testFileSizeSmall, resp.Body.Len())
assert.LessOrEqual(t, resp.Body.Len(), 1024)
if resp.Body.Len() != testFileSizeSmall && 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, testFileSizeLarge, 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, testFileSizeLarge, resp.Body.Len())
if resp.Body.Len() != testFileSizeLarge && 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, testFileSizeSmall, resp.Length)
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS))
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
assert.Equal(t, testFileSizeSmall, 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, testFileSizeLarge, 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, testFileSizeLarge, 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").AddDynamicArguments(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").AddDynamicArguments(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) {
tmpFile, err := os.CreateTemp(repoPath, prefix)
if err != nil {
return "", err
}
defer tmpFile.Close()
var seed [32]byte
rander := mathRand.NewChaCha8(seed) // for testing only, no need to seed
_, err = io.CopyN(tmpFile, rander, int64(size))
if err != nil {
return "", err
}
_ = tmpFile.Close()
// 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, auth_model.AccessTokenScopeWriteRepository)
// Protect branch without any whitelisting
t.Run("ProtectBranchNoWhitelist", func(t *testing.T) {
doProtectBranch(ctx, "protected", "", "", "")
})
// Try to push without permissions, which should fail
t.Run("TryPushWithoutPermissions", func(t *testing.T) {
_, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
assert.NoError(t, err)
doGitPushTestRepositoryFail(dstPath, "origin", "protected")
})
// Set up permissions for normal push but not force push
t.Run("SetupNormalPushPermissions", func(t *testing.T) {
doProtectBranch(ctx, "protected", baseCtx.Username, "", "")
})
// Normal push should work
t.Run("NormalPushWithPermissions", func(t *testing.T) {
_, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
assert.NoError(t, err)
doGitPushTestRepository(dstPath, "origin", "protected")
})
// Try to force push without force push permissions, which should fail
t.Run("ForcePushWithoutForcePermissions", func(t *testing.T) {
t.Run("CreateDivergentHistory", func(t *testing.T) {
git.NewCommand(git.DefaultContext, "reset", "--hard", "HEAD~1").Run(&git.RunOpts{Dir: dstPath})
_, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-new")
assert.NoError(t, err)
})
doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")
})
// Set up permissions for force push but not normal push
t.Run("SetupForcePushPermissions", func(t *testing.T) {
doProtectBranch(ctx, "protected", "", baseCtx.Username, "")
})
// Try to force push without normal push permissions, which should fail
t.Run("ForcePushWithoutNormalPermissions", func(t *testing.T) {
doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")
})
// Set up permissions for normal and force push (both are required to force push)
t.Run("SetupNormalAndForcePushPermissions", func(t *testing.T) {
doProtectBranch(ctx, "protected", baseCtx.Username, baseCtx.Username, "")
})
// Force push should now work
t.Run("ForcePushWithPermissions", func(t *testing.T) {
doGitPushTestRepository(dstPath, "-f", "origin", "protected")
})
t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "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(testFileSizeSmall, 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(testFileSizeSmall, 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(testFileSizeSmall, 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, userToWhitelistPush, userToWhitelistForcePush, unprotectedFilePatterns string) func(t *testing.T) {
// We are going to just use the owner to set the protection.
return func(t *testing.T) {
csrf := GetUserCSRFToken(t, ctx.Session)
formData := map[string]string{
"_csrf": csrf,
"rule_name": branch,
"unprotected_file_patterns": unprotectedFilePatterns,
}
if userToWhitelistPush != "" {
user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelistPush)
assert.NoError(t, err)
formData["whitelist_users"] = strconv.FormatInt(user.ID, 10)
formData["enable_push"] = "whitelist"
formData["enable_whitelist"] = "on"
}
if userToWhitelistForcePush != "" {
user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelistForcePush)
assert.NoError(t, err)
formData["force_push_allowlist_users"] = strconv.FormatInt(user.ID, 10)
formData["enable_force_push"] = "whitelist"
formData["enable_force_push_allowlist"] = "on"
}
// Send the request to update branch protection settings
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), formData)
ctx.Session.MakeRequest(t, req, http.StatusSeeOther)
// Check if master branch has been locked successfully
flashCookie := ctx.Session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522"+url.QueryEscape(branch)+"%2522%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 := t.TempDir()
// 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(db.DefaultContext, 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 := GetUserCSRFToken(t, ctx.Session)
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, auth_model.AccessTokenScopeWriteRepository)
t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected"))
t.Run("PullProtected", doGitPull(dstPath, "origin", "protected"))
t.Run("GenerateCommit", func(t *testing.T) {
_, err := generateCommitWithNewData(testFileSizeSmall, 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)
addCommitStatus := func(status api.CommitStatusState) func(*testing.T) {
return doAPICreateCommitStatus(ctx, commitID, api.CreateStatusOption{
State: status,
TargetURL: "http://test.ci/",
Description: "",
Context: "testci",
})
}
// Call API to add Pending status for commit
t.Run("CreateStatus", addCommitStatus(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", addCommitStatus(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", addCommitStatus(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, 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.DefaultFeatures().SupportProcReceive {
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(db.DefaultContext, 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").AddDynamicArguments("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.False(t, prMsg.HasMerged)
assert.Contains(t, "Testing commit 1", prMsg.Body)
assert.Equal(t, commit, prMsg.Head.Sha)
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("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.False(t, 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").AddDynamicArguments("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.False(t, prMsg.HasMerged)
assert.Equal(t, commit, prMsg.Head.Sha)
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("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.False(t, 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"))
}
}