Files
forge/tests/integration/repofiles_change_test.go
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

693 lines
24 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"path"
"strings"
"testing"
"time"
repo_model "github.com/hanzoai/git/models/repo"
"github.com/hanzoai/git/modules/git"
"github.com/hanzoai/git/modules/gitrepo"
"github.com/hanzoai/git/modules/setting"
api "github.com/hanzoai/git/modules/structs"
"github.com/hanzoai/git/services/contexttest"
files_service "github.com/hanzoai/git/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 getUpdateRepoFilesRenameOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
return &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
// move normally
{
Operation: "rename",
FromTreePath: "README.md",
TreePath: "README.txt",
},
// move from in lfs
{
Operation: "rename",
FromTreePath: "crypt.bin",
TreePath: "crypt1.bin",
},
// move from lfs to normal
{
Operation: "rename",
FromTreePath: "jpeg.jpg",
TreePath: "jpeg.jpeg",
},
// move from normal to lfs
{
Operation: "rename",
FromTreePath: "CONTRIBUTING.md",
TreePath: "CONTRIBUTING.md.bin",
},
},
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
Message: "Rename files",
}
}
func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.Commit) *api.FileResponse {
treePath := "new/file.txt"
encoding := "base64"
content := "VGhpcyBpcyBhIE5FVyBmaWxl"
selfURL := setting.AppURL + "v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath
gitURL := setting.AppURL + "v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885"
downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath
return &api.FileResponse{
Content: &api.ContentsResponse{
Name: path.Base(treePath),
Path: treePath,
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
LastCommitSHA: new(lastCommit.ID.String()),
LastCommitterDate: new(lastCommit.Committer.When),
LastAuthorDate: new(lastCommit.Author.When),
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 + "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: "[email protected]",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "[email protected]",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Parents: []*api.CommitMeta{
{
URL: setting.AppURL + "v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d",
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
},
},
Message: "Creates new/file.txt\n",
Tree: &api.CommitMeta{
URL: setting.AppURL + "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, lastCommitterWhen, lastAuthorWhen time.Time) *api.FileResponse {
encoding := "base64"
content := "VGhpcyBpcyBVUERBVEVEIGNvbnRlbnQgZm9yIHRoZSBSRUFETUUgZmlsZQ=="
selfURL := setting.AppURL + "v1/repos/user2/repo1/contents/" + filename + "?ref=master"
htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + filename
gitURL := setting.AppURL + "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: new(lastCommitSHA),
LastCommitterDate: new(lastCommitterWhen),
LastAuthorDate: new(lastAuthorWhen),
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 + "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: "[email protected]",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "[email protected]",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Parents: []*api.CommitMeta{
{
URL: setting.AppURL + "v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d",
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
},
},
Message: "Updates README.md\n",
Tree: &api.CommitMeta{
URL: setting.AppURL + "v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
SHA: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
},
},
Verification: &api.PayloadCommitVerification{
Verified: false,
Reason: "gpg.error.not_signed_commit",
Signature: "",
Payload: "",
},
}
}
func getExpectedFileResponseForRepoFilesUpdateRename(commitID, lastCommitSHA string) *api.FilesResponse {
details := []struct {
filename, sha, content string
size int64
lfsOid *string
lfsSize *int64
}{
{
filename: "README.txt",
sha: "8276d2a29779af982c0afa976bdb793b52d442a8",
size: 22,
content: "IyBBbiBMRlMtZW5hYmxlZCByZXBvCg==",
},
{
filename: "crypt1.bin",
sha: "d4a41a0d4db4949e129bd22f871171ea988103ef",
size: 129,
content: "dmVyc2lvbiBodHRwczovL2dpdC1sZnMuZ2l0aHViLmNvbS9zcGVjL3YxCm9pZCBzaGEyNTY6MmVjY2RiNDM4MjVkMmE0OWQ5OWQ1NDJkYWEyMDA3NWNmZjFkOTdkOWQyMzQ5YTg5NzdlZmU5YzAzNjYxNzM3YwpzaXplIDIwNDgK",
lfsOid: new("2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c"),
lfsSize: new(int64(2048)),
},
{
filename: "jpeg.jpeg",
sha: "71911bf48766c7181518c1070911019fbb00b1fc",
size: 107,
content: "/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=",
},
{
filename: "CONTRIBUTING.md.bin",
sha: "2b6c6c4eaefa24b22f2092c3d54b263ff26feb58",
size: 127,
content: "dmVyc2lvbiBodHRwczovL2dpdC1sZnMuZ2l0aHViLmNvbS9zcGVjL3YxCm9pZCBzaGEyNTY6N2I2YjJjODhkYmE5Zjc2MGExYTU4NDY5YjY3ZmVlMmI2OThlZjdlOTM5OWM0Y2E0ZjM0YTE0Y2NiZTM5ZjYyMwpzaXplIDI3Cg==",
lfsOid: new("7b6b2c88dba9f760a1a58469b67fee2b698ef7e9399c4ca4f34a14ccbe39f623"),
lfsSize: new(int64(27)),
},
}
var responses []*api.ContentsResponse
for _, detail := range details {
selfURL := setting.AppURL + "v1/repos/user2/lfs/contents/" + detail.filename + "?ref=master"
htmlURL := setting.AppURL + "user2/lfs/src/branch/master/" + detail.filename
gitURL := setting.AppURL + "v1/repos/user2/lfs/git/blobs/" + detail.sha
downloadURL := setting.AppURL + "user2/lfs/raw/branch/master/" + detail.filename
// don't set time related fields because there might be different time in one operation
responses = append(responses, &api.ContentsResponse{
Name: detail.filename,
Path: detail.filename,
SHA: detail.sha,
LastCommitSHA: new(lastCommitSHA),
Type: "file",
Size: detail.size,
Encoding: new("base64"),
Content: &detail.content,
URL: &selfURL,
HTMLURL: &htmlURL,
GitURL: &gitURL,
DownloadURL: &downloadURL,
Links: &api.FileLinksResponse{
Self: &selfURL,
GitURL: &gitURL,
HTMLURL: &htmlURL,
},
LfsOid: detail.lfsOid,
LfsSize: detail.lfsSize,
})
}
return &api.FilesResponse{
Files: responses,
Commit: &api.FileCommitResponse{
CommitMeta: api.CommitMeta{
URL: setting.AppURL + "v1/repos/user2/lfs/git/commits/" + commitID,
SHA: commitID,
},
HTMLURL: setting.AppURL + "user2/lfs/commit/" + commitID,
Author: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "[email protected]",
},
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "[email protected]",
},
},
Parents: []*api.CommitMeta{
{
URL: setting.AppURL + "v1/repos/user2/lfs/git/commits/73cf03db6ece34e12bf91e8853dc58f678f2f82d",
SHA: "73cf03db6ece34e12bf91e8853dc58f678f2f82d",
},
},
Message: "Rename files\n",
Tree: &api.CommitMeta{
URL: setting.AppURL + "v1/repos/user2/lfs/git/trees/5307376dc3a5557dc1c403c29a8984668ca9ecb5",
SHA: "5307376dc3a5557dc1c403c29a8984668ca9ecb5",
},
},
Verification: &api.PayloadCommitVerification{
Verified: false,
Reason: "gpg.error.not_signed_commit",
Signature: "",
Payload: "",
},
}
}
func TestChangeRepoFilesForCreate(t *testing.T) {
// setup
onGitRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getCreateRepoFilesOptions(repo)
// test
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
// asserts
assert.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo)
defer gitRepo.Close()
commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch)
lastCommit, _ := gitRepo.GetCommitByPath("new/file.txt")
expectedFileResponse := getExpectedFileResponseForRepoFilesCreate(commitID, lastCommit)
assert.NotNil(t, expectedFileResponse)
if expectedFileResponse != nil {
assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0])
assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
assert.Equal(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email)
assert.Equal(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name)
}
})
}
func TestChangeRepoFilesForUpdate(t *testing.T) {
// setup
onGitRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getUpdateRepoFilesOptions(repo)
// test
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
// asserts
assert.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(opts.NewBranch)
lastCommit, _ := commit.GetCommitByPath(gitRepo, opts.Files[0].TreePath)
expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When)
assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0])
assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
assert.Equal(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email)
assert.Equal(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name)
})
}
func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) {
// setup
onGitRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
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(t.Context(), repo, doer, opts)
// asserts
assert.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(opts.NewBranch)
lastCommit, _ := commit.GetCommitByPath(gitRepo, opts.Files[0].TreePath)
expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When)
// assert that the old file no longer exists in the last commit of the branch
fromEntry, err := commit.GetTreeEntryByPath(ctx, gitRepo, 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(ctx, gitRepo, opts.Files[0].TreePath)
assert.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.Equal(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA)
assert.Equal(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name)
assert.Equal(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path)
assert.Equal(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL)
assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA)
assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
})
}
func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) {
onGitRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/lfs")
ctx.SetPathParam("id", "54")
contexttest.LoadRepo(t, ctx, 54)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
opts := getUpdateRepoFilesRenameOptions(repo)
// test
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, ctx.Doer, opts)
// asserts
assert.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch)
lastCommit, _ := commit.GetCommitByPath(gitRepo, opts.Files[0].TreePath)
expectedFileResponse := getExpectedFileResponseForRepoFilesUpdateRename(commit.ID.String(), lastCommit.ID.String())
for _, file := range filesResponse.Files {
file.LastCommitterDate, file.LastAuthorDate = nil, nil // there might be different time in one operation, so we ignore them
}
assert.Len(t, filesResponse.Files, 4)
assert.Equal(t, expectedFileResponse.Files, filesResponse.Files)
})
}
// Test opts with branch names removed, should get same results as above test
func TestChangeRepoFilesWithoutBranchNames(t *testing.T) {
// setup
onGitRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getUpdateRepoFilesOptions(repo)
opts.OldBranch = ""
opts.NewBranch = ""
// test
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
// asserts
assert.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch)
lastCommit, _ := commit.GetCommitByPath(gitRepo, opts.Files[0].TreePath)
expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When)
assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0])
})
}
func TestChangeRepoFilesForDelete(t *testing.T) {
onGitRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
t.Run("Delete README.md by commit", func(t *testing.T) {
urlRaw := "/user2/repo1/raw/branch/branch2/README.md"
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK)
opts := &files_service.ChangeRepoFilesOptions{
OldBranch: "branch2",
LastCommitID: "985f0301dba5e7b34be866819cd15ad3d8f508ee",
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: "README.md",
},
},
Message: "test message",
}
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
require.NoError(t, err)
assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.Equal(t, "test message\n", filesResponse.Commit.Message)
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound)
})
t.Run("Delete README.md with options", func(t *testing.T) {
urlRaw := "/user2/repo1/raw/branch/master/README.md"
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK)
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: "README.md",
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
},
},
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
Message: "Message for deleting README.md",
Author: &files_service.IdentityOptions{GitUserName: "Bob Smith", GitUserEmail: "[email protected]"},
}
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
require.NoError(t, err)
require.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.Equal(t, "Message for deleting README.md\n", filesResponse.Commit.Message)
assert.Equal(t, api.Identity{Name: "Bob Smith", Email: "[email protected]"}, filesResponse.Commit.Author.Identity)
assert.Equal(t, api.Identity{Name: "Bob Smith", Email: "[email protected]"}, filesResponse.Commit.Committer.Identity)
assert.Equal(t, &api.PayloadCommitVerification{Reason: "gpg.error.not_signed_commit"}, filesResponse.Verification)
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound)
})
t.Run("Delete directory", func(t *testing.T) {
urlRaw := "/user2/repo1/raw/branch/sub-home-md-img-check/docs/README.md"
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK)
opts := &files_service.ChangeRepoFilesOptions{
OldBranch: "sub-home-md-img-check",
LastCommitID: "4649299398e4d39a5c09eb4f534df6f1e1eb87cc",
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: "docs",
DeleteRecursively: true,
},
},
Message: "test message",
}
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
require.NoError(t, err)
assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.Equal(t, "test message\n", filesResponse.Commit.Message)
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound)
})
})
}
func TestChangeRepoFilesErrors(t *testing.T) {
// setup
onGitRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
t.Run("bad branch", func(t *testing.T) {
opts := getUpdateRepoFilesOptions(repo)
opts.OldBranch = "bad_branch"
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
assert.Error(t, err)
assert.Nil(t, filesResponse)
expectedError := fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", repo.ID, 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(t.Context(), repo, doer, opts)
assert.Nil(t, filesResponse)
assert.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(t.Context(), repo, doer, opts)
assert.Nil(t, filesResponse)
assert.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(t.Context(), repo, doer, opts)
assert.Nil(t, filesResponse)
assert.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(t.Context(), repo, doer, opts)
assert.Nil(t, filesResponse)
assert.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(t.Context(), repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "repository file already exists [path: " + opts.Files[0].TreePath + "]"
assert.EqualError(t, err, expectedError)
})
})
}