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.

api_repo_edit_test.go 16KB

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