Files
Gitea/tests/integration/repo_test.go
T
wxiaoguang a30d865b78 fix: correct full url when using sub-path (#38712)
fix #38708

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2026-07-30 21:09:32 +02:00

682 lines
29 KiB
Go

// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//nolint:govet // disable "composites: gitea.dev/modules/git.FastImportFile struct literal uses unkeyed fields"
package integration
import (
"fmt"
"net/http"
"os"
"path"
"strconv"
"strings"
"testing"
"time"
"gitea.dev/models/db"
issues_model "gitea.dev/models/issues"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/git"
"gitea.dev/modules/git/gitrepo"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
repo_service "gitea.dev/services/repository"
"gitea.dev/tests"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepoView(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("ViewRepoPublic", testViewRepoPublic)
t.Run("ViewRepoWithCache", testViewRepoWithCache)
t.Run("ViewRepoPrivate", testViewRepoPrivate)
t.Run("ViewRepo1CloneLinkAnonymous", testViewRepo1CloneLinkAnonymous)
t.Run("ViewRepo1CloneLinkAuthorized", testViewRepo1CloneLinkAuthorized)
t.Run("ViewRepoWithSymlinks", testViewRepoWithSymlinks)
t.Run("ViewFileInRepo", testViewFileInRepo)
t.Run("BlameFileInRepo", testBlameFileInRepo)
t.Run("ViewRepoDirectory", testViewRepoDirectory)
t.Run("ViewRepoDirectoryReadme", testViewRepoDirectoryReadme)
t.Run("ViewRepoSymlink", testViewRepoSymlink)
t.Run("MarkDownReadmeImage", testMarkDownReadmeImage)
t.Run("MarkDownReadmeImageSubfolder", testMarkDownReadmeImageSubfolder)
t.Run("GeneratedSourceLink", testGeneratedSourceLink)
t.Run("ViewCommit", testViewCommit)
}
func testViewRepoPublic(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
repoTopics := htmlDoc.doc.Find("#repo-topics").Children()
repoSummary := htmlDoc.doc.Find(".repository-summary").Children()
assert.True(t, repoTopics.HasClass("repo-topic"))
assert.True(t, repoSummary.HasClass("repository-menu"))
req = NewRequest(t, "GET", "/org3/repo3")
MakeRequest(t, req, http.StatusNotFound)
session = loginUser(t, "user1")
session.MakeRequest(t, req, http.StatusNotFound)
}
func testViewRepoWithCache(t *testing.T) {
defer tests.PrintCurrentTest(t)()
testView := func(t *testing.T) {
req := NewRequest(t, "GET", "/org3/repo3")
session := loginUser(t, "user2")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
files := htmlDoc.doc.Find("#repo-files-table .repo-file-item")
type file struct {
fileName string
commitID string
commitMsg string
commitTime string
}
var items []file
files.Each(func(i int, s *goquery.Selection) {
tds := s.Find(".repo-file-cell")
var f file
tds.Each(func(i int, s *goquery.Selection) {
switch i {
case 0:
f.fileName = strings.TrimSpace(s.Text())
case 1:
a := s.Find("a")
f.commitMsg = strings.TrimSpace(a.Text())
l, _ := a.Attr("href")
f.commitID = path.Base(l)
}
})
// convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC"
htmlTimeString, _ := s.Find("relative-time").Attr("datetime")
htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString)
f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123)
items = append(items, f)
})
commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123)
assert.Equal(t, []file{
{
fileName: "doc",
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
commitMsg: "init project",
commitTime: commitT,
},
{
fileName: "README.md",
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
commitMsg: "init project",
commitTime: commitT,
},
}, items)
}
// FIXME: these test don't seem quite right, no enough assert
// no last commit cache
testView(t)
// enable last commit cache for all repositories
defer test.MockVariableValue(&setting.CacheService.LastCommit.CommitsCount, 0)()
// first view will not hit the cache
testView(t)
// second view will hit the cache
testView(t)
}
func testViewRepoPrivate(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/org3/repo3")
MakeRequest(t, req, http.StatusNotFound)
t.Run("OrgMemberAccess", func(t *testing.T) {
req = NewRequest(t, "GET", "/org3/repo3")
session := loginUser(t, "user4")
resp := session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), `<div id="repo-files-table"`)
})
t.Run("PublicAccess-AnonymousAccess", func(t *testing.T) {
session := loginUser(t, "user1")
// set unit code to "anonymous read"
req = NewRequestWithValues(t, "POST", "/org3/repo3/settings/public_access", map[string]string{
"repo-unit-access-" + strconv.Itoa(int(unit.TypeCode)): "anonymous-read",
})
session.MakeRequest(t, req, http.StatusSeeOther)
// try to "anonymous read" (ok)
req = NewRequest(t, "GET", "/org3/repo3")
resp := MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), `<span class="ui basic orange label">Public Access</span>`)
// remove "anonymous read"
req = NewRequest(t, "POST", "/org3/repo3/settings/public_access")
session.MakeRequest(t, req, http.StatusSeeOther)
// try to "anonymous read" (not found)
req = NewRequest(t, "GET", "/org3/repo3")
MakeRequest(t, req, http.StatusNotFound)
})
}
func testViewRepo1CloneLinkAnonymous(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
_, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.False(t, exists)
link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, "tea clone user2/repo1", link)
}
func testViewRepo1CloneLinkAuthorized(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.True(t, exists, "The template has changed")
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
assert.Equal(t, sshURL, link)
link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, "tea clone user2/repo1", link)
}
func testViewRepoWithSymlinks(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockVariableValue(&setting.UI.FileIconTheme, "basic")()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo20.git")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
files := htmlDoc.doc.Find("#repo-files-table .repo-file-cell.name")
items := files.Map(func(i int, s *goquery.Selection) string {
cls, _ := s.Find("SVG").Attr("class")
file := strings.Trim(s.Find("A").Text(), " \t\n")
return fmt.Sprintf("%s: %s", file, cls)
})
assert.Len(t, items, 5)
assert.Equal(t, "a: svg octicon-file-directory-fill", items[0])
assert.Equal(t, "link_b: svg octicon-file-directory-symlink", items[1])
assert.Equal(t, "link_d: svg octicon-file-symlink-file", items[2])
assert.Equal(t, "link_hi: svg octicon-file-symlink-file", items[3])
assert.Equal(t, "link_link: svg octicon-file-symlink-file", items[4])
}
// TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file
func testViewFileInRepo(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/README.md")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
description := htmlDoc.doc.Find(".repo-description")
repoTopics := htmlDoc.doc.Find("#repo-topics")
repoSummary := htmlDoc.doc.Find(".repository-summary")
fileSize := htmlDoc.Find("div.file-info-entry > .file-info-size").Text()
assert.Equal(t, 0, description.Length())
assert.Equal(t, 0, repoTopics.Length())
assert.Equal(t, 0, repoSummary.Length())
assert.Equal(t, "30 B", fileSize)
}
// TestBlameFileInRepo repo description, topics and summary should not be displayed when running blame on a file
func testBlameFileInRepo(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/blame/branch/master/README.md")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
description := htmlDoc.doc.Find(".repo-description")
repoTopics := htmlDoc.doc.Find("#repo-topics")
repoSummary := htmlDoc.doc.Find(".repository-summary")
assert.Equal(t, 0, description.Length())
assert.Equal(t, 0, repoTopics.Length())
assert.Equal(t, 0, repoSummary.Length())
}
// TestViewRepoDirectory repo description, topics and summary should not be displayed when within a directory
func testViewRepoDirectory(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo20/src/branch/master/a")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
description := htmlDoc.doc.Find(".repo-description")
repoTopics := htmlDoc.doc.Find("#repo-topics")
repoSummary := htmlDoc.doc.Find(".repository-summary")
repoFilesTable := htmlDoc.doc.Find("#repo-files-table")
assert.NotEmpty(t, repoFilesTable.Nodes)
assert.Zero(t, description.Length())
assert.Zero(t, repoTopics.Length())
assert.Zero(t, repoSummary.Length())
}
// ensure that the all the different ways to find and render a README work
func testViewRepoDirectoryReadme(t *testing.T) {
defer tests.PrintCurrentTest(t)()
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
repo56 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "readme-test"})
const regular, symlink = git.EntryModeBlob, git.EntryModeSymlink
allGitea := []git.FastImportFile{
{regular, ".gitea/README.en.md", "This is .gitea/README.en.md"},
{regular, ".gitea/README.md", "This is .gitea/README.md"},
{regular, ".gitea/README", "This is .gitea/README"},
}
allGithub := []git.FastImportFile{
{regular, ".github/README.en.md", "This is .github/README.en.md"},
{regular, ".github/README.md", "This is .github/README.md"},
{regular, ".github/README", "This is .github/README"},
}
allRoot := []git.FastImportFile{
{regular, "README.en.md", "This is README.en.md"},
{regular, "README.md", "This is README.md"},
{regular, "README", "This is README"},
}
allDocs := []git.FastImportFile{
{regular, "docs/README.en.md", "This is docs/README.en.md"},
{regular, "docs/README.md", "This is docs/README.md"},
{regular, "docs/README", "This is docs/README"},
}
combineFiles := func(slices ...[]git.FastImportFile) (res []git.FastImportFile) {
for _, s := range slices {
res = append(res, s...)
}
return res
}
err := git.ForceFastImport(t.Context(), repo56.CodeStorageRepo(), []git.FastImportCommit{
{Ref: "refs/heads/master", Message: "init master", Files: []git.FastImportFile{{regular, "README.md", "The cake is a lie."}}},
{Ref: "refs/heads/txt", Message: "init txt", Files: []git.FastImportFile{{regular, "README.txt", "My spoon is too big."}}},
{Ref: "refs/heads/plain", Message: "init plain", Files: []git.FastImportFile{{regular, "README", "Birken my stocks gee howdy"}}},
{Ref: "refs/heads/i18n", Message: "init i18n", Files: []git.FastImportFile{{regular, "README.zh.md", "你好世界"}}},
{Ref: "refs/heads/subdir", Message: "init subdir", Files: []git.FastImportFile{{regular, "libcake/README.md", "Four pints of sugar."}}},
{Ref: "refs/heads/special-subdir-docs", Message: "init special-subdir-docs", Files: []git.FastImportFile{{regular, "docs/README.md", "This is in docs/"}}},
{Ref: "refs/heads/special-subdir-.gitea", Message: "init special-subdir-.gitea", Files: []git.FastImportFile{{regular, ".gitea/README.md", "This is in .gitea/"}}},
{Ref: "refs/heads/special-subdir-.github", Message: "init special-subdir-.github", Files: []git.FastImportFile{{regular, ".github/README.md", "This is in .github/"}}},
{Ref: "refs/heads/special-subdir-nested", Message: "init special-subdir-nested", Files: []git.FastImportFile{
{regular, ".gitea/docs/README.md", "This is in docs/"},
{regular, "subproject/.github/README.md", "This is in .github/"},
}},
{Ref: "refs/heads/symlink", Message: "init symlink", Files: []git.FastImportFile{
{symlink, ".github/README.md", "../some/other/path/awefulcake.txt"},
{symlink, "some/README.txt", "other/path/awefulcake.txt"},
{regular, "some/other/path/awefulcake.txt", "This is in some/other/path"},
{symlink, "trampoline", "up/back/down/down"},
{symlink, "up/back/down/down/README.md", "../../../../up/down/left/reelmein"},
{regular, "up/down/left/reelmein", "It's a me, mario"},
}},
{Ref: "refs/heads/symlink-loop", Message: "init symlink-loop", Files: []git.FastImportFile{
{symlink, "README.md", "trampoline"},
{symlink, "some/README.txt", "other/path/awefulcake.txt"},
{symlink, "some/other/path/awefulcake.txt", "../../../README.md"},
{symlink, "trampoline", "README.md"},
}},
{Ref: "refs/heads/sp-ace", Message: "init sp-ace", Files: []git.FastImportFile{{regular, "read me", "The cake is a lie."}}},
{Ref: "refs/heads/fallbacks", Message: "init fallbacks", Files: combineFiles(allGitea, allGithub, allRoot, allDocs)},
{Ref: "refs/heads/fallbacks2", Message: "init fallbacks2", Files: combineFiles(allGitea[1:], allGithub, allRoot, allDocs)},
{Ref: "refs/heads/fallbacks3", Message: "init fallbacks3", Files: combineFiles(allGitea[2:], allGithub, allRoot, allDocs)},
{Ref: "refs/heads/fallbacks4", Message: "init fallbacks4", Files: combineFiles(allGithub, allRoot, allDocs)},
{Ref: "refs/heads/fallbacks5", Message: "init fallbacks5", Files: combineFiles(allGithub[1:], allRoot, allDocs)},
{Ref: "refs/heads/fallbacks6", Message: "init fallbacks6", Files: combineFiles(allGithub[2:], allRoot, allDocs)},
{Ref: "refs/heads/fallbacks7", Message: "init fallbacks7", Files: combineFiles(allRoot, allDocs)},
{Ref: "refs/heads/fallbacks8", Message: "init fallbacks8", Files: combineFiles(allRoot[1:], allDocs)},
{Ref: "refs/heads/fallbacks9", Message: "init fallbacks9", Files: combineFiles(allRoot[2:], allDocs)},
{Ref: "refs/heads/fallbacks10", Message: "init fallbacks10", Files: combineFiles(allDocs)},
{Ref: "refs/heads/fallbacks11", Message: "init fallbacks11", Files: combineFiles(allDocs[1:])},
{Ref: "refs/heads/fallbacks12", Message: "init fallbacks12", Files: combineFiles(allDocs[2:])},
{Ref: "refs/heads/fallbacks-broken-symlinks", Message: "init fallbacks-broken-symlinks", Files: []git.FastImportFile{
{symlink, ".gitea/README.md", "non-existent-file"},
{symlink, ".github/README.md", "non-existent-file"},
{symlink, "README.md", "non-existent-file"},
{regular, "docs/README", "This is docs/README"},
}},
})
require.NoError(t, err)
// there are many combinations:
// - READMEs can be .md, .txt, or have no extension
// - READMEs can be tagged with a language and even a country code
// - READMEs can be stored in "docs/", ".gitea/", or ".github/"
// - READMEs can be symlinks to other files
// - READMEs can be broken symlinks which should not render
//
// this doesn't cover all possible cases, just the major branches of the code
session := loginUser(t, "user2")
check := func(name, url, expectedFilename, expectedReadmeType, expectedContent string) {
t.Run(name, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", url)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
readmeName := htmlDoc.doc.Find("h4.file-header")
readmeContent := htmlDoc.doc.Find(".file-view") // TODO: add a id="readme" to the output to make this test more precise
readmeType, _ := readmeContent.Attr("class")
assert.Equal(t, expectedFilename, strings.TrimSpace(readmeName.Text()))
assert.Contains(t, readmeType, expectedReadmeType)
assert.Contains(t, readmeContent.Text(), expectedContent)
})
}
// viewing the top level
check("Home", "/user2/readme-test/", "README.md", "markdown", "The cake is a lie.")
// viewing different file extensions
check("md", "/user2/readme-test/src/branch/master/", "README.md", "markdown", "The cake is a lie.")
check("txt", "/user2/readme-test/src/branch/txt/", "README.txt", "plain-text", "My spoon is too big.")
check("plain", "/user2/readme-test/src/branch/plain/", "README", "plain-text", "Birken my stocks gee howdy")
check("i18n", "/user2/readme-test/src/branch/i18n/", "README.zh.md", "markdown", "你好世界")
// using HEAD ref
check("branch-HEAD", "/user2/readme-test/src/branch/HEAD/", "README.md", "markdown", "The cake is a lie.")
check("commit-HEAD", "/user2/readme-test/src/commit/HEAD/", "README.md", "markdown", "The cake is a lie.")
// viewing different subdirectories
check("subdir", "/user2/readme-test/src/branch/subdir/libcake", "README.md", "markdown", "Four pints of sugar.")
check("docs-direct", "/user2/readme-test/src/branch/special-subdir-docs/docs/", "README.md", "markdown", "This is in docs/")
check("docs", "/user2/readme-test/src/branch/special-subdir-docs/", "docs/README.md", "markdown", "This is in docs/")
check(".gitea", "/user2/readme-test/src/branch/special-subdir-.gitea/", ".gitea/README.md", "markdown", "This is in .gitea/")
check(".github", "/user2/readme-test/src/branch/special-subdir-.github/", ".github/README.md", "markdown", "This is in .github/")
// symlinks are subtle:
// - they should be able to handle going a reasonable number of times up and down in the tree
// - they shouldn't get stuck on link cycles
// - they should determine the filetype based on the name of the link, not the target
check("symlink", "/user2/readme-test/src/branch/symlink/", ".github/README.md", "markdown", "This is in some/other/path")
check("symlink-multiple", "/user2/readme-test/src/branch/symlink/some/", "README.txt", "plain-text", "This is in some/other/path")
check("symlink-up-and-down", "/user2/readme-test/src/branch/symlink/up/back/down/down", "README.md", "markdown", "It's a me, mario")
// testing fallback rules
// READMEs are searched in this order:
// - directories: .gitea -> .github -> (root) -> docs
// - extensions: longer to shorter, non-English to English, e.g.: ".zh-cn.md" -> ".zh.md" -> "_zh.md" -> "en.md" -> ".md" -> (no-ext)
// and a broken/looped symlink counts as not existing at all and should be skipped.
// again, this doesn't cover all cases, but it covers a few
check("fallback/top", "/user2/readme-test/src/branch/fallbacks/", ".gitea/README.en.md", "markdown", "This is .gitea/README.en.md")
check("fallback/2", "/user2/readme-test/src/branch/fallbacks2/", ".gitea/README.md", "markdown", "This is .gitea/README.md")
check("fallback/3", "/user2/readme-test/src/branch/fallbacks3/", ".gitea/README", "plain-text", "This is .gitea/README")
check("fallback/4", "/user2/readme-test/src/branch/fallbacks4/", ".github/README.en.md", "markdown", "This is .github/README.en.md")
check("fallback/5", "/user2/readme-test/src/branch/fallbacks5/", ".github/README.md", "markdown", "This is .github/README.md")
check("fallback/6", "/user2/readme-test/src/branch/fallbacks6/", ".github/README", "plain-text", "This is .github/README")
check("fallback/7", "/user2/readme-test/src/branch/fallbacks7/", "README.en.md", "markdown", "This is README.en.md")
check("fallback/8", "/user2/readme-test/src/branch/fallbacks8/", "README.md", "markdown", "This is README.md")
check("fallback/9", "/user2/readme-test/src/branch/fallbacks9/", "README", "plain-text", "This is README")
check("fallback/10", "/user2/readme-test/src/branch/fallbacks10/", "docs/README.en.md", "markdown", "This is docs/README.en.md")
check("fallback/11", "/user2/readme-test/src/branch/fallbacks11/", "docs/README.md", "markdown", "This is docs/README.md")
check("fallback/12", "/user2/readme-test/src/branch/fallbacks12/", "docs/README", "plain-text", "This is docs/README")
// this case tests that broken symlinks count as missing files, instead of rendering their contents
check("fallbacks-broken-symlinks", "/user2/readme-test/src/branch/fallbacks-broken-symlinks/", "docs/README", "plain-text", "This is docs/README")
// some cases that should NOT render a README
// - /readme
// - /.github/docs/README.md
// - a symlink loop
missing := func(name, url string) {
t.Run("missing/"+name, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", url)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
_, exists := htmlDoc.doc.Find(".file-view").Attr("class")
assert.False(t, exists, "README should not have rendered")
})
}
missing("sp-ace", "/user2/readme-test/src/branch/sp-ace/")
missing("nested-special", "/user2/readme-test/src/branch/special-subdir-nested/subproject") // the special sub-dirs should only trigger on the repo root
missing("special-subdir-nested", "/user2/readme-test/src/branch/special-subdir-nested/")
missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
}
func testViewRepoSymlink(t *testing.T) {
session := loginUser(t, "user2")
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 2, ID: 1})
err := git.ForceFastImport(t.Context(), repo.CodeStorageRepo(), []git.FastImportCommit{
{
Ref: "refs/heads/symlink", Message: "test", Files: []git.FastImportFile{
{git.EntryModeSymlink, "README.md", "some/other/path/awefulcake.txt"},
{git.EntryModeBlob, "some/other/path/awefulcake.txt", "text content"},
},
},
})
require.NoError(t, err)
req := NewRequest(t, "GET", "/user2/repo1/src/branch/symlink")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
AssertHTMLElement(t, htmlDoc, ".entry-symbol-link", true)
followSymbolLinkHref := htmlDoc.Find(".entry-symbol-link").AttrOr("href", "")
require.Equal(t, "/user2/repo1/src/branch/symlink/README.md?follow_symlink=1", followSymbolLinkHref)
req = NewRequest(t, "GET", followSymbolLinkHref)
resp = session.MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, "/user2/repo1/src/branch/symlink/some/other/path/awefulcake.txt?follow_symlink=1", resp.Header().Get("Location"))
}
func testMarkDownReadmeImage(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/src/branch/home-md-img-check")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
src, exists := htmlDoc.doc.Find(`.markdown img`).Attr("src")
assert.True(t, exists, "Image not found in README")
assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src)
req = NewRequest(t, "GET", "/user2/repo1/src/branch/home-md-img-check/README.md")
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
src, exists = htmlDoc.doc.Find(`.markdown img`).Attr("src")
assert.True(t, exists, "Image not found in markdown file")
assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src)
}
func testMarkDownReadmeImageSubfolder(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
// this branch has the README in the special docs/README.md location
req := NewRequest(t, "GET", "/user2/repo1/src/branch/sub-home-md-img-check")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
src, exists := htmlDoc.doc.Find(`.markdown img`).Attr("src")
assert.True(t, exists, "Image not found in README")
assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src)
req = NewRequest(t, "GET", "/user2/repo1/src/branch/sub-home-md-img-check/docs/README.md")
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
src, exists = htmlDoc.doc.Find(`.markdown img`).Attr("src")
assert.True(t, exists, "Image not found in markdown file")
assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src)
}
func testGeneratedSourceLink(t *testing.T) {
defer tests.PrintCurrentTest(t)()
t.Run("Rendered file", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/README.md?display=source")
resp := MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
dataURL, exists := doc.doc.Find(".copy-line-permalink").Attr("data-url")
assert.True(t, exists)
assert.Equal(t, "/user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md?display=source", dataURL)
dataURL, exists = doc.doc.Find(".ref-in-new-issue").Attr("data-url-param-body-link")
assert.True(t, exists)
assert.Equal(t, setting.AppURL+"user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md?display=source", dataURL)
})
t.Run("Non-Rendered file", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user27")
req := NewRequest(t, "GET", "/user27/repo49/src/branch/master/test/test.txt")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
dataURL, exists := doc.doc.Find(".copy-line-permalink").Attr("data-url")
assert.True(t, exists)
assert.Equal(t, "/user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL)
dataURL, exists = doc.doc.Find(".ref-in-new-issue").Attr("data-url-param-body-link")
assert.True(t, exists)
assert.Equal(t, setting.AppURL+"user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL)
})
}
func testViewCommit(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/user2/repo1/commit/0123456789012345678901234567890123456789")
req.Header.Add("Accept", "text/html")
resp := MakeRequest(t, req, http.StatusNotFound)
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "non-existing commit should render 404 page")
}
// TestGenerateRepository the test cannot succeed when moved as a unit test
func TestGenerateRepository(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// a successful generate from template
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo44 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44})
t.Run("Success", func(t *testing.T) {
tmplRepoLabels := []*issues_model.Label{
{RepoID: 44, Name: "priority/high", Exclusive: true, ExclusiveOrder: 2, Color: "#ee0000", Description: "desc-high"},
{RepoID: 44, Name: "priority/low", Exclusive: true, ExclusiveOrder: 1, Color: "#0000ee", Description: "desc-low"},
}
require.NoError(t, issues_model.NewLabels(t.Context(), tmplRepoLabels...))
generatedRepo, err := repo_service.GenerateRepository(t.Context(), user2, user2, repo44, repo_service.GenerateRepoOptions{
Name: "generated-from-template-44",
GitContent: true,
IssueLabels: true,
})
require.NoError(t, err)
require.NotNil(t, generatedRepo)
exist, err := git.IsRepositoryExist(t.Context(), generatedRepo)
require.NoError(t, err)
require.True(t, exist)
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name})
generatedLabels, err := issues_model.GetLabelsByRepoID(t.Context(), generatedRepo.ID, "", db.ListOptions{})
require.NoError(t, err)
require.Len(t, generatedLabels, len(tmplRepoLabels))
for i, tmplLabel := range tmplRepoLabels {
genLabel := generatedLabels[i]
assert.Equal(t, tmplLabel.Name, genLabel.Name)
assert.Equal(t, tmplLabel.Exclusive, genLabel.Exclusive)
assert.Equal(t, tmplLabel.ExclusiveOrder, genLabel.ExclusiveOrder)
assert.Equal(t, tmplLabel.Color, genLabel.Color)
assert.Equal(t, tmplLabel.Description, genLabel.Description)
}
err = repo_service.DeleteRepositoryDirectly(t.Context(), generatedRepo.ID)
assert.NoError(t, err)
})
t.Run("Failure", func(t *testing.T) {
// a failed creating because some mock data
// create the repository directory so that the creation will fail after database record created.
testFailureRepoName := "generated-from-template-44"
testFailureRepo := gitrepo.CodeRepoByName(user2.Name, testFailureRepoName)
testFailurePath := gitrepo.RepoLocalPath(testFailureRepo)
assert.NoError(t, os.MkdirAll(testFailurePath, os.ModePerm))
generatedRepoFailure, err := repo_service.GenerateRepository(t.Context(), user2, user2, repo44, repo_service.GenerateRepoOptions{
Name: testFailureRepoName,
GitContent: true,
})
assert.Nil(t, generatedRepoFailure)
assert.Error(t, err)
// assert the cleanup is successful
unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: testFailureRepoName})
exist, err := git.IsRepositoryExist(t.Context(), testFailureRepo)
assert.NoError(t, err)
assert.False(t, exist)
})
}