From 052737a2d46a52d20c2f18932c5e4f49f2e69542 Mon Sep 17 00:00:00 2001 From: "seokcheon.ju" Date: Mon, 26 Feb 2024 17:02:49 +0900 Subject: [PATCH 1/2] Merge api on Create or update file contents --- modules/structs/repo_file.go | 12 +- routers/api/v1/api.go | 2 +- routers/api/v1/repo/file.go | 62 +- routers/api/v1/repo/patch.go | 2 +- routers/api/v1/swagger/options.go | 2 +- .../api_helper_for_declarative_test.go | 4 +- .../api_repo_file_create_or_update_test.go | 568 ++++++++++++++++++ .../integration/api_repo_file_create_test.go | 115 +--- .../integration/api_repo_file_update_test.go | 275 --------- tests/integration/empty_repo_test.go | 2 +- tests/integration/gpg_git_test.go | 2 +- 11 files changed, 636 insertions(+), 410 deletions(-) create mode 100644 tests/integration/api_repo_file_create_or_update_test.go diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index 82bde96ab6901..74e11906a8b7e 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -21,6 +21,7 @@ type FileOptions struct { } // CreateFileOptions options for creating files +// Deprecated: Use CreateOrUpdateFileOptions instead // Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) type CreateFileOptions struct { FileOptions @@ -48,10 +49,13 @@ func (o *DeleteFileOptions) Branch() string { return o.FileOptions.BranchName } -// UpdateFileOptions options for updating files +// CreateOrUpdateFileOptions options for creating or updating files // Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) -type UpdateFileOptions struct { - DeleteFileOptions +type CreateOrUpdateFileOptions struct { + FileOptions + // sha is the SHA for the file that already exists + // required only for updating files + SHA string `json:"sha"` // content must be base64 encoded // required: true ContentBase64 string `json:"content"` @@ -60,7 +64,7 @@ type UpdateFileOptions struct { } // Branch returns branch name -func (o *UpdateFileOptions) Branch() string { +func (o *CreateOrUpdateFileOptions) Branch() string { return o.FileOptions.BranchName } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 0913571c27020..7396f0e0f4ea0 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1257,7 +1257,7 @@ func Routes() *web.Route { m.Get("/*", repo.GetContents) m.Group("/*", func() { m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile) - m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile) + m.Put("", bind(api.CreateOrUpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateOrUpdateFile) m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile) }, reqToken()) }, reqRepoReader(unit.TypeCode)) diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 907a5b568e6e2..a4bdee3f8d4ce 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -514,6 +514,7 @@ func ChangeFiles(ctx *context.APIContext) { } // CreateFile handles API call for creating a file +// Deprecated: Use CreateOrUpdateFileOptions instead func CreateFile(ctx *context.APIContext) { // swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile // --- @@ -611,11 +612,11 @@ func CreateFile(ctx *context.APIContext) { } } -// UpdateFile handles API call for updating a file -func UpdateFile(ctx *context.APIContext) { - // swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile +// CreateOrUpdateFile handles API call for creating or updating a file +func CreateOrUpdateFile(ctx *context.APIContext) { + // swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoCreateOrUpdateFile // --- - // summary: Update a file in a repository + // summary: Create or update a file in a repository // consumes: // - application/json // produces: @@ -633,17 +634,19 @@ func UpdateFile(ctx *context.APIContext) { // required: true // - name: filepath // in: path - // description: path of the file to update + // description: path of the file to create or update // type: string // required: true // - name: body // in: body // required: true // schema: - // "$ref": "#/definitions/UpdateFileOptions" + // "$ref": "#/definitions/CreateOrUpdateFileOptions" // responses: // "200": // "$ref": "#/responses/FileResponse" + // "201": + // "$ref": "#/responses/FileResponse" // "403": // "$ref": "#/responses/error" // "404": @@ -652,10 +655,7 @@ func UpdateFile(ctx *context.APIContext) { // "$ref": "#/responses/error" // "423": // "$ref": "#/responses/repoArchivedError" - apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions) - if ctx.Repo.Repository.IsEmpty { - ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty")) - } + apiOpts := web.GetForm(ctx).(*api.CreateOrUpdateFileOptions) if apiOpts.BranchName == "" { apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch @@ -667,16 +667,30 @@ func UpdateFile(ctx *context.APIContext) { return } + var changeRepoFile files_service.ChangeRepoFile + if apiOpts.SHA == "" { + changeRepoFile = files_service.ChangeRepoFile{ + Operation: "create", + TreePath: ctx.Params("*"), + ContentReader: contentReader, + } + } else { + if ctx.Repo.Repository.IsEmpty { + ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty")) + return + } + + changeRepoFile = files_service.ChangeRepoFile{ + Operation: "update", + TreePath: ctx.Params("*"), + ContentReader: contentReader, + SHA: apiOpts.SHA, + FromTreePath: apiOpts.FromPath, + } + } + opts := &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "update", - ContentReader: contentReader, - SHA: apiOpts.SHA, - FromTreePath: apiOpts.FromPath, - TreePath: ctx.Params("*"), - }, - }, + Files: []*files_service.ChangeRepoFile{&changeRepoFile}, Message: apiOpts.Message, OldBranch: apiOpts.BranchName, NewBranch: apiOpts.NewBranchName, @@ -709,7 +723,11 @@ func UpdateFile(ctx *context.APIContext) { handleCreateOrUpdateFileError(ctx, err) } else { fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0) - ctx.JSON(http.StatusOK, fileResponse) + if apiOpts.SHA == "" { + ctx.JSON(http.StatusCreated, fileResponse) + } else { + ctx.JSON(http.StatusOK, fileResponse) + } } } @@ -728,10 +746,10 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { return } - ctx.Error(http.StatusInternalServerError, "UpdateFile", err) + ctx.Error(http.StatusInternalServerError, "CreateOrUpdateFile", err) } -// Called from both CreateFile or UpdateFile to handle both +// Called from both CreateFile or CreateOrUpdateFile to handle both func createOrUpdateFiles(ctx *context.APIContext, opts *files_service.ChangeRepoFilesOptions) (*api.FilesResponse, error) { if !canWriteFiles(ctx, opts.OldBranch) { return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{ diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go index 0e0601b7d93e7..e820d353d025b 100644 --- a/routers/api/v1/repo/patch.go +++ b/routers/api/v1/repo/patch.go @@ -41,7 +41,7 @@ func ApplyDiffPatch(ctx *context.APIContext) { // in: body // required: true // schema: - // "$ref": "#/definitions/UpdateFileOptions" + // "$ref": "#/definitions/ApplyDiffPatchFileOptions" // responses: // "200": // "$ref": "#/responses/FileResponse" diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 6f7859df62ed4..c8894d6e20ac8 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -123,7 +123,7 @@ type swaggerParameterBodies struct { CreateFileOptions api.CreateFileOptions // in:body - UpdateFileOptions api.UpdateFileOptions + CreateOrUpdateFileOptions api.CreateOrUpdateFileOptions // in:body DeleteFileOptions api.DeleteFileOptions diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index 7755b9861ae30..2a63f592a6eb1 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -353,9 +353,9 @@ func doAPIGetBranch(ctx APITestContext, branch string, callback ...func(*testing } } -func doAPICreateFile(ctx APITestContext, treepath string, options *api.CreateFileOptions, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { +func doAPICreateFile(ctx APITestContext, treepath string, options *api.CreateOrUpdateFileOptions, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { return func(t *testing.T) { - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", ctx.Username, ctx.Reponame, treepath), &options). + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", ctx.Username, ctx.Reponame, treepath), &options). AddTokenAuth(ctx.Token) if ctx.ExpectedCode != 0 { ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) diff --git a/tests/integration/api_repo_file_create_or_update_test.go b/tests/integration/api_repo_file_create_or_update_test.go new file mode 100644 index 0000000000000..3376691abbb38 --- /dev/null +++ b/tests/integration/api_repo_file_create_or_update_test.go @@ -0,0 +1,568 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + stdCtx "context" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path/filepath" + "testing" + "time" + + auth_model "code.gitea.io/gitea/models/auth" + 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/context" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func getCreateFileOptions() api.CreateOrUpdateFileOptions { + content := "This is new text" + contentEncoded := base64.StdEncoding.EncodeToString([]byte(content)) + return api.CreateOrUpdateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: "master", + NewBranchName: "master", + Message: "Making this new file new/file.txt", + Author: api.Identity{ + Name: "Anne Doe", + Email: "annedoe@example.com", + }, + Committer: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + Dates: api.CommitDateOptions{ + Author: time.Unix(946684810, 0), + Committer: time.Unix(978307190, 0), + }, + }, + ContentBase64: contentEncoded, + } +} + +func getUpdateFileOptions() *api.CreateOrUpdateFileOptions { + content := "This is updated text" + contentEncoded := base64.StdEncoding.EncodeToString([]byte(content)) + return &api.CreateOrUpdateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: "master", + NewBranchName: "master", + Message: "My update of new/file.txt", + Author: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + Committer: api.Identity{ + Name: "Anne Doe", + Email: "annedoe@example.com", + }, + }, + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", + ContentBase64: contentEncoded, + } +} + +func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) *api.FileResponse { + sha := "08bd14b2e2852529157324de9c226b3364e76136" + encoding := "base64" + content := "VGhpcyBpcyB1cGRhdGVkIHRleHQ=" + 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/" + sha + downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath + return &api.FileResponse{ + Content: &api.ContentsResponse{ + Name: filepath.Base(treePath), + Path: treePath, + SHA: sha, + LastCommitSHA: lastCommitSHA, + Type: "file", + Size: 20, + 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: "John Doe", + Email: "johndoe@example.com", + }, + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "Anne Doe", + Email: "annedoe@example.com", + }, + }, + Message: "My update of README.md\n", + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "gpg.error.not_signed_commit", + Signature: "", + Payload: "", + }, + } +} + +func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCommitSHA string) *api.FileResponse { + sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" + encoding := "base64" + content := "VGhpcyBpcyBuZXcgdGV4dA==" + selfURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master" + htmlURL := setting.AppURL + repoFullName + "/src/branch/master/" + treePath + gitURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha + downloadURL := setting.AppURL + repoFullName + "/raw/branch/master/" + treePath + return &api.FileResponse{ + Content: &api.ContentsResponse{ + Name: filepath.Base(treePath), + Path: treePath, + SHA: sha, + LastCommitSHA: latestCommitSHA, + Size: 16, + Type: "file", + 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/" + repoFullName + "/git/commits/" + commitID, + SHA: commitID, + }, + HTMLURL: setting.AppURL + repoFullName + "/commit/" + commitID, + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Anne Doe", + Email: "annedoe@example.com", + }, + Date: "2000-01-01T00:00:10Z", + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + Date: "2000-12-31T23:59:50Z", + }, + Message: "Updates README.md\n", + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "gpg.error.not_signed_commit", + Signature: "", + Payload: "", + }, + } +} + +func BenchmarkAPICreateFileSmall(b *testing.B) { + onGiteaRun(b, func(b *testing.B, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(b, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo + + b.ResetTimer() + for n := 0; n < b.N; n++ { + treePath := fmt.Sprintf("update/file%d.txt", n) + _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) + } + }) +} + +func BenchmarkAPICreateFileMedium(b *testing.B) { + data := make([]byte, 10*1024*1024) + + onGiteaRun(b, func(b *testing.B, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(b, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo + + b.ResetTimer() + for n := 0; n < b.N; n++ { + treePath := fmt.Sprintf("update/file%d.txt", n) + copy(data, treePath) + _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) + } + }) +} + +func TestAPICreateFile(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + fileID := 0 + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + // Test creating a file in repo1 which user2 owns, try both with branch and empty branch + for _, branch := range [...]string{ + "master", // Branch + "", // Empty branch + } { + createFileOptions := getCreateFileOptions() + createFileOptions.BranchName = branch + fileID++ + treePath := fmt.Sprintf("new/file%d.txt", fileID) + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + AddTokenAuth(token2) + resp := MakeRequest(t, req, http.StatusCreated) + gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), repo1) + commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) + latestCommit, _ := gitRepo.GetCommitByPath(treePath) + expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath, latestCommit.ID.String()) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) + gitRepo.Close() + } + + // Test creating a file in a new branch + createFileOptions := getCreateFileOptions() + createFileOptions.BranchName = repo1.DefaultBranch + createFileOptions.NewBranchName = "new_branch" + fileID++ + treePath := fmt.Sprintf("new/file%d.txt", fileID) + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + AddTokenAuth(token2) + resp := MakeRequest(t, req, http.StatusCreated) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" + expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) + expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) + + // Test creating a file without a message + createFileOptions = getCreateFileOptions() + createFileOptions.Message = "" + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, &fileResponse) + expectedMessage := "Add " + treePath + "\n" + assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + + // Test trying to create a file that already exists, should fail + createFileOptions = getCreateFileOptions() + treePath = "README.md" + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusUnprocessableEntity) + expectedAPIError := context.APIError{ + Message: "repository file already exists [path: " + treePath + "]", + URL: setting.API.SwaggerURL, + } + var apiError context.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, expectedAPIError, apiError) + + // Test creating a file in repo1 by user4 who does not have write access + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). + AddTokenAuth(token4) + MakeRequest(t, req, http.StatusNotFound) + + // Tests a repo with no token given so will fail + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions) + MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusCreated) + + // Test using org repo "org3/repo3" where user2 is a collaborator + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusCreated) + + // Test using org repo "org3/repo3" with no user token + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions) + MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + AddTokenAuth(token4) + MakeRequest(t, req, http.StatusForbidden) + + // Test creating a file in an empty repository + doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true)(t) + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, "empty-repo", treePath), &createFileOptions). + AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusCreated) + emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}) // public repo + gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), emptyRepo) + commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) + latestCommit, _ := gitRepo.GetCommitByPath(treePath) + expectedFileResponse := getExpectedFileResponseForCreate("user2/empty-repo", commitID, treePath, latestCommit.ID.String()) + DecodeJSON(t, resp, &fileResponse) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) + gitRepo.Close() + }) +} + +func TestAPIUpdateFile(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + fileID := 0 + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // Test updating a file in repo1 which user2 owns, try both with branch and empty branch + for _, branch := range [...]string{ + "master", // Branch + "", // Empty branch + } { + fileID++ + treePath := fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions := getUpdateFileOptions() + updateFileOptions.BranchName = branch + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + AddTokenAuth(token2) + resp := MakeRequest(t, req, http.StatusOK) + gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), repo1) + commitID, _ := gitRepo.GetBranchCommitID(updateFileOptions.NewBranchName) + lasCommit, _ := gitRepo.GetCommitByPath(treePath) + expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath, lasCommit.ID.String()) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + gitRepo.Close() + } + + // Test updating a file in a new branch + updateFileOptions := getUpdateFileOptions() + updateFileOptions.BranchName = repo1.DefaultBranch + updateFileOptions.NewBranchName = "new_branch" + fileID++ + treePath := fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + AddTokenAuth(token2) + resp := MakeRequest(t, req, http.StatusOK) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" + expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) + expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.EqualValues(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) + + // Test updating a file and renaming it + updateFileOptions = getUpdateFileOptions() + updateFileOptions.BranchName = repo1.DefaultBranch + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions.FromPath = treePath + treePath = "rename/" + treePath + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &fileResponse) + expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" + expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) + expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + + // Test updating a file without a message + updateFileOptions = getUpdateFileOptions() + updateFileOptions.Message = "" + updateFileOptions.BranchName = repo1.DefaultBranch + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &fileResponse) + expectedMessage := "Update " + treePath + "\n" + assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + + // Test updating a file with the wrong SHA + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions = getUpdateFileOptions() + correctSHA := updateFileOptions.SHA + updateFileOptions.SHA = "badsha" + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusUnprocessableEntity) + expectedAPIError := context.APIError{ + Message: "sha does not match [given: " + updateFileOptions.SHA + ", expected: " + correctSHA + "]", + URL: setting.API.SwaggerURL, + } + var apiError context.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, expectedAPIError, apiError) + + // Test updating a file without SHA assume as create + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions = getUpdateFileOptions() + updateFileOptions.SHA = "" + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusUnprocessableEntity) + expectedAPIError = context.APIError{ + Message: "repository file already exists [path: " + treePath + "]", + URL: setting.API.SwaggerURL, + } + DecodeJSON(t, resp, &apiError) + assert.Equal(t, expectedAPIError, apiError) + + // Test creating a file in repo1 by user4 who does not have write access + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). + AddTokenAuth(token4) + MakeRequest(t, req, http.StatusNotFound) + + // Tests a repo with no token given so will fail + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions) + MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusOK) + + // Test using org repo "org3/repo3" where user2 is a collaborator + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(org3, repo3, treePath) + updateFileOptions = getUpdateFileOptions() + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusOK) + + // Test using org repo "org3/repo3" with no user token + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(org3, repo3, treePath) + updateFileOptions = getUpdateFileOptions() + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions) + MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions = getUpdateFileOptions() + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + AddTokenAuth(token4) + MakeRequest(t, req, http.StatusForbidden) + }) +} diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index 41ad7211ff87b..6db74b52e5a1b 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -9,7 +9,6 @@ import ( "fmt" "net/http" "net/url" - "path/filepath" "testing" "time" @@ -25,7 +24,7 @@ import ( "github.com/stretchr/testify/assert" ) -func getCreateFileOptions() api.CreateFileOptions { +func getDeprecatedCreateFileOptions() api.CreateFileOptions { content := "This is new text" contentEncoded := base64.StdEncoding.EncodeToString([]byte(content)) return api.CreateFileOptions{ @@ -50,95 +49,7 @@ func getCreateFileOptions() api.CreateFileOptions { } } -func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCommitSHA string) *api.FileResponse { - sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" - encoding := "base64" - content := "VGhpcyBpcyBuZXcgdGV4dA==" - selfURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master" - htmlURL := setting.AppURL + repoFullName + "/src/branch/master/" + treePath - gitURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha - downloadURL := setting.AppURL + repoFullName + "/raw/branch/master/" + treePath - return &api.FileResponse{ - Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: latestCommitSHA, - Size: 16, - Type: "file", - 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/" + repoFullName + "/git/commits/" + commitID, - SHA: commitID, - }, - HTMLURL: setting.AppURL + repoFullName + "/commit/" + commitID, - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Anne Doe", - Email: "annedoe@example.com", - }, - Date: "2000-01-01T00:00:10Z", - }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "John Doe", - Email: "johndoe@example.com", - }, - Date: "2000-12-31T23:59:50Z", - }, - Message: "Updates README.md\n", - }, - Verification: &api.PayloadCommitVerification{ - Verified: false, - Reason: "gpg.error.not_signed_commit", - Signature: "", - Payload: "", - }, - } -} - -func BenchmarkAPICreateFileSmall(b *testing.B) { - onGiteaRun(b, func(b *testing.B, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(b, &user_model.User{ID: 2}) // owner of the repo1 & repo16 - repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo - - b.ResetTimer() - for n := 0; n < b.N; n++ { - treePath := fmt.Sprintf("update/file%d.txt", n) - _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) - } - }) -} - -func BenchmarkAPICreateFileMedium(b *testing.B) { - data := make([]byte, 10*1024*1024) - - onGiteaRun(b, func(b *testing.B, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(b, &user_model.User{ID: 2}) // owner of the repo1 & repo16 - repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo - - b.ResetTimer() - for n := 0; n < b.N; n++ { - treePath := fmt.Sprintf("update/file%d.txt", n) - copy(data, treePath) - _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) - } - }) -} - -func TestAPICreateFile(t *testing.T) { +func TestAPIDeprecatedCreateFile(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org @@ -160,7 +71,7 @@ func TestAPICreateFile(t *testing.T) { "master", // Branch "", // Empty branch } { - createFileOptions := getCreateFileOptions() + createFileOptions := getDeprecatedCreateFileOptions() createFileOptions.BranchName = branch fileID++ treePath := fmt.Sprintf("new/file%d.txt", fileID) @@ -186,7 +97,7 @@ func TestAPICreateFile(t *testing.T) { } // Test creating a file in a new branch - createFileOptions := getCreateFileOptions() + createFileOptions := getDeprecatedCreateFileOptions() createFileOptions.BranchName = repo1.DefaultBranch createFileOptions.NewBranchName = "new_branch" fileID++ @@ -205,7 +116,7 @@ func TestAPICreateFile(t *testing.T) { assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) // Test creating a file without a message - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() createFileOptions.Message = "" fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) @@ -217,7 +128,7 @@ func TestAPICreateFile(t *testing.T) { assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) // Test trying to create a file that already exists, should fail - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() treePath = "README.md" req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). AddTokenAuth(token2) @@ -231,7 +142,7 @@ func TestAPICreateFile(t *testing.T) { assert.Equal(t, expectedAPIError, apiError) // Test creating a file in repo1 by user4 who does not have write access - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). @@ -239,14 +150,14 @@ func TestAPICreateFile(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) // Tests a repo with no token given so will fail - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions) MakeRequest(t, req, http.StatusNotFound) // Test using access token for a private repo that the user of the token owns - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). @@ -254,7 +165,7 @@ func TestAPICreateFile(t *testing.T) { MakeRequest(t, req, http.StatusCreated) // Test using org repo "org3/repo3" where user2 is a collaborator - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions). @@ -262,14 +173,14 @@ func TestAPICreateFile(t *testing.T) { MakeRequest(t, req, http.StatusCreated) // Test using org repo "org3/repo3" with no user token - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions) MakeRequest(t, req, http.StatusNotFound) // Test using repo "user2/repo1" where user4 is a NOT collaborator - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). @@ -278,7 +189,7 @@ func TestAPICreateFile(t *testing.T) { // Test creating a file in an empty repository doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true)(t) - createFileOptions = getCreateFileOptions() + createFileOptions = getDeprecatedCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, "empty-repo", treePath), &createFileOptions). diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index ac28e0c0a2665..e69de29bb2d1d 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -1,275 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - stdCtx "context" - "encoding/base64" - "fmt" - "net/http" - "net/url" - "path/filepath" - "testing" - - auth_model "code.gitea.io/gitea/models/auth" - 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/gitrepo" - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/services/context" - - "github.com/stretchr/testify/assert" -) - -func getUpdateFileOptions() *api.UpdateFileOptions { - content := "This is updated text" - contentEncoded := base64.StdEncoding.EncodeToString([]byte(content)) - return &api.UpdateFileOptions{ - DeleteFileOptions: api.DeleteFileOptions{ - FileOptions: api.FileOptions{ - BranchName: "master", - NewBranchName: "master", - Message: "My update of new/file.txt", - Author: api.Identity{ - Name: "John Doe", - Email: "johndoe@example.com", - }, - Committer: api.Identity{ - Name: "Anne Doe", - Email: "annedoe@example.com", - }, - }, - SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", - }, - ContentBase64: contentEncoded, - } -} - -func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) *api.FileResponse { - sha := "08bd14b2e2852529157324de9c226b3364e76136" - encoding := "base64" - content := "VGhpcyBpcyB1cGRhdGVkIHRleHQ=" - 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/" + sha - downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath - return &api.FileResponse{ - Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 20, - 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: "John Doe", - Email: "johndoe@example.com", - }, - }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Anne Doe", - Email: "annedoe@example.com", - }, - }, - Message: "My update of README.md\n", - }, - Verification: &api.PayloadCommitVerification{ - Verified: false, - Reason: "gpg.error.not_signed_commit", - Signature: "", - Payload: "", - }, - } -} - -func TestAPIUpdateFile(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo - repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo - fileID := 0 - - // Get user2's token - session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - // Get user4's token - session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // Test updating a file in repo1 which user2 owns, try both with branch and empty branch - for _, branch := range [...]string{ - "master", // Branch - "", // Empty branch - } { - fileID++ - treePath := fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions := getUpdateFileOptions() - updateFileOptions.BranchName = branch - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - resp := MakeRequest(t, req, http.StatusOK) - gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), repo1) - commitID, _ := gitRepo.GetBranchCommitID(updateFileOptions.NewBranchName) - lasCommit, _ := gitRepo.GetCommitByPath(treePath) - expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath, lasCommit.ID.String()) - var fileResponse api.FileResponse - DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - gitRepo.Close() - } - - // Test updating a file in a new branch - updateFileOptions := getUpdateFileOptions() - updateFileOptions.BranchName = repo1.DefaultBranch - updateFileOptions.NewBranchName = "new_branch" - fileID++ - treePath := fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - resp := MakeRequest(t, req, http.StatusOK) - var fileResponse api.FileResponse - DecodeJSON(t, resp, &fileResponse) - expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" - expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) - expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) - assert.EqualValues(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) - - // Test updating a file and renaming it - updateFileOptions = getUpdateFileOptions() - updateFileOptions.BranchName = repo1.DefaultBranch - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions.FromPath = treePath - treePath = "rename/" + treePath - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &fileResponse) - expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" - expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) - expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) - - // Test updating a file without a message - updateFileOptions = getUpdateFileOptions() - updateFileOptions.Message = "" - updateFileOptions.BranchName = repo1.DefaultBranch - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &fileResponse) - expectedMessage := "Update " + treePath + "\n" - assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) - - // Test updating a file with the wrong SHA - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions = getUpdateFileOptions() - correctSHA := updateFileOptions.SHA - updateFileOptions.SHA = "badsha" - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusUnprocessableEntity) - expectedAPIError := context.APIError{ - Message: "sha does not match [given: " + updateFileOptions.SHA + ", expected: " + correctSHA + "]", - URL: setting.API.SwaggerURL, - } - var apiError context.APIError - DecodeJSON(t, resp, &apiError) - assert.Equal(t, expectedAPIError, apiError) - - // Test creating a file in repo1 by user4 who does not have write access - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" where user2 is a collaborator - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(org3, repo3, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" with no user token - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(org3, repo3, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) - }) -} diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index ea393a606167a..daa963e8e8b93 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -108,7 +108,7 @@ func TestEmptyRepoAddFileByAPI(t *testing.T) { session := loginUser(t, "user30") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user30/empty/contents/new-file.txt", &api.CreateFileOptions{ + req := NewRequestWithJSON(t, "PUT", "/api/v1/repos/user30/empty/contents/new-file.txt", &api.CreateOrUpdateFileOptions{ FileOptions: api.FileOptions{ NewBranchName: "new_branch", Message: "init", diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_git_test.go index 00890cfb38630..799ae20948fb8 100644 --- a/tests/integration/gpg_git_test.go +++ b/tests/integration/gpg_git_test.go @@ -263,7 +263,7 @@ func TestGPGGit(t *testing.T) { } func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { - return doAPICreateFile(ctx, path, &api.CreateFileOptions{ + return doAPICreateFile(ctx, path, &api.CreateOrUpdateFileOptions{ FileOptions: api.FileOptions{ BranchName: from, NewBranchName: to, From 3f3cc09659cbb775ec667d18c459212e2c03aacc Mon Sep 17 00:00:00 2001 From: "seokcheon.ju" Date: Tue, 27 Feb 2024 19:50:45 +0900 Subject: [PATCH 2/2] resolve conflict with main branch --- tests/integration/api_repo_file_create_or_update_test.go | 2 +- tests/integration/api_repo_file_update_test.go | 0 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 tests/integration/api_repo_file_update_test.go diff --git a/tests/integration/api_repo_file_create_or_update_test.go b/tests/integration/api_repo_file_create_or_update_test.go index 3376691abbb38..d5f8025c563f1 100644 --- a/tests/integration/api_repo_file_create_or_update_test.go +++ b/tests/integration/api_repo_file_create_or_update_test.go @@ -17,10 +17,10 @@ import ( 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/context" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/context" "github.com/stretchr/testify/assert" ) diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go deleted file mode 100644 index e69de29bb2d1d..0000000000000