You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

376 lines
13 KiB

  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package integrations
  5. import (
  6. "crypto/rand"
  7. "fmt"
  8. "io/ioutil"
  9. "net/http"
  10. "net/url"
  11. "os"
  12. "path"
  13. "path/filepath"
  14. "strconv"
  15. "testing"
  16. "time"
  17. "code.gitea.io/gitea/models"
  18. "code.gitea.io/gitea/modules/git"
  19. api "code.gitea.io/gitea/modules/structs"
  20. "github.com/stretchr/testify/assert"
  21. )
  22. const (
  23. littleSize = 1024 //1ko
  24. bigSize = 128 * 1024 * 1024 //128Mo
  25. )
  26. func TestGit(t *testing.T) {
  27. onGiteaRun(t, testGit)
  28. }
  29. func testGit(t *testing.T, u *url.URL) {
  30. username := "user2"
  31. baseAPITestContext := NewAPITestContext(t, username, "repo1")
  32. u.Path = baseAPITestContext.GitPath()
  33. forkedUserCtx := NewAPITestContext(t, "user4", "repo1")
  34. t.Run("HTTP", func(t *testing.T) {
  35. PrintCurrentTest(t)
  36. ensureAnonymousClone(t, u)
  37. httpContext := baseAPITestContext
  38. httpContext.Reponame = "repo-tmp-17"
  39. forkedUserCtx.Reponame = httpContext.Reponame
  40. dstPath, err := ioutil.TempDir("", httpContext.Reponame)
  41. assert.NoError(t, err)
  42. defer os.RemoveAll(dstPath)
  43. t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false))
  44. t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, models.AccessModeRead))
  45. t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username))
  46. u.Path = httpContext.GitPath()
  47. u.User = url.UserPassword(username, userPassword)
  48. t.Run("Clone", doGitClone(dstPath, u))
  49. little, big := standardCommitAndPushTest(t, dstPath)
  50. littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
  51. rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
  52. mediaTest(t, &httpContext, little, big, littleLFS, bigLFS)
  53. t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
  54. t.Run("MergeFork", func(t *testing.T) {
  55. t.Run("CreatePRAndMerge", doMergeFork(httpContext, forkedUserCtx, "master", httpContext.Username+":master"))
  56. t.Run("DeleteRepository", doAPIDeleteRepository(httpContext))
  57. rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
  58. mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
  59. })
  60. })
  61. t.Run("SSH", func(t *testing.T) {
  62. PrintCurrentTest(t)
  63. sshContext := baseAPITestContext
  64. sshContext.Reponame = "repo-tmp-18"
  65. keyname := "my-testing-key"
  66. forkedUserCtx.Reponame = sshContext.Reponame
  67. t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false))
  68. t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, models.AccessModeRead))
  69. t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username))
  70. //Setup key the user ssh key
  71. withKeyFile(t, keyname, func(keyFile string) {
  72. t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile))
  73. //Setup remote link
  74. //TODO: get url from api
  75. sshURL := createSSHUrl(sshContext.GitPath(), u)
  76. //Setup clone folder
  77. dstPath, err := ioutil.TempDir("", sshContext.Reponame)
  78. assert.NoError(t, err)
  79. defer os.RemoveAll(dstPath)
  80. t.Run("Clone", doGitClone(dstPath, sshURL))
  81. little, big := standardCommitAndPushTest(t, dstPath)
  82. littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
  83. rawTest(t, &sshContext, little, big, littleLFS, bigLFS)
  84. mediaTest(t, &sshContext, little, big, littleLFS, bigLFS)
  85. t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
  86. t.Run("MergeFork", func(t *testing.T) {
  87. t.Run("CreatePRAndMerge", doMergeFork(sshContext, forkedUserCtx, "master", sshContext.Username+":master"))
  88. t.Run("DeleteRepository", doAPIDeleteRepository(sshContext))
  89. rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
  90. mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
  91. })
  92. })
  93. })
  94. }
  95. func ensureAnonymousClone(t *testing.T, u *url.URL) {
  96. dstLocalPath, err := ioutil.TempDir("", "repo1")
  97. assert.NoError(t, err)
  98. defer os.RemoveAll(dstLocalPath)
  99. t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
  100. }
  101. func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string) {
  102. t.Run("Standard", func(t *testing.T) {
  103. PrintCurrentTest(t)
  104. little, big = commitAndPushTest(t, dstPath, "data-file-")
  105. })
  106. return
  107. }
  108. func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) {
  109. t.Run("LFS", func(t *testing.T) {
  110. PrintCurrentTest(t)
  111. prefix := "lfs-data-file-"
  112. _, err := git.NewCommand("lfs").AddArguments("install").RunInDir(dstPath)
  113. assert.NoError(t, err)
  114. _, err = git.NewCommand("lfs").AddArguments("track", prefix+"*").RunInDir(dstPath)
  115. assert.NoError(t, err)
  116. err = git.AddChanges(dstPath, false, ".gitattributes")
  117. assert.NoError(t, err)
  118. littleLFS, bigLFS = commitAndPushTest(t, dstPath, prefix)
  119. t.Run("Locks", func(t *testing.T) {
  120. PrintCurrentTest(t)
  121. lockTest(t, dstPath)
  122. })
  123. })
  124. return
  125. }
  126. func commitAndPushTest(t *testing.T, dstPath, prefix string) (little, big string) {
  127. t.Run("PushCommit", func(t *testing.T) {
  128. PrintCurrentTest(t)
  129. t.Run("Little", func(t *testing.T) {
  130. PrintCurrentTest(t)
  131. little = doCommitAndPush(t, littleSize, dstPath, prefix)
  132. })
  133. t.Run("Big", func(t *testing.T) {
  134. if testing.Short() {
  135. t.Skip("Skipping test in short mode.")
  136. return
  137. }
  138. PrintCurrentTest(t)
  139. big = doCommitAndPush(t, bigSize, dstPath, prefix)
  140. })
  141. })
  142. return
  143. }
  144. func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) {
  145. t.Run("Raw", func(t *testing.T) {
  146. PrintCurrentTest(t)
  147. username := ctx.Username
  148. reponame := ctx.Reponame
  149. session := loginUser(t, username)
  150. // Request raw paths
  151. req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little))
  152. resp := session.MakeRequest(t, req, http.StatusOK)
  153. assert.Equal(t, littleSize, resp.Body.Len())
  154. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS))
  155. resp = session.MakeRequest(t, req, http.StatusOK)
  156. assert.NotEqual(t, littleSize, resp.Body.Len())
  157. assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier)
  158. if !testing.Short() {
  159. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big))
  160. resp = session.MakeRequest(t, req, http.StatusOK)
  161. assert.Equal(t, bigSize, resp.Body.Len())
  162. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS))
  163. resp = session.MakeRequest(t, req, http.StatusOK)
  164. assert.NotEqual(t, bigSize, resp.Body.Len())
  165. assert.Contains(t, resp.Body.String(), models.LFSMetaFileIdentifier)
  166. }
  167. })
  168. }
  169. func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) {
  170. t.Run("Media", func(t *testing.T) {
  171. PrintCurrentTest(t)
  172. username := ctx.Username
  173. reponame := ctx.Reponame
  174. session := loginUser(t, username)
  175. // Request media paths
  176. req := NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", little))
  177. resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  178. assert.Equal(t, littleSize, resp.Length)
  179. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS))
  180. resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  181. assert.Equal(t, littleSize, resp.Length)
  182. if !testing.Short() {
  183. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big))
  184. resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  185. assert.Equal(t, bigSize, resp.Length)
  186. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS))
  187. resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  188. assert.Equal(t, bigSize, resp.Length)
  189. }
  190. })
  191. }
  192. func lockTest(t *testing.T, repoPath string) {
  193. lockFileTest(t, "README.md", repoPath)
  194. }
  195. func lockFileTest(t *testing.T, filename, repoPath string) {
  196. _, err := git.NewCommand("lfs").AddArguments("locks").RunInDir(repoPath)
  197. assert.NoError(t, err)
  198. _, err = git.NewCommand("lfs").AddArguments("lock", filename).RunInDir(repoPath)
  199. assert.NoError(t, err)
  200. _, err = git.NewCommand("lfs").AddArguments("locks").RunInDir(repoPath)
  201. assert.NoError(t, err)
  202. _, err = git.NewCommand("lfs").AddArguments("unlock", filename).RunInDir(repoPath)
  203. assert.NoError(t, err)
  204. }
  205. func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string {
  206. name, err := generateCommitWithNewData(size, repoPath, "user2@example.com", "User Two", prefix)
  207. assert.NoError(t, err)
  208. _, err = git.NewCommand("push", "origin", "master").RunInDir(repoPath) //Push
  209. assert.NoError(t, err)
  210. return name
  211. }
  212. func generateCommitWithNewData(size int, repoPath, email, fullName, prefix string) (string, error) {
  213. //Generate random file
  214. data := make([]byte, size)
  215. _, err := rand.Read(data)
  216. if err != nil {
  217. return "", err
  218. }
  219. tmpFile, err := ioutil.TempFile(repoPath, prefix)
  220. if err != nil {
  221. return "", err
  222. }
  223. defer tmpFile.Close()
  224. _, err = tmpFile.Write(data)
  225. if err != nil {
  226. return "", err
  227. }
  228. //Commit
  229. err = git.AddChanges(repoPath, false, filepath.Base(tmpFile.Name()))
  230. if err != nil {
  231. return "", err
  232. }
  233. err = git.CommitChanges(repoPath, git.CommitChangesOptions{
  234. Committer: &git.Signature{
  235. Email: email,
  236. Name: fullName,
  237. When: time.Now(),
  238. },
  239. Author: &git.Signature{
  240. Email: email,
  241. Name: fullName,
  242. When: time.Now(),
  243. },
  244. Message: fmt.Sprintf("Testing commit @ %v", time.Now()),
  245. })
  246. return filepath.Base(tmpFile.Name()), err
  247. }
  248. func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
  249. return func(t *testing.T) {
  250. PrintCurrentTest(t)
  251. t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "protected"))
  252. t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected"))
  253. ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame)
  254. t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", ""))
  255. t.Run("GenerateCommit", func(t *testing.T) {
  256. _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
  257. assert.NoError(t, err)
  258. })
  259. t.Run("FailToPushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "origin", "protected"))
  260. t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected"))
  261. var pr api.PullRequest
  262. var err error
  263. t.Run("CreatePullRequest", func(t *testing.T) {
  264. pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected")(t)
  265. assert.NoError(t, err)
  266. })
  267. t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  268. t.Run("PullProtected", doGitPull(dstPath, "origin", "protected"))
  269. t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username))
  270. t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master"))
  271. t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce"))
  272. t.Run("GenerateCommit", func(t *testing.T) {
  273. _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
  274. assert.NoError(t, err)
  275. })
  276. t.Run("FailToForcePushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected"))
  277. t.Run("MergeProtectedToToforce", doGitMerge(dstPath, "protected"))
  278. t.Run("PushToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "toforce:protected"))
  279. t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master"))
  280. }
  281. }
  282. func doProtectBranch(ctx APITestContext, branch string, userToWhitelist string) func(t *testing.T) {
  283. // We are going to just use the owner to set the protection.
  284. return func(t *testing.T) {
  285. csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings/branches", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)))
  286. if userToWhitelist == "" {
  287. // Change branch to protected
  288. 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{
  289. "_csrf": csrf,
  290. "protected": "on",
  291. })
  292. ctx.Session.MakeRequest(t, req, http.StatusFound)
  293. } else {
  294. user, err := models.GetUserByName(userToWhitelist)
  295. assert.NoError(t, err)
  296. // Change branch to protected
  297. 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{
  298. "_csrf": csrf,
  299. "protected": "on",
  300. "enable_whitelist": "on",
  301. "whitelist_users": strconv.FormatInt(user.ID, 10),
  302. })
  303. ctx.Session.MakeRequest(t, req, http.StatusFound)
  304. }
  305. // Check if master branch has been locked successfully
  306. flashCookie := ctx.Session.GetCookie("macaron_flash")
  307. assert.NotNil(t, flashCookie)
  308. assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
  309. }
  310. }
  311. func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) {
  312. return func(t *testing.T) {
  313. var pr api.PullRequest
  314. var err error
  315. t.Run("CreatePullRequest", func(t *testing.T) {
  316. pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t)
  317. assert.NoError(t, err)
  318. })
  319. t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  320. }
  321. }