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  }