// Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package repo import ( "fmt" "io" "net/http" "net/url" "testing" repo_model "gitea.dev/models/repo" "gitea.dev/models/unittest" "gitea.dev/modules/git" "gitea.dev/modules/test" "gitea.dev/services/contexttest" repo_service "gitea.dev/services/repository" wiki_service "gitea.dev/services/wiki" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( testWikiContent = "Wiki contents for unit tests" testWikiMessage = "Wiki commit message for unit tests" ) func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) (*git.Repository, *git.TreeEntry) { wikiRepo, err := git.OpenRepository(t.Context(), repo.WikiStorageRepo()) require.NoError(t, err) defer wikiRepo.Close() commit, err := wikiRepo.GetBranchCommit(t.Context(), "master") assert.NoError(t, err) entries, err := commit.Tree().ListEntries(t.Context(), wikiRepo) assert.NoError(t, err) for _, entry := range entries { if entry.Name() == wiki_service.WebPathToGitPath(wikiName) { return wikiRepo, entry } } return wikiRepo, nil } func wikiContent(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) string { wikiRepo, entry := wikiEntry(t, repo, wikiName) if !assert.NotNil(t, entry) { return "" } reader, err := entry.Blob(wikiRepo).DataAsync(t.Context()) assert.NoError(t, err) defer reader.Close() bytes, err := io.ReadAll(reader) assert.NoError(t, err) return string(bytes) } func assertWikiExists(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) { _, entry := wikiEntry(t, repo, wikiName) assert.NotNil(t, entry) } func assertWikiNotExists(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) { _, entry := wikiEntry(t, repo, wikiName) assert.Nil(t, entry) } func assertPagesMetas(t *testing.T, expectedNames []string, metas any) { pageMetas, ok := metas.([]PageMeta) require.True(t, ok) require.Len(t, pageMetas, len(expectedNames)) for i, pageMeta := range pageMetas { assert.Equal(t, expectedNames[i], pageMeta.Name) } } func TestWiki(t *testing.T) { unittest.PrepareTestEnv(t) t.Run("Home", func(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki") ctx.SetPathParam("*", "Home") contexttest.LoadRepo(t, ctx, 1) Wiki(ctx) assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, "Home", ctx.Data["Title"]) assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"]) }) t.Run("Image", func(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/jpeg.jpg") ctx.SetPathParam("*", "jpeg.jpg") contexttest.LoadRepo(t, ctx, 1) Wiki(ctx) assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.Equal(t, "/user2/repo1/wiki/raw/jpeg.jpg", ctx.Resp.Header().Get("Location")) }) t.Run("Pages", testWikiPages) t.Run("NewWiki", testNewWiki) t.Run("NewWikiPost", testNewWikiPost) t.Run("NewWikiPostReservedName", testNewWikiPostReservedName) t.Run("EditWiki", testEditWiki) t.Run("EditWikiPost", testEditWikiPost) t.Run("DeletePost", testDeleteWikiPagePost) t.Run("Raw", testWikiRaw) t.Run("DefaultWikiBranch", testDefaultWikiBranch) } func testWikiPages(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/?action=_pages") contexttest.LoadRepo(t, ctx, 1) WikiPages(ctx) assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"]) } func testNewWiki(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/?action=_new") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) NewWiki(ctx) assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, ctx.Tr("repo.wiki.new_page"), ctx.Data["Title"]) } func testNewWikiPost(t *testing.T) { for _, title := range []string{ "New page", "&&&&", } { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/?action=_new") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) contexttest.MockRequestPostForm(ctx.Req, url.Values{ "title": []string{title}, "content": []string{testWikiContent}, "message": []string{testWikiMessage}, }) NewWikiPost(ctx) assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) assert.Equal(t, testWikiContent, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) } } func testNewWikiPostReservedName(t *testing.T) { ctx, resp := contexttest.MockContext(t, "user2/repo1/wiki/?action=_new") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) contexttest.MockRequestPostForm(ctx.Req, url.Values{ "title": []string{"_edit"}, "content": []string{testWikiContent}, "message": []string{testWikiMessage}, }) NewWikiPost(ctx) assert.Equal(t, http.StatusBadRequest, ctx.Resp.WrittenStatus()) assert.EqualValues(t, ctx.Tr("repo.wiki.reserved_page", "_edit"), test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) assertWikiNotExists(t, ctx.Repo.Repository, "_edit") } func testEditWiki(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/Home?action=_edit") ctx.SetPathParam("*", "Home") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) EditWiki(ctx) assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, "Home", ctx.Data["Title"]) assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["WikiEditContent"]) ctx, _ = contexttest.MockContext(t, "user2/repo1/wiki/jpeg.jpg?action=_edit") ctx.SetPathParam("*", "jpeg.jpg") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) EditWiki(ctx) assert.Equal(t, http.StatusForbidden, ctx.Resp.WrittenStatus()) } func testEditWikiPost(t *testing.T) { const existingPageTitle = "Page With Image" for _, title := range []string{existingPageTitle, "New/"} { unittest.PrepareTestEnv(t) ctx, _ := contexttest.MockContext(t, fmt.Sprintf("/user2/repo1/wiki/%s?action=_new", url.PathEscape(existingPageTitle))) ctx.SetPathParam("*", existingPageTitle) contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) contexttest.MockRequestPostForm(ctx.Req, url.Values{ "title": []string{title}, "content": []string{testWikiContent}, "message": []string{testWikiMessage}, }) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", existingPageTitle)) EditWikiPost(ctx) assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) assert.Equal(t, testWikiContent, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) if title != existingPageTitle { assertWikiNotExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", existingPageTitle)) } } } func testDeleteWikiPagePost(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/Home?action=_delete") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) DeleteWikiPagePost(ctx) assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assertWikiNotExists(t, ctx.Repo.Repository, "Home") } func testWikiRaw(t *testing.T) { for filepath, filetype := range map[string]string{ "jpeg.jpg": "image/jpeg", "images/jpeg.jpg": "image/jpeg", "files/Non-Renderable-File.zip": "application/octet-stream", "Page With Spaced Name": "text/plain; charset=utf-8", "Page-With-Spaced-Name": "text/plain; charset=utf-8", "Page With Spaced Name.md": "", // there is no "Page With Spaced Name.md" in repo "Page-With-Spaced-Name.md": "text/plain; charset=utf-8", } { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/raw/"+url.PathEscape(filepath)) ctx.SetPathParam("*", filepath) contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) WikiRaw(ctx) if filetype == "" { assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) } else { assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) assert.Equal(t, filetype, ctx.Resp.Header().Get("Content-Type"), "filepath: %s", filepath) } } } func testDefaultWikiBranch(t *testing.T) { // repo with no wiki repoWithNoWiki := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) assert.False(t, repo_service.HasWiki(t.Context(), repoWithNoWiki)) assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(t.Context(), repoWithNoWiki, "main")) // repo with wiki assert.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime( t.Context(), &repo_model.Repository{ID: 1, DefaultWikiBranch: "wrong-branch"}, "default_wiki_branch", ), ) ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki") ctx.SetPathParam("*", "Home") contexttest.LoadRepo(t, ctx, 1) assert.Equal(t, "wrong-branch", ctx.Repo.Repository.DefaultWikiBranch) Wiki(ctx) // after the visiting, the out-of-sync database record will update the branch name to "master" repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) assert.Equal(t, "master", ctx.Repo.Repository.DefaultWikiBranch) // invalid branch name should fail assert.Error(t, wiki_service.ChangeDefaultWikiBranch(t.Context(), repo, "the bad name")) repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) assert.Equal(t, "master", repo.DefaultWikiBranch) // the same branch name, should succeed (actually a no-op) assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(t.Context(), repo, "master")) repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) assert.Equal(t, "master", repo.DefaultWikiBranch) // change to another name assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(t.Context(), repo, "main")) repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) assert.Equal(t, "main", repo.DefaultWikiBranch) }