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  }