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.

repo_test.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "fmt"
  6. "net/http"
  7. "path"
  8. "strings"
  9. "testing"
  10. "time"
  11. "code.gitea.io/gitea/modules/setting"
  12. "code.gitea.io/gitea/modules/test"
  13. "code.gitea.io/gitea/tests"
  14. "github.com/PuerkitoBio/goquery"
  15. "github.com/stretchr/testify/assert"
  16. )
  17. func TestViewRepo(t *testing.T) {
  18. defer tests.PrepareTestEnv(t)()
  19. session := loginUser(t, "user2")
  20. req := NewRequest(t, "GET", "/user2/repo1")
  21. resp := session.MakeRequest(t, req, http.StatusOK)
  22. htmlDoc := NewHTMLParser(t, resp.Body)
  23. noDescription := htmlDoc.doc.Find("#repo-desc").Children()
  24. repoTopics := htmlDoc.doc.Find("#repo-topics").Children()
  25. repoSummary := htmlDoc.doc.Find(".repository-summary").Children()
  26. assert.True(t, noDescription.HasClass("no-description"))
  27. assert.True(t, repoTopics.HasClass("repo-topic"))
  28. assert.True(t, repoSummary.HasClass("repository-menu"))
  29. req = NewRequest(t, "GET", "/org3/repo3")
  30. MakeRequest(t, req, http.StatusNotFound)
  31. session = loginUser(t, "user1")
  32. session.MakeRequest(t, req, http.StatusNotFound)
  33. }
  34. func testViewRepo(t *testing.T) {
  35. defer tests.PrepareTestEnv(t)()
  36. req := NewRequest(t, "GET", "/org3/repo3")
  37. session := loginUser(t, "user2")
  38. resp := session.MakeRequest(t, req, http.StatusOK)
  39. htmlDoc := NewHTMLParser(t, resp.Body)
  40. files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR")
  41. type file struct {
  42. fileName string
  43. commitID string
  44. commitMsg string
  45. commitTime string
  46. }
  47. var items []file
  48. files.Each(func(i int, s *goquery.Selection) {
  49. tds := s.Find("td")
  50. var f file
  51. tds.Each(func(i int, s *goquery.Selection) {
  52. if i == 0 {
  53. f.fileName = strings.TrimSpace(s.Text())
  54. } else if i == 1 {
  55. a := s.Find("a")
  56. f.commitMsg = strings.TrimSpace(a.Text())
  57. l, _ := a.Attr("href")
  58. f.commitID = path.Base(l)
  59. }
  60. })
  61. // convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC"
  62. htmlTimeString, _ := s.Find("relative-time.time-since").Attr("datetime")
  63. htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString)
  64. f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123)
  65. items = append(items, f)
  66. })
  67. commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123)
  68. assert.EqualValues(t, []file{
  69. {
  70. fileName: "doc",
  71. commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
  72. commitMsg: "init project",
  73. commitTime: commitT,
  74. },
  75. {
  76. fileName: "README.md",
  77. commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
  78. commitMsg: "init project",
  79. commitTime: commitT,
  80. },
  81. }, items)
  82. }
  83. func TestViewRepo2(t *testing.T) {
  84. // no last commit cache
  85. testViewRepo(t)
  86. // enable last commit cache for all repositories
  87. oldCommitsCount := setting.CacheService.LastCommit.CommitsCount
  88. setting.CacheService.LastCommit.CommitsCount = 0
  89. // first view will not hit the cache
  90. testViewRepo(t)
  91. // second view will hit the cache
  92. testViewRepo(t)
  93. setting.CacheService.LastCommit.CommitsCount = oldCommitsCount
  94. }
  95. func TestViewRepo3(t *testing.T) {
  96. defer tests.PrepareTestEnv(t)()
  97. req := NewRequest(t, "GET", "/org3/repo3")
  98. session := loginUser(t, "user4")
  99. session.MakeRequest(t, req, http.StatusOK)
  100. }
  101. func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
  102. defer tests.PrepareTestEnv(t)()
  103. req := NewRequest(t, "GET", "/user2/repo1")
  104. resp := MakeRequest(t, req, http.StatusOK)
  105. htmlDoc := NewHTMLParser(t, resp.Body)
  106. link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link")
  107. assert.True(t, exists, "The template has changed")
  108. assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
  109. _, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link")
  110. assert.False(t, exists)
  111. }
  112. func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
  113. defer tests.PrepareTestEnv(t)()
  114. session := loginUser(t, "user2")
  115. req := NewRequest(t, "GET", "/user2/repo1")
  116. resp := session.MakeRequest(t, req, http.StatusOK)
  117. htmlDoc := NewHTMLParser(t, resp.Body)
  118. link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link")
  119. assert.True(t, exists, "The template has changed")
  120. assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
  121. link, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link")
  122. assert.True(t, exists, "The template has changed")
  123. sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
  124. assert.Equal(t, sshURL, link)
  125. }
  126. func TestViewRepoWithSymlinks(t *testing.T) {
  127. defer tests.PrepareTestEnv(t)()
  128. session := loginUser(t, "user2")
  129. req := NewRequest(t, "GET", "/user2/repo20.git")
  130. resp := session.MakeRequest(t, req, http.StatusOK)
  131. htmlDoc := NewHTMLParser(t, resp.Body)
  132. files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name > SPAN.truncate")
  133. items := files.Map(func(i int, s *goquery.Selection) string {
  134. cls, _ := s.Find("SVG").Attr("class")
  135. file := strings.Trim(s.Find("A").Text(), " \t\n")
  136. return fmt.Sprintf("%s: %s", file, cls)
  137. })
  138. assert.Len(t, items, 5)
  139. assert.Equal(t, "a: svg octicon-file-directory-fill", items[0])
  140. assert.Equal(t, "link_b: svg octicon-file-directory-symlink", items[1])
  141. assert.Equal(t, "link_d: svg octicon-file-symlink-file", items[2])
  142. assert.Equal(t, "link_hi: svg octicon-file-symlink-file", items[3])
  143. assert.Equal(t, "link_link: svg octicon-file-symlink-file", items[4])
  144. }
  145. // TestViewAsRepoAdmin tests PR #2167
  146. func TestViewAsRepoAdmin(t *testing.T) {
  147. for user, expectedNoDescription := range map[string]bool{
  148. "user2": true,
  149. "user4": false,
  150. } {
  151. defer tests.PrepareTestEnv(t)()
  152. session := loginUser(t, user)
  153. req := NewRequest(t, "GET", "/user2/repo1.git")
  154. resp := session.MakeRequest(t, req, http.StatusOK)
  155. htmlDoc := NewHTMLParser(t, resp.Body)
  156. noDescription := htmlDoc.doc.Find("#repo-desc").Children()
  157. repoTopics := htmlDoc.doc.Find("#repo-topics").Children()
  158. repoSummary := htmlDoc.doc.Find(".repository-summary").Children()
  159. assert.Equal(t, expectedNoDescription, noDescription.HasClass("no-description"))
  160. assert.True(t, repoTopics.HasClass("repo-topic"))
  161. assert.True(t, repoSummary.HasClass("repository-menu"))
  162. }
  163. }
  164. // TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file
  165. func TestViewFileInRepo(t *testing.T) {
  166. defer tests.PrepareTestEnv(t)()
  167. session := loginUser(t, "user2")
  168. req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/README.md")
  169. resp := session.MakeRequest(t, req, http.StatusOK)
  170. htmlDoc := NewHTMLParser(t, resp.Body)
  171. description := htmlDoc.doc.Find("#repo-desc")
  172. repoTopics := htmlDoc.doc.Find("#repo-topics")
  173. repoSummary := htmlDoc.doc.Find(".repository-summary")
  174. assert.EqualValues(t, 0, description.Length())
  175. assert.EqualValues(t, 0, repoTopics.Length())
  176. assert.EqualValues(t, 0, repoSummary.Length())
  177. }
  178. // TestBlameFileInRepo repo description, topics and summary should not be displayed when running blame on a file
  179. func TestBlameFileInRepo(t *testing.T) {
  180. defer tests.PrepareTestEnv(t)()
  181. session := loginUser(t, "user2")
  182. req := NewRequest(t, "GET", "/user2/repo1/blame/branch/master/README.md")
  183. resp := session.MakeRequest(t, req, http.StatusOK)
  184. htmlDoc := NewHTMLParser(t, resp.Body)
  185. description := htmlDoc.doc.Find("#repo-desc")
  186. repoTopics := htmlDoc.doc.Find("#repo-topics")
  187. repoSummary := htmlDoc.doc.Find(".repository-summary")
  188. assert.EqualValues(t, 0, description.Length())
  189. assert.EqualValues(t, 0, repoTopics.Length())
  190. assert.EqualValues(t, 0, repoSummary.Length())
  191. }
  192. // TestViewRepoDirectory repo description, topics and summary should not be displayed when within a directory
  193. func TestViewRepoDirectory(t *testing.T) {
  194. defer tests.PrepareTestEnv(t)()
  195. session := loginUser(t, "user2")
  196. req := NewRequest(t, "GET", "/user2/repo20/src/branch/master/a")
  197. resp := session.MakeRequest(t, req, http.StatusOK)
  198. htmlDoc := NewHTMLParser(t, resp.Body)
  199. description := htmlDoc.doc.Find("#repo-desc")
  200. repoTopics := htmlDoc.doc.Find("#repo-topics")
  201. repoSummary := htmlDoc.doc.Find(".repository-summary")
  202. repoFilesTable := htmlDoc.doc.Find("#repo-files-table")
  203. assert.NotZero(t, len(repoFilesTable.Nodes))
  204. assert.Zero(t, description.Length())
  205. assert.Zero(t, repoTopics.Length())
  206. assert.Zero(t, repoSummary.Length())
  207. }
  208. // ensure that the all the different ways to find and render a README work
  209. func TestViewRepoDirectoryReadme(t *testing.T) {
  210. defer tests.PrepareTestEnv(t)()
  211. // there are many combinations:
  212. // - READMEs can be .md, .txt, or have no extension
  213. // - READMEs can be tagged with a language and even a country code
  214. // - READMEs can be stored in docs/, .gitea/, or .github/
  215. // - READMEs can be symlinks to other files
  216. // - READMEs can be broken symlinks which should not render
  217. //
  218. // this doesn't cover all possible cases, just the major branches of the code
  219. session := loginUser(t, "user2")
  220. check := func(name, url, expectedFilename, expectedReadmeType, expectedContent string) {
  221. t.Run(name, func(t *testing.T) {
  222. defer tests.PrintCurrentTest(t)()
  223. req := NewRequest(t, "GET", url)
  224. resp := session.MakeRequest(t, req, http.StatusOK)
  225. htmlDoc := NewHTMLParser(t, resp.Body)
  226. readmeName := htmlDoc.doc.Find("h4.file-header")
  227. readmeContent := htmlDoc.doc.Find(".file-view") // TODO: add a id="readme" to the output to make this test more precise
  228. readmeType, _ := readmeContent.Attr("class")
  229. assert.Equal(t, expectedFilename, strings.TrimSpace(readmeName.Text()))
  230. assert.Contains(t, readmeType, expectedReadmeType)
  231. assert.Contains(t, readmeContent.Text(), expectedContent)
  232. })
  233. }
  234. // viewing the top level
  235. check("Home", "/user2/readme-test/", "README.md", "markdown", "The cake is a lie.")
  236. // viewing different file extensions
  237. check("md", "/user2/readme-test/src/branch/master/", "README.md", "markdown", "The cake is a lie.")
  238. check("txt", "/user2/readme-test/src/branch/txt/", "README.txt", "plain-text", "My spoon is too big.")
  239. check("plain", "/user2/readme-test/src/branch/plain/", "README", "plain-text", "Birken my stocks gee howdy")
  240. check("i18n", "/user2/readme-test/src/branch/i18n/", "README.zh.md", "markdown", "蛋糕是一个谎言")
  241. // using HEAD ref
  242. check("branch-HEAD", "/user2/readme-test/src/branch/HEAD/", "README.md", "markdown", "The cake is a lie.")
  243. check("commit-HEAD", "/user2/readme-test/src/commit/HEAD/", "README.md", "markdown", "The cake is a lie.")
  244. // viewing different subdirectories
  245. check("subdir", "/user2/readme-test/src/branch/subdir/libcake", "README.md", "markdown", "Four pints of sugar.")
  246. check("docs-direct", "/user2/readme-test/src/branch/special-subdir-docs/docs/", "README.md", "markdown", "This is in docs/")
  247. check("docs", "/user2/readme-test/src/branch/special-subdir-docs/", "docs/README.md", "markdown", "This is in docs/")
  248. check(".gitea", "/user2/readme-test/src/branch/special-subdir-.gitea/", ".gitea/README.md", "markdown", "This is in .gitea/")
  249. check(".github", "/user2/readme-test/src/branch/special-subdir-.github/", ".github/README.md", "markdown", "This is in .github/")
  250. // symlinks
  251. // symlinks are subtle:
  252. // - they should be able to handle going a reasonable number of times up and down in the tree
  253. // - they shouldn't get stuck on link cycles
  254. // - they should determine the filetype based on the name of the link, not the target
  255. check("symlink", "/user2/readme-test/src/branch/symlink/", "README.md", "markdown", "This is in some/other/path")
  256. check("symlink-multiple", "/user2/readme-test/src/branch/symlink/some/", "README.txt", "plain-text", "This is in some/other/path")
  257. check("symlink-up-and-down", "/user2/readme-test/src/branch/symlink/up/back/down/down", "README.md", "markdown", "It's a me, mario")
  258. // testing fallback rules
  259. // READMEs are searched in this order:
  260. // - [README.zh-cn.md, README.zh_cn.md, README.zh.md, README_zh.md, README.md, README.txt, README,
  261. // docs/README.zh-cn.md, docs/README.zh_cn.md, docs/README.zh.md, docs/README_zh.md, docs/README.md, docs/README.txt, docs/README,
  262. // .gitea/README.zh-cn.md, .gitea/README.zh_cn.md, .gitea/README.zh.md, .gitea/README_zh.md, .gitea/README.md, .gitea/README.txt, .gitea/README,
  263. // .github/README.zh-cn.md, .github/README.zh_cn.md, .github/README.zh.md, .github/README_zh.md, .github/README.md, .github/README.txt, .github/README]
  264. // and a broken/looped symlink counts as not existing at all and should be skipped.
  265. // again, this doesn't cover all cases, but it covers a few
  266. check("fallback/top", "/user2/readme-test/src/branch/fallbacks/", "README.en.md", "markdown", "This is README.en.md")
  267. check("fallback/2", "/user2/readme-test/src/branch/fallbacks2/", "README.md", "markdown", "This is README.md")
  268. check("fallback/3", "/user2/readme-test/src/branch/fallbacks3/", "README", "plain-text", "This is README")
  269. check("fallback/4", "/user2/readme-test/src/branch/fallbacks4/", "docs/README.en.md", "markdown", "This is docs/README.en.md")
  270. check("fallback/5", "/user2/readme-test/src/branch/fallbacks5/", "docs/README.md", "markdown", "This is docs/README.md")
  271. check("fallback/6", "/user2/readme-test/src/branch/fallbacks6/", "docs/README", "plain-text", "This is docs/README")
  272. check("fallback/7", "/user2/readme-test/src/branch/fallbacks7/", ".gitea/README.en.md", "markdown", "This is .gitea/README.en.md")
  273. check("fallback/8", "/user2/readme-test/src/branch/fallbacks8/", ".gitea/README.md", "markdown", "This is .gitea/README.md")
  274. check("fallback/9", "/user2/readme-test/src/branch/fallbacks9/", ".gitea/README", "plain-text", "This is .gitea/README")
  275. // this case tests that broken symlinks count as missing files, instead of rendering their contents
  276. check("fallbacks-broken-symlinks", "/user2/readme-test/src/branch/fallbacks-broken-symlinks/", "docs/README", "plain-text", "This is docs/README")
  277. // some cases that should NOT render a README
  278. // - /readme
  279. // - /.github/docs/README.md
  280. // - a symlink loop
  281. missing := func(name, url string) {
  282. t.Run("missing/"+name, func(t *testing.T) {
  283. defer tests.PrintCurrentTest(t)()
  284. req := NewRequest(t, "GET", url)
  285. resp := session.MakeRequest(t, req, http.StatusOK)
  286. htmlDoc := NewHTMLParser(t, resp.Body)
  287. _, exists := htmlDoc.doc.Find(".file-view").Attr("class")
  288. assert.False(t, exists, "README should not have rendered")
  289. })
  290. }
  291. missing("sp-ace", "/user2/readme-test/src/branch/sp-ace/")
  292. missing("nested-special", "/user2/readme-test/src/branch/special-subdir-nested/subproject") // the special subdirs should only trigger on the repo root
  293. missing("special-subdir-nested", "/user2/readme-test/src/branch/special-subdir-nested/")
  294. missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
  295. }
  296. func TestMarkDownReadmeImage(t *testing.T) {
  297. defer tests.PrepareTestEnv(t)()
  298. session := loginUser(t, "user2")
  299. req := NewRequest(t, "GET", "/user2/repo1/src/branch/home-md-img-check")
  300. resp := session.MakeRequest(t, req, http.StatusOK)
  301. htmlDoc := NewHTMLParser(t, resp.Body)
  302. src, exists := htmlDoc.doc.Find(`.markdown img`).Attr("src")
  303. assert.True(t, exists, "Image not found in README")
  304. assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src)
  305. req = NewRequest(t, "GET", "/user2/repo1/src/branch/home-md-img-check/README.md")
  306. resp = session.MakeRequest(t, req, http.StatusOK)
  307. htmlDoc = NewHTMLParser(t, resp.Body)
  308. src, exists = htmlDoc.doc.Find(`.markdown img`).Attr("src")
  309. assert.True(t, exists, "Image not found in markdown file")
  310. assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src)
  311. }
  312. func TestMarkDownReadmeImageSubfolder(t *testing.T) {
  313. defer tests.PrepareTestEnv(t)()
  314. session := loginUser(t, "user2")
  315. // this branch has the README in the special docs/README.md location
  316. req := NewRequest(t, "GET", "/user2/repo1/src/branch/sub-home-md-img-check")
  317. resp := session.MakeRequest(t, req, http.StatusOK)
  318. htmlDoc := NewHTMLParser(t, resp.Body)
  319. src, exists := htmlDoc.doc.Find(`.markdown img`).Attr("src")
  320. assert.True(t, exists, "Image not found in README")
  321. assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src)
  322. req = NewRequest(t, "GET", "/user2/repo1/src/branch/sub-home-md-img-check/docs/README.md")
  323. resp = session.MakeRequest(t, req, http.StatusOK)
  324. htmlDoc = NewHTMLParser(t, resp.Body)
  325. src, exists = htmlDoc.doc.Find(`.markdown img`).Attr("src")
  326. assert.True(t, exists, "Image not found in markdown file")
  327. assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src)
  328. }
  329. func TestGeneratedSourceLink(t *testing.T) {
  330. defer tests.PrepareTestEnv(t)()
  331. t.Run("Rendered file", func(t *testing.T) {
  332. defer tests.PrintCurrentTest(t)()
  333. req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/README.md?display=source")
  334. resp := MakeRequest(t, req, http.StatusOK)
  335. doc := NewHTMLParser(t, resp.Body)
  336. dataURL, exists := doc.doc.Find(".copy-line-permalink").Attr("data-url")
  337. assert.True(t, exists)
  338. assert.Equal(t, "/user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md?display=source", dataURL)
  339. dataURL, exists = doc.doc.Find(".ref-in-new-issue").Attr("data-url-param-body-link")
  340. assert.True(t, exists)
  341. assert.Equal(t, "/user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md?display=source", dataURL)
  342. })
  343. t.Run("Non-Rendered file", func(t *testing.T) {
  344. defer tests.PrintCurrentTest(t)()
  345. session := loginUser(t, "user27")
  346. req := NewRequest(t, "GET", "/user27/repo49/src/branch/master/test/test.txt")
  347. resp := session.MakeRequest(t, req, http.StatusOK)
  348. doc := NewHTMLParser(t, resp.Body)
  349. dataURL, exists := doc.doc.Find(".copy-line-permalink").Attr("data-url")
  350. assert.True(t, exists)
  351. assert.Equal(t, "/user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL)
  352. dataURL, exists = doc.doc.Find(".ref-in-new-issue").Attr("data-url-param-body-link")
  353. assert.True(t, exists)
  354. assert.Equal(t, "/user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL)
  355. })
  356. }
  357. func TestViewCommit(t *testing.T) {
  358. defer tests.PrepareTestEnv(t)()
  359. req := NewRequest(t, "GET", "/user2/repo1/commit/0123456789012345678901234567890123456789")
  360. req.Header.Add("Accept", "text/html")
  361. resp := MakeRequest(t, req, http.StatusNotFound)
  362. assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "non-existing commit should render 404 page")
  363. }