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