jojo/tests/integration/wiki_test.go
2026-05-12 13:13:03 +02:00

228 lines
7.8 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
auth_model "forgejo.org/models/auth"
unit_model "forgejo.org/models/unit"
"forgejo.org/models/unittest"
user_model "forgejo.org/models/user"
"forgejo.org/modules/git"
api "forgejo.org/modules/structs"
"forgejo.org/modules/util"
"forgejo.org/tests"
"forgejo.org/tests/forgery"
"github.com/PuerkitoBio/goquery"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func assertFileExist(t *testing.T, p string) {
exist, err := util.IsExist(p)
require.NoError(t, err)
if !assert.True(t, exist) {
dir := filepath.Dir(p)
t.Logf("Listing files that were present in dir path %s", dir)
entries, err := os.ReadDir(dir)
require.NoError(t, err)
for _, e := range entries {
t.Logf("file in path %s -> %s", dir, e.Name())
}
t.Logf("End of %d entries in directory %s", len(entries), dir)
}
}
func assertFileEqual(t *testing.T, p string, content []byte) {
bs, err := os.ReadFile(p)
require.NoError(t, err)
assert.Equal(t, content, bs)
}
type (
RepoWikiMethod string
RepoWikiAuth string
RepoWikiTarget string
RepoWikiOperation string
)
const (
RepoWikiSSH RepoWikiMethod = "SSH"
RepoWikiHTTP RepoWikiMethod = "HTTP"
RepoWikiAnonymous RepoWikiAuth = "Anonymous"
RepoWikiAuthenticated RepoWikiAuth = "Authenticated"
RepoWikiAuthenticatedNonOwnerUser RepoWikiAuth = "Authenticated-NonOwner"
RepoWikiPublic RepoWikiTarget = "Public"
RepoWikiPrivate RepoWikiTarget = "Private"
RepoWikiRead RepoWikiOperation = "Read"
RepoWikiWrite RepoWikiOperation = "Write"
)
func TestRepoWikiGitOperation(t *testing.T) {
onApplicationRun(t, func(t *testing.T, u *url.URL) {
for _, method := range []RepoWikiMethod{RepoWikiSSH, RepoWikiHTTP} {
for _, auth := range []RepoWikiAuth{RepoWikiAnonymous, RepoWikiAuthenticated, RepoWikiAuthenticatedNonOwnerUser} {
for _, target := range []RepoWikiTarget{RepoWikiPublic, RepoWikiPrivate} {
for _, operation := range []RepoWikiOperation{RepoWikiRead, RepoWikiWrite} {
t.Run(fmt.Sprintf("%s/%s/%s/%s", method, auth, target, operation), func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
doRepoWikiGitOperation(t, u, method, auth, target, operation)
})
}
}
}
}
})
}
func doRepoWikiGitOperation(t *testing.T, serverURL *url.URL, method RepoWikiMethod, auth RepoWikiAuth, target RepoWikiTarget, operation RepoWikiOperation) {
repo := "repo1"
if target == RepoWikiPrivate {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
privateRepo := forgery.CreateRepository(t, user2, &forgery.CreateRepositoryOptions{
IsPrivate: true,
})
forgery.EnableRepoUnit(t, privateRepo, unit_model.TypeWiki, nil)
session := loginUser(t, user2.LoginName)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", user2.LoginName, privateRepo.Name)
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{
Title: "Page With Image",
ContentBase64: base64.StdEncoding.EncodeToString([]byte("# Page With Image\n\n![Gitea Logo](./raw/jpeg.jpg)\n")),
Message: "",
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusCreated)
repo = privateRepo.Name
}
dstPath := t.TempDir()
r := fmt.Sprintf("%suser2/%s.wiki.git", serverURL.String(), repo)
testURL, err := url.Parse(r)
require.NoError(t, err)
if method == RepoWikiHTTP {
switch auth {
case RepoWikiAnonymous:
// no-op
case RepoWikiAuthenticated:
testURL.User = url.UserPassword("user2", userPassword)
case RepoWikiAuthenticatedNonOwnerUser:
testURL.User = url.UserPassword("user20", userPassword)
default:
t.Fatalf("unexpected auth = %s", auth)
}
doRepoWikiGitOperationInner(t, testURL, dstPath, auth, target, operation)
} else if method == RepoWikiSSH {
var user string
switch auth {
case RepoWikiAnonymous:
t.Skip() // anonymous ssh is not supported
case RepoWikiAuthenticated:
user = "user2" // owner of the repo
case RepoWikiAuthenticatedNonOwnerUser:
user = "user20" // not the owner of the repo, not a collaborator
default:
t.Fatalf("unexpected auth = %s", auth)
}
keyname := "my-testing-key"
withKeyFile(t, keyname, func(keyFile string) {
baseAPITestContext := NewAPITestContext(t, user, repo, auth_model.AccessTokenScopeWriteUser)
t.Run("CreateUserKey", doAPICreateUserKey(baseAPITestContext, fmt.Sprintf("test-key-%s", uuid.New().String()), keyFile, func(t *testing.T, pk api.PublicKey) {}))
baseAPITestContext.Username = "user2" // target repo owner to compose URLs
baseAPITestContext.Reponame = fmt.Sprintf("%s.wiki", repo)
testURL = createSSHUrl(baseAPITestContext.GitPath(), testURL)
doRepoWikiGitOperationInner(t, testURL, dstPath, auth, target, operation)
})
} else {
t.Fatalf("unexpected method = %s", method)
}
}
func doRepoWikiGitOperationInner(t *testing.T, gitURL *url.URL, dstPath string, auth RepoWikiAuth, target RepoWikiTarget, operation RepoWikiOperation) {
err := git.CloneWithArgs(t.Context(), git.AllowLFSFiltersArgs(), gitURL.String(), dstPath, git.CloneRepoOptions{})
if target == RepoWikiPrivate && (auth == RepoWikiAnonymous || auth == RepoWikiAuthenticatedNonOwnerUser) {
require.Error(t, err, "clone must fail; auth %s shouldn't be able to access private repo")
return // no other test conditions to satisfy if the clone failed
}
require.NoError(t, err, "clone must succeed; auth %s should be able to access a public repo")
assertFileExist(t, filepath.Join(dstPath, "Page-With-Image.md"))
assertFileEqual(t, filepath.Join(dstPath, "Page-With-Image.md"), []byte("# Page With Image\n\n![Gitea Logo](./raw/jpeg.jpg)\n"))
if operation == RepoWikiWrite {
f, err := os.OpenFile(filepath.Join(dstPath, "Home.md"), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
defer f.Close()
require.NoError(t, err)
_, err = io.WriteString(f, fmt.Sprintf("# Home Page Edited!\n%s", uuid.New().String()))
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
err = git.AddChanges(dstPath, true)
require.NoError(t, err)
err = git.CommitChanges(dstPath, git.CommitChangesOptions{Message: "Changes made!"})
require.NoError(t, err)
// don't use git.Push() because it doesn't support credential helper, and 'origin' would have had its URL saved
// with the creds stripped in dstPath so we need the credential helper to be configured.
cmd := git.NewCommand(t.Context())
if gitURL.Scheme == "http" {
_, credCleanup, err := cmd.AddAuthCredentialHelperForRemote(gitURL.String())
require.NoError(t, err)
defer credCleanup()
}
cmd.AddArguments("push", "origin")
stdout, stderr, err := cmd.RunStdString(&git.RunOpts{
Dir: dstPath,
Timeout: 2 * time.Second,
})
if auth == RepoWikiAuthenticated {
require.NoError(t, err, "stdout = %q, stderr = %q", stdout, stderr)
} else {
require.Error(t, err, "push must fail as authentication mode %s doesn't allow write, but succeeded. stdout = %q, stderr = %q", auth, stdout, stderr)
}
}
}
func Test_RepoWikiPages(t *testing.T) {
defer tests.PrepareTestEnv(t)()
url := "/user2/repo1/wiki/?action=_pages"
req := NewRequest(t, "GET", url)
resp := MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
expectedPagePaths := []string{
"Home", "Long-Page", "Page-With-Image", "Page-With-Spaced-Name", "Unescaped-File", "XSS",
}
doc.Find("tr").Each(func(i int, s *goquery.Selection) {
firstAnchor := s.Find("a").First()
href, _ := firstAnchor.Attr("href")
pagePath := strings.TrimPrefix(href, "/user2/repo1/wiki/")
assert.Equal(t, expectedPagePaths[i], pagePath)
})
}