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.

329 lines
15 KiB

  1. // Copyright 2019 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. "fmt"
  7. "net/http"
  8. "net/url"
  9. "testing"
  10. "code.gitea.io/gitea/models"
  11. api "code.gitea.io/gitea/modules/structs"
  12. "github.com/stretchr/testify/assert"
  13. )
  14. // getRepoEditOptionFromRepo gets the options for an existing repo exactly as is
  15. func getRepoEditOptionFromRepo(repo *models.Repository) *api.EditRepoOption {
  16. name := repo.Name
  17. description := repo.Description
  18. website := repo.Website
  19. private := repo.IsPrivate
  20. hasIssues := false
  21. var internalTracker *api.InternalTracker
  22. var externalTracker *api.ExternalTracker
  23. if unit, err := repo.GetUnit(models.UnitTypeIssues); err == nil {
  24. config := unit.IssuesConfig()
  25. hasIssues = true
  26. internalTracker = &api.InternalTracker{
  27. EnableTimeTracker: config.EnableTimetracker,
  28. AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime,
  29. EnableIssueDependencies: config.EnableDependencies,
  30. }
  31. } else if unit, err := repo.GetUnit(models.UnitTypeExternalTracker); err == nil {
  32. config := unit.ExternalTrackerConfig()
  33. hasIssues = true
  34. externalTracker = &api.ExternalTracker{
  35. ExternalTrackerURL: config.ExternalTrackerURL,
  36. ExternalTrackerFormat: config.ExternalTrackerFormat,
  37. ExternalTrackerStyle: config.ExternalTrackerStyle,
  38. }
  39. }
  40. hasWiki := false
  41. var externalWiki *api.ExternalWiki
  42. if _, err := repo.GetUnit(models.UnitTypeWiki); err == nil {
  43. hasWiki = true
  44. } else if unit, err := repo.GetUnit(models.UnitTypeExternalWiki); err == nil {
  45. hasWiki = true
  46. config := unit.ExternalWikiConfig()
  47. externalWiki = &api.ExternalWiki{
  48. ExternalWikiURL: config.ExternalWikiURL,
  49. }
  50. }
  51. defaultBranch := repo.DefaultBranch
  52. hasPullRequests := false
  53. ignoreWhitespaceConflicts := false
  54. allowMerge := false
  55. allowRebase := false
  56. allowRebaseMerge := false
  57. allowSquash := false
  58. if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil {
  59. config := unit.PullRequestsConfig()
  60. hasPullRequests = true
  61. ignoreWhitespaceConflicts = config.IgnoreWhitespaceConflicts
  62. allowMerge = config.AllowMerge
  63. allowRebase = config.AllowRebase
  64. allowRebaseMerge = config.AllowRebaseMerge
  65. allowSquash = config.AllowSquash
  66. }
  67. archived := repo.IsArchived
  68. return &api.EditRepoOption{
  69. Name: &name,
  70. Description: &description,
  71. Website: &website,
  72. Private: &private,
  73. HasIssues: &hasIssues,
  74. ExternalTracker: externalTracker,
  75. InternalTracker: internalTracker,
  76. HasWiki: &hasWiki,
  77. ExternalWiki: externalWiki,
  78. DefaultBranch: &defaultBranch,
  79. HasPullRequests: &hasPullRequests,
  80. IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts,
  81. AllowMerge: &allowMerge,
  82. AllowRebase: &allowRebase,
  83. AllowRebaseMerge: &allowRebaseMerge,
  84. AllowSquash: &allowSquash,
  85. Archived: &archived,
  86. }
  87. }
  88. // getNewRepoEditOption Gets the options to change everything about an existing repo by adding to strings or changing
  89. // the boolean
  90. func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption {
  91. // Gives a new property to everything
  92. name := *opts.Name + "renamed"
  93. description := "new description"
  94. website := "http://wwww.newwebsite.com"
  95. private := !*opts.Private
  96. hasIssues := !*opts.HasIssues
  97. hasWiki := !*opts.HasWiki
  98. defaultBranch := "master"
  99. hasPullRequests := !*opts.HasPullRequests
  100. ignoreWhitespaceConflicts := !*opts.IgnoreWhitespaceConflicts
  101. allowMerge := !*opts.AllowMerge
  102. allowRebase := !*opts.AllowRebase
  103. allowRebaseMerge := !*opts.AllowRebaseMerge
  104. allowSquash := !*opts.AllowSquash
  105. archived := !*opts.Archived
  106. return &api.EditRepoOption{
  107. Name: &name,
  108. Description: &description,
  109. Website: &website,
  110. Private: &private,
  111. DefaultBranch: &defaultBranch,
  112. HasIssues: &hasIssues,
  113. HasWiki: &hasWiki,
  114. HasPullRequests: &hasPullRequests,
  115. IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts,
  116. AllowMerge: &allowMerge,
  117. AllowRebase: &allowRebase,
  118. AllowRebaseMerge: &allowRebaseMerge,
  119. AllowSquash: &allowSquash,
  120. Archived: &archived,
  121. }
  122. }
  123. func TestAPIRepoEdit(t *testing.T) {
  124. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  125. user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16
  126. user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org
  127. user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos
  128. repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo
  129. repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo
  130. repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo
  131. // Get user2's token
  132. session := loginUser(t, user2.Name)
  133. token2 := getTokenForLoggedInUser(t, session)
  134. session = emptyTestSession(t)
  135. // Get user4's token
  136. session = loginUser(t, user4.Name)
  137. token4 := getTokenForLoggedInUser(t, session)
  138. session = emptyTestSession(t)
  139. // Test editing a repo1 which user2 owns, changing name and many properties
  140. origRepoEditOption := getRepoEditOptionFromRepo(repo1)
  141. repoEditOption := getNewRepoEditOption(origRepoEditOption)
  142. url := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo1.Name, token2)
  143. req := NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  144. resp := session.MakeRequest(t, req, http.StatusOK)
  145. var repo api.Repository
  146. DecodeJSON(t, resp, &repo)
  147. assert.NotNil(t, repo)
  148. // check response
  149. assert.Equal(t, *repoEditOption.Name, repo.Name)
  150. assert.Equal(t, *repoEditOption.Description, repo.Description)
  151. assert.Equal(t, *repoEditOption.Website, repo.Website)
  152. assert.Equal(t, *repoEditOption.Archived, repo.Archived)
  153. // check repo1 from database
  154. repo1edited := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
  155. repo1editedOption := getRepoEditOptionFromRepo(repo1edited)
  156. assert.Equal(t, *repoEditOption.Name, *repo1editedOption.Name)
  157. assert.Equal(t, *repoEditOption.Description, *repo1editedOption.Description)
  158. assert.Equal(t, *repoEditOption.Website, *repo1editedOption.Website)
  159. assert.Equal(t, *repoEditOption.Archived, *repo1editedOption.Archived)
  160. assert.Equal(t, *repoEditOption.Private, *repo1editedOption.Private)
  161. assert.Equal(t, *repoEditOption.HasWiki, *repo1editedOption.HasWiki)
  162. //Test editing repo1 to use internal issue and wiki (default)
  163. *repoEditOption.HasIssues = true
  164. repoEditOption.ExternalTracker = nil
  165. repoEditOption.InternalTracker = &api.InternalTracker{
  166. EnableTimeTracker: false,
  167. AllowOnlyContributorsToTrackTime: false,
  168. EnableIssueDependencies: false,
  169. }
  170. *repoEditOption.HasWiki = true
  171. repoEditOption.ExternalWiki = nil
  172. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2)
  173. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  174. resp = session.MakeRequest(t, req, http.StatusOK)
  175. DecodeJSON(t, resp, &repo)
  176. assert.NotNil(t, repo)
  177. // check repo1 was written to database
  178. repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
  179. repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
  180. assert.Equal(t, *repo1editedOption.HasIssues, true)
  181. assert.Nil(t, repo1editedOption.ExternalTracker)
  182. assert.Equal(t, *repo1editedOption.InternalTracker, *repoEditOption.InternalTracker)
  183. assert.Equal(t, *repo1editedOption.HasWiki, true)
  184. assert.Nil(t, repo1editedOption.ExternalWiki)
  185. //Test editing repo1 to use external issue and wiki
  186. repoEditOption.ExternalTracker = &api.ExternalTracker{
  187. ExternalTrackerURL: "http://www.somewebsite.com",
  188. ExternalTrackerFormat: "http://www.somewebsite.com/{user}/{repo}?issue={index}",
  189. ExternalTrackerStyle: "alphanumeric",
  190. }
  191. repoEditOption.ExternalWiki = &api.ExternalWiki{
  192. ExternalWikiURL: "http://www.somewebsite.com",
  193. }
  194. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  195. resp = session.MakeRequest(t, req, http.StatusOK)
  196. DecodeJSON(t, resp, &repo)
  197. assert.NotNil(t, repo)
  198. // check repo1 was written to database
  199. repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
  200. repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
  201. assert.Equal(t, *repo1editedOption.HasIssues, true)
  202. assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker)
  203. assert.Equal(t, *repo1editedOption.HasWiki, true)
  204. assert.Equal(t, *repo1editedOption.ExternalWiki, *repoEditOption.ExternalWiki)
  205. // Do some tests with invalid URL for external tracker and wiki
  206. repoEditOption.ExternalTracker.ExternalTrackerURL = "htp://www.somewebsite.com"
  207. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  208. resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity)
  209. repoEditOption.ExternalTracker.ExternalTrackerURL = "http://www.somewebsite.com"
  210. repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user/{repo}?issue={index}"
  211. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  212. resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity)
  213. repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user}/{repo}?issue={index}"
  214. repoEditOption.ExternalWiki.ExternalWikiURL = "htp://www.somewebsite.com"
  215. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  216. resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity)
  217. //Test small repo change through API with issue and wiki option not set; They shall not be touched.
  218. *repoEditOption.Description = "small change"
  219. repoEditOption.HasIssues = nil
  220. repoEditOption.ExternalTracker = nil
  221. repoEditOption.HasWiki = nil
  222. repoEditOption.ExternalWiki = nil
  223. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  224. resp = session.MakeRequest(t, req, http.StatusOK)
  225. DecodeJSON(t, resp, &repo)
  226. assert.NotNil(t, repo)
  227. // check repo1 was written to database
  228. repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
  229. repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
  230. assert.Equal(t, *repo1editedOption.Description, *repoEditOption.Description)
  231. assert.Equal(t, *repo1editedOption.HasIssues, true)
  232. assert.NotNil(t, *repo1editedOption.ExternalTracker)
  233. assert.Equal(t, *repo1editedOption.HasWiki, true)
  234. assert.NotNil(t, *repo1editedOption.ExternalWiki)
  235. // reset repo in db
  236. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2)
  237. req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption)
  238. _ = session.MakeRequest(t, req, http.StatusOK)
  239. // Test editing a non-existing repo
  240. name := "repodoesnotexist"
  241. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, name, token2)
  242. req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{Name: &name})
  243. _ = session.MakeRequest(t, req, http.StatusNotFound)
  244. // Test editing repo16 by user4 who does not have write access
  245. origRepoEditOption = getRepoEditOptionFromRepo(repo16)
  246. repoEditOption = getNewRepoEditOption(origRepoEditOption)
  247. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token4)
  248. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  249. session.MakeRequest(t, req, http.StatusNotFound)
  250. // Tests a repo with no token given so will fail
  251. origRepoEditOption = getRepoEditOptionFromRepo(repo16)
  252. repoEditOption = getNewRepoEditOption(origRepoEditOption)
  253. url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name)
  254. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  255. _ = session.MakeRequest(t, req, http.StatusNotFound)
  256. // Test using access token for a private repo that the user of the token owns
  257. origRepoEditOption = getRepoEditOptionFromRepo(repo16)
  258. repoEditOption = getNewRepoEditOption(origRepoEditOption)
  259. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2)
  260. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  261. _ = session.MakeRequest(t, req, http.StatusOK)
  262. // reset repo in db
  263. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2)
  264. req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption)
  265. _ = session.MakeRequest(t, req, http.StatusOK)
  266. // Test making a repo public that is private
  267. repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository)
  268. assert.True(t, repo16.IsPrivate)
  269. private := false
  270. repoEditOption = &api.EditRepoOption{
  271. Private: &private,
  272. }
  273. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2)
  274. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  275. _ = session.MakeRequest(t, req, http.StatusOK)
  276. repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository)
  277. assert.False(t, repo16.IsPrivate)
  278. // Make it private again
  279. private = true
  280. repoEditOption.Private = &private
  281. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  282. _ = session.MakeRequest(t, req, http.StatusOK)
  283. // Test using org repo "user3/repo3" where user2 is a collaborator
  284. origRepoEditOption = getRepoEditOptionFromRepo(repo3)
  285. repoEditOption = getNewRepoEditOption(origRepoEditOption)
  286. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user3.Name, repo3.Name, token2)
  287. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  288. session.MakeRequest(t, req, http.StatusOK)
  289. // reset repo in db
  290. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user3.Name, *repoEditOption.Name, token2)
  291. req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption)
  292. _ = session.MakeRequest(t, req, http.StatusOK)
  293. // Test using org repo "user3/repo3" with no user token
  294. origRepoEditOption = getRepoEditOptionFromRepo(repo3)
  295. repoEditOption = getNewRepoEditOption(origRepoEditOption)
  296. url = fmt.Sprintf("/api/v1/repos/%s/%s", user3.Name, repo3.Name)
  297. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  298. session.MakeRequest(t, req, http.StatusNotFound)
  299. // Test using repo "user2/repo1" where user4 is a NOT collaborator
  300. origRepoEditOption = getRepoEditOptionFromRepo(repo1)
  301. repoEditOption = getNewRepoEditOption(origRepoEditOption)
  302. url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo1.Name, token4)
  303. req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
  304. session.MakeRequest(t, req, http.StatusForbidden)
  305. })
  306. }