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

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