Skip to content

Commit c5a7beb

Browse files
committed
add api test, informative commit messages
1 parent 162dedb commit c5a7beb

File tree

2 files changed

+349
-4
lines changed

2 files changed

+349
-4
lines changed

routers/api/v1/repo/file.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"io"
1313
"net/http"
1414
"path"
15+
"strings"
1516
"time"
1617

1718
"code.gitea.io/gitea/models"
@@ -487,7 +488,7 @@ func ChangeFiles(ctx *context.APIContext) {
487488
}
488489

489490
if opts.Message == "" {
490-
opts.Message = "Upload files over API"
491+
opts.Message = changeFilesCommitMessage(ctx, files)
491492
}
492493

493494
if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
@@ -576,7 +577,7 @@ func CreateFile(ctx *context.APIContext) {
576577
}
577578

578579
if opts.Message == "" {
579-
opts.Message = ctx.Tr("repo.editor.add", opts.Files[0].TreePath)
580+
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
580581
}
581582

582583
if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
@@ -669,7 +670,7 @@ func UpdateFile(ctx *context.APIContext) {
669670
}
670671

671672
if opts.Message == "" {
672-
opts.Message = ctx.Tr("repo.editor.update", opts.Files[0].TreePath)
673+
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
673674
}
674675

675676
if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
@@ -717,6 +718,36 @@ func createOrUpdateFiles(ctx *context.APIContext, opts *files_service.ChangeRepo
717718
return files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts)
718719
}
719720

