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