forgejo/tests/integration/repofiles_change_test.go
Gusted b44dcf553c
[TESTS] Fix usage of LoadRepoCommit
It loads the Commit with a temporary open GitRepo. This is incorrect,
the GitRepo should be open as long as the Commit can be used. This
mainly removes the usage of this function as it's not needed.
2024-08-26 08:03:48 +02:00

495 lines
18 KiB
Go

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/url"
"path/filepath"
"strings"
"testing"
"time"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func getCreateRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
return &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "create",
TreePath: "new/file.txt",
ContentReader: strings.NewReader("This is a NEW file"),
},
},
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
Message: "Creates new/file.txt",
Author: nil,
Committer: nil,
}
}
func getUpdateRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
return &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "update",
TreePath: "README.md",
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
ContentReader: strings.NewReader("This is UPDATED content for the README file"),
},
},
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
Message: "Updates README.md",
Author: nil,
Committer: nil,
}
}
func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
return &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: "README.md",
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
},
},
LastCommitID: "",
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
Message: "Deletes README.md",
Author: &files_service.IdentityOptions{
Name: "Bob Smith",
Email: "bob@smith.com",
},
Committer: nil,
}
}
func getExpectedFileResponseForRepofilesDelete() *api.FileResponse {
// Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined
return &api.FileResponse{
Content: nil,
Commit: &api.FileCommitResponse{
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Bob Smith",
Email: "bob@smith.com",
},
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "Bob Smith",
Email: "bob@smith.com",
},
},
Message: "Deletes README.md\n",
},
Verification: &api.PayloadCommitVerification{
Verified: false,
Reason: "gpg.error.not_signed_commit",
Signature: "",
Payload: "",
},
}
}
func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) *api.FileResponse {
treePath := "new/file.txt"
encoding := "base64"
content := "VGhpcyBpcyBhIE5FVyBmaWxl"
selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath
gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885"
downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath
return &api.FileResponse{
Content: &api.ContentsResponse{
Name: filepath.Base(treePath),
Path: treePath,
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
LastCommitSHA: lastCommitSHA,
Type: "file",
Size: 18,
Encoding: &encoding,
Content: &content,
URL: &selfURL,
HTMLURL: &htmlURL,
GitURL: &gitURL,
DownloadURL: &downloadURL,
Links: &api.FileLinksResponse{
Self: &selfURL,
GitURL: &gitURL,
HTMLURL: &htmlURL,
},
},
Commit: &api.FileCommitResponse{
CommitMeta: api.CommitMeta{
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID,
SHA: commitID,
},
HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
Author: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Parents: []*api.CommitMeta{
{
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d",
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
},
},
Message: "Updates README.md\n",
Tree: &api.CommitMeta{
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc",
},
},
Verification: &api.PayloadCommitVerification{
Verified: false,
Reason: "gpg.error.not_signed_commit",
Signature: "",
Payload: "",
},
}
}
func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA string) *api.FileResponse {
encoding := "base64"
content := "VGhpcyBpcyBVUERBVEVEIGNvbnRlbnQgZm9yIHRoZSBSRUFETUUgZmlsZQ=="
selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + filename + "?ref=master"
htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + filename
gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/dbf8d00e022e05b7e5cf7e535de857de57925647"
downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + filename
return &api.FileResponse{
Content: &api.ContentsResponse{
Name: filename,
Path: filename,
SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647",
LastCommitSHA: lastCommitSHA,
Type: "file",
Size: 43,
Encoding: &encoding,
Content: &content,
URL: &selfURL,
HTMLURL: &htmlURL,
GitURL: &gitURL,
DownloadURL: &downloadURL,
Links: &api.FileLinksResponse{
Self: &selfURL,
GitURL: &gitURL,
HTMLURL: &htmlURL,
},
},
Commit: &api.FileCommitResponse{
CommitMeta: api.CommitMeta{
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID,
SHA: commitID,
},
HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
Author: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Parents: []*api.CommitMeta{
{
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d",
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
},
},
Message: "Updates README.md\n",
Tree: &api.CommitMeta{
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
SHA: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
},
},
Verification: &api.PayloadCommitVerification{
Verified: false,
Reason: "gpg.error.not_signed_commit",
Signature: "",
Payload: "",
},
}
}
func TestChangeRepoFilesForCreate(t *testing.T) {
// setup
onGiteaRun(t, func(t *testing.T, u *url.URL) {
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
opts := getCreateRepoFilesOptions(repo)
// test
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
// asserts
require.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
defer gitRepo.Close()
commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch)
lastCommit, _ := gitRepo.GetCommitByPath("new/file.txt")
expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID, lastCommit.ID.String())
assert.NotNil(t, expectedFileResponse)
if expectedFileResponse != nil {
assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0])
assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name)
}
})
}
func TestChangeRepoFilesForUpdate(t *testing.T) {
// setup
onGiteaRun(t, func(t *testing.T, u *url.URL) {
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
opts := getUpdateRepoFilesOptions(repo)
// test
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
// asserts
require.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(opts.NewBranch)
lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String())
assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0])
assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name)
})
}
func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) {
// setup
onGiteaRun(t, func(t *testing.T, u *url.URL) {
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
opts := getUpdateRepoFilesOptions(repo)
opts.Files[0].FromTreePath = "README.md"
opts.Files[0].TreePath = "README_new.md" // new file name, README_new.md
// test
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
// asserts
require.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(opts.NewBranch)
lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String())
// assert that the old file no longer exists in the last commit of the branch
fromEntry, err := commit.GetTreeEntryByPath(opts.Files[0].FromTreePath)
switch err.(type) {
case git.ErrNotExist:
// correct, continue
default:
t.Fatalf("expected git.ErrNotExist, got:%v", err)
}
toEntry, err := commit.GetTreeEntryByPath(opts.Files[0].TreePath)
require.NoError(t, err)
assert.Nil(t, fromEntry) // Should no longer exist here
assert.NotNil(t, toEntry) // Should exist here
// assert SHA has remained the same but paths use the new file name
assert.EqualValues(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA)
assert.EqualValues(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name)
assert.EqualValues(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path)
assert.EqualValues(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL)
assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
})
}
// Test opts with branch names removed, should get same results as above test
func TestChangeRepoFilesWithoutBranchNames(t *testing.T) {
// setup
onGiteaRun(t, func(t *testing.T, u *url.URL) {
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
opts := getUpdateRepoFilesOptions(repo)
opts.OldBranch = ""
opts.NewBranch = ""
// test
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
// asserts
require.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch)
lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String())
assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0])
})
}
func TestChangeRepoFilesForDelete(t *testing.T) {
onGiteaRun(t, testDeleteRepoFiles)
}
func testDeleteRepoFiles(t *testing.T, u *url.URL) {
// setup
unittest.PrepareTestEnv(t)
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
opts := getDeleteRepoFilesOptions(repo)
t.Run("Delete README.md file", func(t *testing.T) {
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
require.NoError(t, err)
expectedFileResponse := getExpectedFileResponseForRepofilesDelete()
assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity)
assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity)
assert.EqualValues(t, expectedFileResponse.Verification, filesResponse.Verification)
})
t.Run("Verify README.md has been deleted", func(t *testing.T) {
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
assert.Nil(t, filesResponse)
expectedError := "repository file does not exist [path: " + opts.Files[0].TreePath + "]"
assert.EqualError(t, err, expectedError)
})
}
// Test opts with branch names removed, same results
func TestChangeRepoFilesForDeleteWithoutBranchNames(t *testing.T) {
onGiteaRun(t, testDeleteRepoFilesWithoutBranchNames)
}
func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) {
// setup
unittest.PrepareTestEnv(t)
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
opts := getDeleteRepoFilesOptions(repo)
opts.OldBranch = ""
opts.NewBranch = ""
t.Run("Delete README.md without Branch Name", func(t *testing.T) {
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
require.NoError(t, err)
expectedFileResponse := getExpectedFileResponseForRepofilesDelete()
assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)
assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity)
assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity)
assert.EqualValues(t, expectedFileResponse.Verification, filesResponse.Verification)
})
}
func TestChangeRepoFilesErrors(t *testing.T) {
// setup
onGiteaRun(t, func(t *testing.T, u *url.URL) {
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
t.Run("bad branch", func(t *testing.T) {
opts := getUpdateRepoFilesOptions(repo)
opts.OldBranch = "bad_branch"
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
require.Error(t, err)
assert.Nil(t, filesResponse)
expectedError := "branch does not exist [name: " + opts.OldBranch + "]"
assert.EqualError(t, err, expectedError)
})
t.Run("bad SHA", func(t *testing.T) {
opts := getUpdateRepoFilesOptions(repo)
origSHA := opts.Files[0].SHA
opts.Files[0].SHA = "bad_sha"
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
assert.Nil(t, filesResponse)
require.Error(t, err)
expectedError := "sha does not match [given: " + opts.Files[0].SHA + ", expected: " + origSHA + "]"
assert.EqualError(t, err, expectedError)
})
t.Run("new branch already exists", func(t *testing.T) {
opts := getUpdateRepoFilesOptions(repo)
opts.NewBranch = "develop"
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
assert.Nil(t, filesResponse)
require.Error(t, err)
expectedError := "branch already exists [name: " + opts.NewBranch + "]"
assert.EqualError(t, err, expectedError)
})
t.Run("treePath is empty:", func(t *testing.T) {
opts := getUpdateRepoFilesOptions(repo)
opts.Files[0].TreePath = ""
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
assert.Nil(t, filesResponse)
require.Error(t, err)
expectedError := "path contains a malformed path component [path: ]"
assert.EqualError(t, err, expectedError)
})
t.Run("treePath is a git directory:", func(t *testing.T) {
opts := getUpdateRepoFilesOptions(repo)
opts.Files[0].TreePath = ".git"
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
assert.Nil(t, filesResponse)
require.Error(t, err)
expectedError := "path contains a malformed path component [path: " + opts.Files[0].TreePath + "]"
assert.EqualError(t, err, expectedError)
})
t.Run("create file that already exists", func(t *testing.T) {
opts := getCreateRepoFilesOptions(repo)
opts.Files[0].TreePath = "README.md" // already exists
fileResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
assert.Nil(t, fileResponse)
require.Error(t, err)
expectedError := "repository file already exists [path: " + opts.Files[0].TreePath + "]"
assert.EqualError(t, err, expectedError)
})
})
}