721+
// format commit message if empty
722+
func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.ChangeRepoFile) string {
723+
var (
724+
createFiles []string
725+
updateFiles []string
726+
deleteFiles []string
727+
)
728+
for _, file := range files {
729+
switch file.Operation {
730+
case "create":
731+
createFiles = append(createFiles, file.TreePath)
732+
case "update":
733+
updateFiles = append(updateFiles, file.TreePath)
734+
case "delete":
735+
deleteFiles = append(deleteFiles, file.TreePath)
736+
}
737+
}
738+
message := ""
739+
if len(createFiles) != 0 {
740+
message += ctx.Tr("repo.editor.add") + strings.Join(createFiles, ", ") + "\n"
741+
}
742+
if len(updateFiles) != 0 {
743+
message += ctx.Tr("repo.editor.update") + strings.Join(updateFiles, ", ") + "\n"
744+
}
745+
if len(deleteFiles) != 0 {
746+
message += ctx.Tr("repo.editor.delete") + strings.Join(deleteFiles, ", ") + "\n"
747+
}
748+
return message
749+
}
750+
720751
// DeleteFile Delete a file in a repository
721752
func DeleteFile(ctx *context.APIContext) {
722753
// swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile
@@ -803,7 +834,7 @@ func DeleteFile(ctx *context.APIContext) {
803834
}
804835

805836
if opts.Message == "" {
806-
opts.Message = ctx.Tr("repo.editor.delete", opts.Files[0].TreePath)
837+
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
807838
}
808839

809840
if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package integration
5+
6+
import (
7+
stdCtx "context"
8+
"encoding/base64"
9+
"fmt"
10+
"net/http"
11+
"net/url"
12+
"testing"
13+
14+
auth_model "code.gitea.io/gitea/models/auth"
15+
repo_model "code.gitea.io/gitea/models/repo"
16+
"code.gitea.io/gitea/models/unittest"
17+
user_model "code.gitea.io/gitea/models/user"
18+
"code.gitea.io/gitea/modules/context"
19+
"code.gitea.io/gitea/modules/git"
20+
"code.gitea.io/gitea/modules/setting"
21+
api "code.gitea.io/gitea/modules/structs"
22+
23+
"github.com/stretchr/testify/assert"
24+
)
25+
26+
func getChangeFilesOptions() *api.ChangeFilesOptions {
27+
newContent := "This is new text"
28+
updateContent := "This is updated text"
29+
newContentEncoded := base64.StdEncoding.EncodeToString([]byte(newContent))
30+
updateContentEncoded := base64.StdEncoding.EncodeToString([]byte(updateContent))
31+
return &api.ChangeFilesOptions{
32+
FileOptions: api.FileOptions{
33+
BranchName: "master",
34+
NewBranchName: "master",
35+
Message: "My update of new/file.txt",
36+
Author: api.Identity{
37+
Name: "John Doe",
38+
39+
},
40+
Committer: api.Identity{
41+
Name: "Anne Doe",
42+
43+
},
44+
},
45+
Files: []*api.ChangeFileOperation{
46+
{
47+
Operation: "create",
48+
Content: newContentEncoded,
49+
},
50+
{
51+
Operation: "update",
52+
Content: updateContentEncoded,
53+
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
54+
},
55+
{
56+
Operation: "delete",
57+
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
58+
},
59+
},
60+
}
61+
}
62+
63+
func TestAPIUChangeFiles(t *testing.T) {
64+
onGiteaRun(t, func(t *testing.T, u *url.URL) {
65+
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
66+
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
67+
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos
68+
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
69+
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
70+
repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
71+
fileID := 0
72+
73+
// Get user2's token
74+
session := loginUser(t, user2.Name)
75+
token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo)
76+
// Get user4's token
77+
session = loginUser(t, user4.Name)
78+
token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo)
79+
80+
// Test changing files in repo1 which user2 owns, try both with branch and empty branch
81+
for _, branch := range [...]string{
82+
"master", // Branch
83+
"", // Empty branch
84+
} {
85+
fileID++
86+
createTreePath := fmt.Sprintf("new/file%d.txt", fileID)
87+
updateTreePath := fmt.Sprintf("update/file%d.txt", fileID)
88+
deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID)
89+
createFile(user2, repo1, updateTreePath)
90+
createFile(user2, repo1, deleteTreePath)
91+
changeFilesOptions := getChangeFilesOptions()
92+
changeFilesOptions.BranchName = branch
93+
changeFilesOptions.Files[0].Path = createTreePath
94+
changeFilesOptions.Files[1].Path = updateTreePath
95+
changeFilesOptions.Files[2].Path = deleteTreePath
96+
url := fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo1.Name, token2)
97+
req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
98+
resp := MakeRequest(t, req, http.StatusOK)
99+
gitRepo, _ := git.OpenRepository(stdCtx.Background(), repo1.RepoPath())
100+
commitID, _ := gitRepo.GetBranchCommitID(changeFilesOptions.NewBranchName)
101+
createLasCommit, _ := gitRepo.GetCommitByPath(createTreePath)
102+
updateLastCommit, _ := gitRepo.GetCommitByPath(updateTreePath)
103+
expectedCreateFileResponse := getExpectedFileResponseForCreate(fmt.Sprintf("%v/%v", user2.Name, repo1.Name), commitID, createTreePath, createLasCommit.ID.String())
104+
expectedUpdateFileResponse := getExpectedFileResponseForUpdate(commitID, updateTreePath, updateLastCommit.ID.String())
105+
var fileResponse []api.FileResponse
106+
DecodeJSON(t, resp, &fileResponse)
107+
108+
// test create file
109+
assert.EqualValues(t, expectedCreateFileResponse.Content, fileResponse[0].Content)
110+
assert.EqualValues(t, expectedCreateFileResponse.Commit.SHA, fileResponse[0].Commit.SHA)
111+
assert.EqualValues(t, expectedCreateFileResponse.Commit.HTMLURL, fileResponse[0].Commit.HTMLURL)
112+
assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Email, fileResponse[0].Commit.Author.Email)
113+
assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Name, fileResponse[0].Commit.Author.Name)
114+
assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Date, fileResponse[0].Commit.Author.Date)
115+
assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Email, fileResponse[0].Commit.Committer.Email)
116+
assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Name, fileResponse[0].Commit.Committer.Name)
117+
assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Date, fileResponse[0].Commit.Committer.Date)
118+
119+
// test update file
120+
assert.EqualValues(t, expectedUpdateFileResponse.Content, fileResponse[1].Content)
121+
assert.EqualValues(t, expectedUpdateFileResponse.Commit.SHA, fileResponse[1].Commit.SHA)
122+
assert.EqualValues(t, expectedUpdateFileResponse.Commit.HTMLURL, fileResponse[1].Commit.HTMLURL)
123+
assert.EqualValues(t, expectedUpdateFileResponse.Commit.Author.Email, fileResponse[1].Commit.Author.Email)
124+
assert.EqualValues(t, expectedUpdateFileResponse.Commit.Author.Name, fileResponse[1].Commit.Author.Name)
125+
126+
// test delete file
127+
assert.NotNil(t, fileResponse[2])
128+
assert.Nil(t, fileResponse[2].Content)
129+
130+
gitRepo.Close()
131+
}
132+
133+
// Test changing files in a new branch
134+
changeFilesOptions := getChangeFilesOptions()
135+
changeFilesOptions.BranchName = repo1.DefaultBranch
136+
changeFilesOptions.NewBranchName = "new_branch"
137+
fileID++
138+
createTreePath := fmt.Sprintf("new/file%d.txt", fileID)
139+
updateTreePath := fmt.Sprintf("update/file%d.txt", fileID)
140+
deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID)
141+
changeFilesOptions.Files[0].Path = createTreePath
142+
changeFilesOptions.Files[1].Path = updateTreePath
143+
changeFilesOptions.Files[2].Path = deleteTreePath
144+
createFile(user2, repo1, updateTreePath)
145+
createFile(user2, repo1, deleteTreePath)
146+
url := fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo1.Name, token2)
147+
req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
148+
resp := MakeRequest(t, req, http.StatusOK)
149+
var fileResponse []api.FileResponse
150+
DecodeJSON(t, resp, &fileResponse)
151+
expectedCreateSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
152+
expectedCreateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID)
153+
expectedCreateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID)
154+
expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136"
155+
expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID)
156+
expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID)
157+
assert.EqualValues(t, expectedCreateSHA, fileResponse[0].Content.SHA)
158+
assert.EqualValues(t, expectedCreateHTMLURL, *fileResponse[0].Content.HTMLURL)
159+
assert.EqualValues(t, expectedCreateDownloadURL, *fileResponse[0].Content.DownloadURL)
160+
assert.EqualValues(t, changeFilesOptions.Message+"\n", fileResponse[0].Commit.Message)
161+
assert.EqualValues(t, expectedUpdateSHA, fileResponse[1].Content.SHA)
162+
assert.EqualValues(t, expectedUpdateHTMLURL, *fileResponse[1].Content.HTMLURL)
163+
assert.EqualValues(t, expectedUpdateDownloadURL, *fileResponse[1].Content.DownloadURL)
164+
assert.EqualValues(t, changeFilesOptions.Message+"\n", fileResponse[1].Commit.Message)
165+
assert.NotNil(t, fileResponse[2])
166+
assert.Nil(t, fileResponse[2].Content)
167+
assert.EqualValues(t, changeFilesOptions.Message+"\n", fileResponse[2].Commit.Message)
168+
169+
// Test updating a file and renaming it
170+
changeFilesOptions = getChangeFilesOptions()
171+
changeFilesOptions.BranchName = repo1.DefaultBranch
172+
fileID++
173+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
174+
createFile(user2, repo1, updateTreePath)
175+
changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]}
176+
changeFilesOptions.Files[0].FromPath = updateTreePath
177+
changeFilesOptions.Files[0].Path = "rename/" + updateTreePath
178+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
179+
resp = MakeRequest(t, req, http.StatusOK)
180+
DecodeJSON(t, resp, &fileResponse)
181+
expectedUpdateSHA = "08bd14b2e2852529157324de9c226b3364e76136"
182+
expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID)
183+
expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID)
184+
assert.EqualValues(t, expectedUpdateSHA, fileResponse[0].Content.SHA)
185+
assert.EqualValues(t, expectedUpdateHTMLURL, *fileResponse[0].Content.HTMLURL)
186+
assert.EqualValues(t, expectedUpdateDownloadURL, *fileResponse[0].Content.DownloadURL)
187+
188+
// Test updating a file without a message
189+
changeFilesOptions = getChangeFilesOptions()
190+
changeFilesOptions.Message = ""
191+
changeFilesOptions.BranchName = repo1.DefaultBranch
192+
fileID++
193+
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
194+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
195+
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
196+
createFile(user2, repo1, updateTreePath)
197+
createFile(user2, repo1, deleteTreePath)
198+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
199+
resp = MakeRequest(t, req, http.StatusOK)
200+
DecodeJSON(t, resp, &fileResponse)
201+
expectedMessage := fmt.Sprintf("Create: %v\nUpdate: %v\nDelete: %v\n", createTreePath, updateTreePath, deleteTreePath)
202+
for _, response := range fileResponse {
203+
assert.EqualValues(t, expectedMessage, response.Commit.Message)
204+
}
205+
206+
// Test updating a file with the wrong SHA
207+
fileID++
208+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
209+
createFile(user2, repo1, updateTreePath)
210+
changeFilesOptions = getChangeFilesOptions()
211+
changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]}
212+
correctSHA := changeFilesOptions.Files[0].SHA
213+
changeFilesOptions.Files[0].SHA = "badsha"
214+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
215+
resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
216+
expectedAPIError := context.APIError{
217+
Message: "sha does not match [given: " + changeFilesOptions.Files[0].SHA + ", expected: " + correctSHA + "]",
218+
URL: setting.API.SwaggerURL,
219+
}
220+
var apiError context.APIError
221+
DecodeJSON(t, resp, &apiError)
222+
assert.Equal(t, expectedAPIError, apiError)
223+
224+
// Test creating a file in repo1 by user4 who does not have write access
225+
fileID++
226+
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
227+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
228+
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
229+
createFile(user2, repo16, updateTreePath)
230+
createFile(user2, repo16, deleteTreePath)
231+
changeFilesOptions = getChangeFilesOptions()
232+
changeFilesOptions.Files[0].Path = createTreePath
233+
changeFilesOptions.Files[1].Path = updateTreePath
234+
changeFilesOptions.Files[2].Path = deleteTreePath
235+
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo16.Name, token4)
236+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
237+
MakeRequest(t, req, http.StatusNotFound)
238+
239+
// Tests a repo with no token given so will fail
240+
fileID++
241+
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
242+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
243+
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
244+
createFile(user2, repo16, updateTreePath)
245+
createFile(user2, repo16, deleteTreePath)
246+
changeFilesOptions = getChangeFilesOptions()
247+
changeFilesOptions.Files[0].Path = createTreePath
248+
changeFilesOptions.Files[1].Path = updateTreePath
249+
changeFilesOptions.Files[2].Path = deleteTreePath
250+
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name)
251+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
252+
MakeRequest(t, req, http.StatusNotFound)
253+
254+
// Test using access token for a private repo that the user of the token owns
255+
fileID++
256+
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
257+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
258+
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
259+
createFile(user2, repo16, updateTreePath)
260+
createFile(user2, repo16, deleteTreePath)
261+
changeFilesOptions = getChangeFilesOptions()
262+
changeFilesOptions.Files[0].Path = createTreePath
263+
changeFilesOptions.Files[1].Path = updateTreePath
264+
changeFilesOptions.Files[2].Path = deleteTreePath
265+
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo16.Name, token2)
266+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
267+
MakeRequest(t, req, http.StatusOK)
268+
269+
// Test using org repo "user3/repo3" where user2 is a collaborator
270+
fileID++
271+
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
272+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
273+
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
274+
createFile(user3, repo3, updateTreePath)
275+
createFile(user3, repo3, deleteTreePath)
276+
changeFilesOptions = getChangeFilesOptions()
277+
changeFilesOptions.Files[0].Path = createTreePath
278+
changeFilesOptions.Files[1].Path = updateTreePath
279+
changeFilesOptions.Files[2].Path = deleteTreePath
280+
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user3.Name, repo3.Name, token2)
281+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
282+
MakeRequest(t, req, http.StatusOK)
283+
284+
// Test using org repo "user3/repo3" with no user token
285+
fileID++
286+
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
287+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
288+
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
289+
createFile(user3, repo3, updateTreePath)
290+
createFile(user3, repo3, deleteTreePath)
291+
changeFilesOptions = getChangeFilesOptions()
292+
changeFilesOptions.Files[0].Path = createTreePath
293+
changeFilesOptions.Files[1].Path = updateTreePath
294+
changeFilesOptions.Files[2].Path = deleteTreePath
295+
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user3.Name, repo3.Name)
296+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
297+
MakeRequest(t, req, http.StatusNotFound)
298+
299+
// Test using repo "user2/repo1" where user4 is a NOT collaborator
300+
fileID++
301+
createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
302+
updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
303+
deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
304+
createFile(user2, repo1, updateTreePath)
305+
createFile(user2, repo1, deleteTreePath)
306+
changeFilesOptions = getChangeFilesOptions()
307+
changeFilesOptions.Files[0].Path = createTreePath
308+
changeFilesOptions.Files[1].Path = updateTreePath
309+
changeFilesOptions.Files[2].Path = deleteTreePath
310+
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo1.Name, token4)
311+
req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions)
312+
MakeRequest(t, req, http.StatusForbidden)
313+
})
314+
}

0 commit comments

Comments
 (0)