1// Copyright 2019 The Gitea Authors. All rights reserved. 2// Use of this source code is governed by a MIT-style 3// license that can be found in the LICENSE file. 4 5package integrations 6 7import ( 8 "encoding/base64" 9 "fmt" 10 "net/http" 11 "net/url" 12 "path/filepath" 13 "testing" 14 "time" 15 16 repo_model "code.gitea.io/gitea/models/repo" 17 "code.gitea.io/gitea/models/unittest" 18 user_model "code.gitea.io/gitea/models/user" 19 "code.gitea.io/gitea/modules/context" 20 "code.gitea.io/gitea/modules/git" 21 "code.gitea.io/gitea/modules/setting" 22 api "code.gitea.io/gitea/modules/structs" 23 24 "github.com/stretchr/testify/assert" 25) 26 27func getCreateFileOptions() api.CreateFileOptions { 28 content := "This is new text" 29 contentEncoded := base64.StdEncoding.EncodeToString([]byte(content)) 30 return api.CreateFileOptions{ 31 FileOptions: api.FileOptions{ 32 BranchName: "master", 33 NewBranchName: "master", 34 Message: "Making this new file new/file.txt", 35 Author: api.Identity{ 36 Name: "Anne Doe", 37 Email: "annedoe@example.com", 38 }, 39 Committer: api.Identity{ 40 Name: "John Doe", 41 Email: "johndoe@example.com", 42 }, 43 Dates: api.CommitDateOptions{ 44 Author: time.Unix(946684810, 0), 45 Committer: time.Unix(978307190, 0), 46 }, 47 }, 48 Content: contentEncoded, 49 } 50} 51 52func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileResponse { 53 sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" 54 encoding := "base64" 55 content := "VGhpcyBpcyBuZXcgdGV4dA==" 56 selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" 57 htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath 58 gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha 59 downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath 60 return &api.FileResponse{ 61 Content: &api.ContentsResponse{ 62 Name: filepath.Base(treePath), 63 Path: treePath, 64 SHA: sha, 65 Size: 16, 66 Type: "file", 67 Encoding: &encoding, 68 Content: &content, 69 URL: &selfURL, 70 HTMLURL: &htmlURL, 71 GitURL: &gitURL, 72 DownloadURL: &downloadURL, 73 Links: &api.FileLinksResponse{ 74 Self: &selfURL, 75 GitURL: &gitURL, 76 HTMLURL: &htmlURL, 77 }, 78 }, 79 Commit: &api.FileCommitResponse{ 80 CommitMeta: api.CommitMeta{ 81 URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID, 82 SHA: commitID, 83 }, 84 HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, 85 Author: &api.CommitUser{ 86 Identity: api.Identity{ 87 Name: "Anne Doe", 88 Email: "annedoe@example.com", 89 }, 90 Date: "2000-01-01T00:00:10Z", 91 }, 92 Committer: &api.CommitUser{ 93 Identity: api.Identity{ 94 Name: "John Doe", 95 Email: "johndoe@example.com", 96 }, 97 Date: "2000-12-31T23:59:50Z", 98 }, 99 Message: "Updates README.md\n", 100 }, 101 Verification: &api.PayloadCommitVerification{ 102 Verified: false, 103 Reason: "gpg.error.not_signed_commit", 104 Signature: "", 105 Payload: "", 106 }, 107 } 108} 109 110func BenchmarkAPICreateFileSmall(b *testing.B) { 111 onGiteaRunTB(b, func(t testing.TB, u *url.URL) { 112 b := t.(*testing.B) 113 user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) // owner of the repo1 & repo16 114 repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) // public repo 115 116 for n := 0; n < b.N; n++ { 117 treePath := fmt.Sprintf("update/file%d.txt", n) 118 createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) 119 } 120 }) 121} 122 123func BenchmarkAPICreateFileMedium(b *testing.B) { 124 data := make([]byte, 10*1024*1024) 125 126 onGiteaRunTB(b, func(t testing.TB, u *url.URL) { 127 b := t.(*testing.B) 128 user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) // owner of the repo1 & repo16 129 repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) // public repo 130 131 b.ResetTimer() 132 for n := 0; n < b.N; n++ { 133 treePath := fmt.Sprintf("update/file%d.txt", n) 134 copy(data, treePath) 135 createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) 136 } 137 }) 138} 139 140func TestAPICreateFile(t *testing.T) { 141 onGiteaRun(t, func(t *testing.T, u *url.URL) { 142 user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) // owner of the repo1 & repo16 143 user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) // owner of the repo3, is an org 144 user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User) // owner of neither repos 145 repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) // public repo 146 repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) // public repo 147 repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}).(*repo_model.Repository) // private repo 148 fileID := 0 149 150 // Get user2's token 151 session := loginUser(t, user2.Name) 152 token2 := getTokenForLoggedInUser(t, session) 153 // Get user4's token 154 session = loginUser(t, user4.Name) 155 token4 := getTokenForLoggedInUser(t, session) 156 session = emptyTestSession(t) 157 158 // Test creating a file in repo1 which user2 owns, try both with branch and empty branch 159 for _, branch := range [...]string{ 160 "master", // Branch 161 "", // Empty branch 162 } { 163 createFileOptions := getCreateFileOptions() 164 createFileOptions.BranchName = branch 165 fileID++ 166 treePath := fmt.Sprintf("new/file%d.txt", fileID) 167 url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) 168 req := NewRequestWithJSON(t, "POST", url, &createFileOptions) 169 resp := session.MakeRequest(t, req, http.StatusCreated) 170 gitRepo, _ := git.OpenRepository(repo1.RepoPath()) 171 commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) 172 expectedFileResponse := getExpectedFileResponseForCreate(commitID, treePath) 173 var fileResponse api.FileResponse 174 DecodeJSON(t, resp, &fileResponse) 175 assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) 176 assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) 177 assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) 178 assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) 179 assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) 180 assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) 181 assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) 182 assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) 183 assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) 184 gitRepo.Close() 185 } 186 187 // Test creating a file in a new branch 188 createFileOptions := getCreateFileOptions() 189 createFileOptions.BranchName = repo1.DefaultBranch 190 createFileOptions.NewBranchName = "new_branch" 191 fileID++ 192 treePath := fmt.Sprintf("new/file%d.txt", fileID) 193 url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) 194 req := NewRequestWithJSON(t, "POST", url, &createFileOptions) 195 resp := session.MakeRequest(t, req, http.StatusCreated) 196 var fileResponse api.FileResponse 197 DecodeJSON(t, resp, &fileResponse) 198 expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" 199 expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) 200 expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) 201 assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) 202 assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) 203 assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) 204 assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) 205 206 // Test creating a file without a message 207 createFileOptions = getCreateFileOptions() 208 createFileOptions.Message = "" 209 fileID++ 210 treePath = fmt.Sprintf("new/file%d.txt", fileID) 211 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) 212 req = NewRequestWithJSON(t, "POST", url, &createFileOptions) 213 resp = session.MakeRequest(t, req, http.StatusCreated) 214 DecodeJSON(t, resp, &fileResponse) 215 expectedMessage := "Add '" + treePath + "'\n" 216 assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) 217 218 // Test trying to create a file that already exists, should fail 219 createFileOptions = getCreateFileOptions() 220 treePath = "README.md" 221 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) 222 req = NewRequestWithJSON(t, "POST", url, &createFileOptions) 223 resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) 224 expectedAPIError := context.APIError{ 225 Message: "repository file already exists [path: " + treePath + "]", 226 URL: setting.API.SwaggerURL, 227 } 228 var apiError context.APIError 229 DecodeJSON(t, resp, &apiError) 230 assert.Equal(t, expectedAPIError, apiError) 231 232 // Test creating a file in repo1 by user4 who does not have write access 233 createFileOptions = getCreateFileOptions() 234 fileID++ 235 treePath = fmt.Sprintf("new/file%d.txt", fileID) 236 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) 237 req = NewRequestWithJSON(t, "POST", url, &createFileOptions) 238 session.MakeRequest(t, req, http.StatusNotFound) 239 240 // Tests a repo with no token given so will fail 241 createFileOptions = getCreateFileOptions() 242 fileID++ 243 treePath = fmt.Sprintf("new/file%d.txt", fileID) 244 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) 245 req = NewRequestWithJSON(t, "POST", url, &createFileOptions) 246 session.MakeRequest(t, req, http.StatusNotFound) 247 248 // Test using access token for a private repo that the user of the token owns 249 createFileOptions = getCreateFileOptions() 250 fileID++ 251 treePath = fmt.Sprintf("new/file%d.txt", fileID) 252 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) 253 req = NewRequestWithJSON(t, "POST", url, &createFileOptions) 254 session.MakeRequest(t, req, http.StatusCreated) 255 256 // Test using org repo "user3/repo3" where user2 is a collaborator 257 createFileOptions = getCreateFileOptions() 258 fileID++ 259 treePath = fmt.Sprintf("new/file%d.txt", fileID) 260 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) 261 req = NewRequestWithJSON(t, "POST", url, &createFileOptions) 262 session.MakeRequest(t, req, http.StatusCreated) 263 264 // Test using org repo "user3/repo3" with no user token 265 createFileOptions = getCreateFileOptions() 266 fileID++ 267 treePath = fmt.Sprintf("new/file%d.txt", fileID) 268 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) 269 req = NewRequestWithJSON(t, "POST", url, &createFileOptions) 270 session.MakeRequest(t, req, http.StatusNotFound) 271 272 // Test using repo "user2/repo1" where user4 is a NOT collaborator 273 createFileOptions = getCreateFileOptions() 274 fileID++ 275 treePath = fmt.Sprintf("new/file%d.txt", fileID) 276 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) 277 req = NewRequestWithJSON(t, "POST", url, &createFileOptions) 278 session.MakeRequest(t, req, http.StatusForbidden) 279 }) 280} 281