diff options
Diffstat (limited to 'tests/integration')
56 files changed, 1996 insertions, 704 deletions
diff --git a/tests/integration/actions_runner_test.go b/tests/integration/actions_runner_test.go index 75402929a1..6cc5a10e0f 100644 --- a/tests/integration/actions_runner_test.go +++ b/tests/integration/actions_runner_test.go @@ -115,10 +115,9 @@ func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1 } type mockTaskOutcome struct { - result runnerv1.Result - outputs map[string]string - logRows []*runnerv1.LogRow - execTime time.Duration + result runnerv1.Result + outputs map[string]string + logRows []*runnerv1.LogRow } func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, outcome *mockTaskOutcome) { @@ -145,7 +144,6 @@ func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, outcome *mockTa sentOutputKeys = append(sentOutputKeys, outputKey) assert.ElementsMatch(t, sentOutputKeys, resp.Msg.SentOutputs) } - time.Sleep(outcome.execTime) resp, err := r.client.runnerServiceClient.UpdateTask(t.Context(), connect.NewRequest(&runnerv1.UpdateTaskRequest{ State: &runnerv1.TaskState{ Id: task.Id, diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index f576dc38ab..088491d570 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" actions_module "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/json" @@ -633,12 +634,12 @@ jobs: assert.NotEmpty(t, addFileResp) sha = addFileResp.Commit.SHA assert.Eventually(t, func() bool { - latestCommitStatuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll) + latestCommitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll) assert.NoError(t, err) if len(latestCommitStatuses) == 0 { return false } - if latestCommitStatuses[0].State == api.CommitStatusPending { + if latestCommitStatuses[0].State == commitstatus.CommitStatusPending { insertFakeStatus(t, repo, sha, latestCommitStatuses[0].TargetURL, latestCommitStatuses[0].Context) return true } @@ -676,17 +677,17 @@ jobs: } func checkCommitStatusAndInsertFakeStatus(t *testing.T, repo *repo_model.Repository, sha string) { - latestCommitStatuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll) + latestCommitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll) assert.NoError(t, err) assert.Len(t, latestCommitStatuses, 1) - assert.Equal(t, api.CommitStatusPending, latestCommitStatuses[0].State) + assert.Equal(t, commitstatus.CommitStatusPending, latestCommitStatuses[0].State) insertFakeStatus(t, repo, sha, latestCommitStatuses[0].TargetURL, latestCommitStatuses[0].Context) } func insertFakeStatus(t *testing.T, repo *repo_model.Repository, sha, targetURL, context string) { err := commitstatus_service.CreateCommitStatus(db.DefaultContext, repo, user_model.NewActionsUser(), sha, &git_model.CommitStatus{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: targetURL, Context: context, }) @@ -719,7 +720,7 @@ func TestWorkflowDispatchPublicApi(t *testing.T) { { Operation: "create", TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(`name: test + ContentReader: strings.NewReader(` on: workflow_dispatch jobs: @@ -799,7 +800,7 @@ func TestWorkflowDispatchPublicApiWithInputs(t *testing.T) { { Operation: "create", TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(`name: test + ContentReader: strings.NewReader(` on: workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } jobs: @@ -890,7 +891,7 @@ func TestWorkflowDispatchPublicApiJSON(t *testing.T) { { Operation: "create", TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(`name: test + ContentReader: strings.NewReader(` on: workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } jobs: @@ -976,7 +977,7 @@ func TestWorkflowDispatchPublicApiWithInputsJSON(t *testing.T) { { Operation: "create", TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(`name: test + ContentReader: strings.NewReader(` on: workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } jobs: @@ -1070,7 +1071,7 @@ func TestWorkflowDispatchPublicApiWithInputsNonDefaultBranchJSON(t *testing.T) { { Operation: "create", TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(`name: test + ContentReader: strings.NewReader(` on: workflow_dispatch jobs: @@ -1106,7 +1107,7 @@ jobs: { Operation: "update", TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(`name: test + ContentReader: strings.NewReader(` on: workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } jobs: @@ -1156,6 +1157,7 @@ jobs: run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ Title: "add workflow", RepoID: repo.ID, + Repo: repo, Event: "workflow_dispatch", Ref: "refs/heads/dispatch", WorkflowID: "dispatch.yml", @@ -1207,7 +1209,7 @@ func TestWorkflowApi(t *testing.T) { { Operation: "create", TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(`name: test + ContentReader: strings.NewReader(` on: workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } jobs: @@ -1448,3 +1450,157 @@ jobs: assert.Equal(t, pullRequest.MergedCommitID, actionRun.CommitSHA) }) } + +func TestActionRunNameWithContextVariables(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "action-run-name-with-variables", + Description: "test action run name", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/runname.yml", + ContentReader: strings.NewReader(`name: test +on: + [create,delete] +run-name: ${{ gitea.actor }} is running this workflow +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow with run-name", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + // Get the commit ID of the default branch + gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo) + assert.NoError(t, err) + defer gitRepo.Close() + branch, err := git_model.GetBranch(db.DefaultContext, repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + + // create a branch + err = repo_service.CreateNewBranchFromCommit(db.DefaultContext, user2, repo, gitRepo, branch.CommitID, "test-action-run-name-with-variables") + assert.NoError(t, err) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: user2.LoginName + " is running this workflow", + RepoID: repo.ID, + Event: "create", + Ref: "refs/heads/test-action-run-name-with-variables", + WorkflowID: "runname.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + }) +} + +func TestActionRunName(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "action-run-name", + Description: "test action run-name", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/runname.yml", + ContentReader: strings.NewReader(`name: test +on: + [create,delete] +run-name: run name without variables +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow with run name", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + // Get the commit ID of the default branch + gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo) + assert.NoError(t, err) + defer gitRepo.Close() + branch, err := git_model.GetBranch(db.DefaultContext, repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + + // create a branch + err = repo_service.CreateNewBranchFromCommit(db.DefaultContext, user2, repo, gitRepo, branch.CommitID, "test-action-run-name") + assert.NoError(t, err) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "run name without variables", + RepoID: repo.ID, + Event: "create", + Ref: "refs/heads/test-action-run-name", + WorkflowID: "runname.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + }) +} diff --git a/tests/integration/api_actions_runner_test.go b/tests/integration/api_actions_runner_test.go index ace7aa381a..fb9ba5b0c2 100644 --- a/tests/integration/api_actions_runner_test.go +++ b/tests/integration/api_actions_runner_test.go @@ -41,8 +41,6 @@ func testActionsRunnerAdmin(t *testing.T) { runnerList := api.ActionRunnersResponse{} DecodeJSON(t, runnerListResp, &runnerList) - assert.Len(t, runnerList.Entries, 4) - idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 }) require.NotEqual(t, -1, idx) expectedRunner := runnerList.Entries[idx] @@ -160,16 +158,20 @@ func testActionsRunnerOwner(t *testing.T) { runnerList := api.ActionRunnersResponse{} DecodeJSON(t, runnerListResp, &runnerList) - assert.Len(t, runnerList.Entries, 1) - assert.Equal(t, "runner_to_be_deleted-org", runnerList.Entries[0].Name) - assert.Equal(t, int64(34347), runnerList.Entries[0].ID) - assert.False(t, runnerList.Entries[0].Ephemeral) - assert.Len(t, runnerList.Entries[0].Labels, 2) - assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name) - assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name) + idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34347 }) + require.NotEqual(t, -1, idx) + expectedRunner := runnerList.Entries[idx] + + require.NotNil(t, expectedRunner) + assert.Equal(t, "runner_to_be_deleted-org", expectedRunner.Name) + assert.Equal(t, int64(34347), expectedRunner.ID) + assert.False(t, expectedRunner.Ephemeral) + assert.Len(t, expectedRunner.Labels, 2) + assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name) + assert.Equal(t, "linux", expectedRunner.Labels[1].Name) // Verify get the runner by id - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token) runnerResp := MakeRequest(t, req, http.StatusOK) runner := api.ActionRunner{} @@ -183,11 +185,11 @@ func testActionsRunnerOwner(t *testing.T) { assert.Equal(t, "linux", runner.Labels[1].Name) // Verify delete the runner by id - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // Verify runner deletion - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) }) @@ -329,4 +331,12 @@ func testActionsRunnerRepo(t *testing.T) { req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) }) + + t.Run("DeleteAdminRunnerNotFoundUnknownID", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository) + // Verify delete a runner by unknown id is not found + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 4384797347934)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) } diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index 083535a9a5..b30cdfd0fc 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -263,7 +263,7 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) var req *RequestWrapper var resp *httptest.ResponseRecorder - for i := 0; i < 6; i++ { + for range 6 { req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{ MergeMessageField: "doAPIMergePullRequest Merge", Do: string(repo_model.MergeStyleMerge), diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index e035f7200b..370c90a100 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -166,7 +166,7 @@ func TestAPICreateIssueParallel(t *testing.T) { urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name) var wg sync.WaitGroup - for i := 0; i < 10; i++ { + for i := range 10 { wg.Add(1) go func(parentT *testing.T, i int) { parentT.Run(fmt.Sprintf("ParallelCreateIssue_%d", i), func(t *testing.T) { @@ -267,10 +267,7 @@ func TestAPISearchIssues(t *testing.T) { defer tests.PrepareTestEnv(t)() // as this API was used in the frontend, it uses UI page size - expectedIssueCount := 20 // from the fixtures - if expectedIssueCount > setting.UI.IssuePagingNum { - expectedIssueCount = setting.UI.IssuePagingNum - } + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures link, _ := url.Parse("/api/v1/repos/issues/search") token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue) @@ -371,10 +368,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) { defer tests.PrepareTestEnv(t)() // as this API was used in the frontend, it uses UI page size - expectedIssueCount := 20 // from the fixtures - if expectedIssueCount > setting.UI.IssuePagingNum { - expectedIssueCount = setting.UI.IssuePagingNum - } + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures link, _ := url.Parse("/api/v1/repos/issues/search") token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue) diff --git a/tests/integration/api_packages_chef_test.go b/tests/integration/api_packages_chef_test.go index 86b3be9d0c..8f2c2592e7 100644 --- a/tests/integration/api_packages_chef_test.go +++ b/tests/integration/api_packages_chef_test.go @@ -181,7 +181,7 @@ nwIDAQAB var data []byte if version == "1.3" { - data = []byte(fmt.Sprintf( + data = fmt.Appendf(nil, "Method:%s\nPath:%s\nX-Ops-Content-Hash:%s\nX-Ops-Sign:version=%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s\nX-Ops-Server-API-Version:%s", req.Method, path.Clean(req.URL.Path), @@ -190,17 +190,17 @@ nwIDAQAB req.Header.Get("X-Ops-Timestamp"), username, req.Header.Get("X-Ops-Server-Api-Version"), - )) + ) } else { sum := sha1.Sum([]byte(path.Clean(req.URL.Path))) - data = []byte(fmt.Sprintf( + data = fmt.Appendf(nil, "Method:%s\nHashed Path:%s\nX-Ops-Content-Hash:%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s", req.Method, base64.StdEncoding.EncodeToString(sum[:]), req.Header.Get("X-Ops-Content-Hash"), req.Header.Get("X-Ops-Timestamp"), username, - )) + ) } for k := range req.Header { diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index b2db77685d..204f099bbe 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto/sha256" "encoding/base64" + "encoding/hex" "fmt" "net/http" "strconv" @@ -17,7 +18,6 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" - container_model "code.gitea.io/gitea/models/packages/container" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" container_module "code.gitea.io/gitea/modules/packages/container" @@ -57,7 +57,7 @@ func TestPackageContainer(t *testing.T) { return values } - images := []string{"test", "te/st"} + images := []string{"test", "sub/name"} tags := []string{"latest", "main"} multiTag := "multi" @@ -70,7 +70,8 @@ func TestPackageContainer(t *testing.T) { configContent := `{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/true"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"container":"b89fe92a887d55c0961f02bdfbfd8ac3ddf66167db374770d2d9e9fab3311510","container_config":{"Hostname":"b89fe92a887d","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/true\"]"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"created":"2022-01-01T00:00:00.000000000Z","docker_version":"20.10.12","history":[{"created":"2022-01-01T00:00:00.000000000Z","created_by":"/bin/sh -c #(nop) COPY file:0e7589b0c800daaf6fa460d2677101e4676dd9491980210cb345480e513f3602 in /true "},{"created":"2022-01-01T00:00:00.000000001Z","created_by":"/bin/sh -c #(nop) CMD [\"/true\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:0ff3b91bdf21ecdf2f2f3d4372c2098a14dbe06cd678e8f0a85fd4902d00e2e2"]}}` manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6" - manifestContent := `{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` + manifestContent := `{"schemaVersion":2,"mediaType":"` + container_module.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` + manifestContentType := container_module.ContentTypeDockerDistributionManifestV2 untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d" untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` @@ -250,7 +251,7 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location")) assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) - pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_model.UploadVersion) + pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_module.UploadVersion) assert.NoError(t, err) pfs, err := packages_model.GetFilesByVersionID(db.DefaultContext, pv.ID) @@ -296,11 +297,22 @@ func TestPackageContainer(t *testing.T) { SetHeader("Content-Range", "1-10") MakeRequest(t, req, http.StatusRequestedRangeNotSatisfiable) - contentRange := fmt.Sprintf("0-%d", len(blobContent)-1) - req.SetHeader("Content-Range", contentRange) + // first patch without Content-Range + req = NewRequestWithBody(t, "PATCH", setting.AppURL+uploadURL[1:], bytes.NewReader(blobContent[:1])). + AddTokenAuth(userToken) + resp = MakeRequest(t, req, http.StatusAccepted) + assert.NotEmpty(t, resp.Header().Get("Location")) + assert.Equal(t, "0-0", resp.Header().Get("Range")) + + // then send remaining content with Content-Range + req = NewRequestWithBody(t, "PATCH", setting.AppURL+uploadURL[1:], bytes.NewReader(blobContent[1:])). + SetHeader("Content-Range", fmt.Sprintf("1-%d", len(blobContent)-1)). + AddTokenAuth(userToken) resp = MakeRequest(t, req, http.StatusAccepted) + contentRange := fmt.Sprintf("0-%d", len(blobContent)-1) assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid")) + assert.NotEmpty(t, resp.Header().Get("Location")) assert.Equal(t, contentRange, resp.Header().Get("Range")) uploadURL = resp.Header().Get("Location") @@ -310,7 +322,8 @@ func TestPackageContainer(t *testing.T) { resp = MakeRequest(t, req, http.StatusNoContent) assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid")) - assert.Equal(t, fmt.Sprintf("0-%d", len(blobContent)), resp.Header().Get("Range")) + assert.Equal(t, uploadURL, resp.Header().Get("Location")) + assert.Equal(t, contentRange, resp.Header().Get("Range")) pbu, err = packages_model.GetBlobUploadByID(db.DefaultContext, uuid) assert.NoError(t, err) @@ -341,7 +354,8 @@ func TestPackageContainer(t *testing.T) { resp = MakeRequest(t, req, http.StatusNoContent) assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid")) - assert.Equal(t, "0-0", resp.Header().Get("Range")) + // FIXME: undefined behavior when the uploaded content is empty: https://github.com/opencontainers/distribution-spec/issues/578 + assert.Nil(t, resp.Header().Values("Range")) req = NewRequest(t, "DELETE", setting.AppURL+uploadURL[1:]). AddTokenAuth(userToken) @@ -429,7 +443,7 @@ func TestPackageContainer(t *testing.T) { assert.Len(t, pd.Files, 3) for _, pfd := range pd.Files { switch pfd.File.Name { - case container_model.ManifestFilename: + case container_module.ManifestFilename: assert.True(t, pfd.File.IsLead) assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, manifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) @@ -492,7 +506,7 @@ func TestPackageContainer(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length")) - assert.Equal(t, oci.MediaTypeImageManifest, resp.Header().Get("Content-Type")) + assert.Equal(t, manifestContentType, resp.Header().Get("Content-Type")) assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) assert.Equal(t, manifestContent, resp.Body.String()) }) @@ -531,7 +545,7 @@ func TestPackageContainer(t *testing.T) { assert.Len(t, pd.Files, 3) for _, pfd := range pd.Files { - if pfd.File.Name == container_model.ManifestFilename { + if pfd.File.Name == container_module.ManifestFilename { assert.True(t, pfd.File.IsLead) assert.Equal(t, oci.MediaTypeImageManifest, pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, untaggedManifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) @@ -623,6 +637,22 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, blobContent, resp.Body.Bytes()) }) + t.Run("GetBlob/Empty", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + emptyDigestBuf := sha256.Sum256(nil) + emptyDigest := "sha256:" + hex.EncodeToString(emptyDigestBuf[:]) + req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, emptyDigest), strings.NewReader("")).AddTokenAuth(userToken) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, emptyDigest)).AddTokenAuth(userToken) + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "0", resp.Header().Get("Content-Length")) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/blobs/%s", url, emptyDigest)).AddTokenAuth(userToken) + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "0", resp.Header().Get("Content-Length")) + }) + t.Run("GetTagList", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -732,7 +762,7 @@ func TestPackageContainer(t *testing.T) { url := fmt.Sprintf("%sv2/%s/parallel", setting.AppURL, user.Name) var wg sync.WaitGroup - for i := 0; i < 10; i++ { + for i := range 10 { wg.Add(1) content := []byte{byte(i)} diff --git a/tests/integration/api_packages_debian_test.go b/tests/integration/api_packages_debian_test.go index 98027d774c..3ae60d2aa2 100644 --- a/tests/integration/api_packages_debian_test.go +++ b/tests/integration/api_packages_debian_test.go @@ -284,7 +284,7 @@ func TestPackageDebian(t *testing.T) { // because "Iterate" keeps a dangling SQL session but the callback function still uses the same session to execute statements. // The "Iterate" problem has been checked by TestContextSafety now, so here we only need to check the cleanup logic with a small number packagesCount := 2 - for i := 0; i < packagesCount; i++ { + for i := range packagesCount { uploadURL := fmt.Sprintf("%s/pool/%s/%s/upload", rootURL, "test", "main") req := NewRequestWithBody(t, "PUT", uploadURL, createArchive(packageName, "1.0."+strconv.Itoa(i), "all")).AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusCreated) diff --git a/tests/integration/api_packages_goproxy_test.go b/tests/integration/api_packages_goproxy_test.go index dab9fefc5e..fa0ee5b901 100644 --- a/tests/integration/api_packages_goproxy_test.go +++ b/tests/integration/api_packages_goproxy_test.go @@ -87,7 +87,7 @@ func TestPackageGo(t *testing.T) { AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusConflict) - time.Sleep(time.Second) + time.Sleep(time.Second) // Ensure the timestamp is different, then the "list" below can have stable order content = createArchive(map[string][]byte{ packageName + "@" + packageVersion2 + "/go.mod": []byte(goModContent), diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go index 408c8805c2..30ef1884cd 100644 --- a/tests/integration/api_packages_maven_test.go +++ b/tests/integration/api_packages_maven_test.go @@ -321,7 +321,7 @@ func TestPackageMavenConcurrent(t *testing.T) { defer tests.PrintCurrentTest(t)() var wg sync.WaitGroup - for i := 0; i < 10; i++ { + for i := range 10 { wg.Add(1) go func(i int) { putFile(t, fmt.Sprintf("/%s/%s.jar", packageVersion, strconv.Itoa(i)), "test", http.StatusCreated) diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index c0e69a82cd..65b1b9845a 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -46,21 +46,30 @@ func TestPackageNuGet(t *testing.T) { defer tests.PrepareTestEnv(t)() type FeedEntryProperties struct { - Version string `xml:"Version"` - NormalizedVersion string `xml:"NormalizedVersion"` Authors string `xml:"Authors"` + Copyright string `xml:"Copyright,omitempty"` + Created nuget.TypedValue[time.Time] `xml:"Created"` Dependencies string `xml:"Dependencies"` Description string `xml:"Description"` - VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"` + DevelopmentDependency nuget.TypedValue[bool] `xml:"DevelopmentDependency"` DownloadCount nuget.TypedValue[int64] `xml:"DownloadCount"` - PackageSize nuget.TypedValue[int64] `xml:"PackageSize"` - Created nuget.TypedValue[time.Time] `xml:"Created"` + ID string `xml:"Id"` + IconURL string `xml:"IconUrl,omitempty"` + Language string `xml:"Language,omitempty"` LastUpdated nuget.TypedValue[time.Time] `xml:"LastUpdated"` - Published nuget.TypedValue[time.Time] `xml:"Published"` + LicenseURL string `xml:"LicenseUrl,omitempty"` + MinClientVersion string `xml:"MinClientVersion,omitempty"` + NormalizedVersion string `xml:"NormalizedVersion"` + Owners string `xml:"Owners,omitempty"` + PackageSize nuget.TypedValue[int64] `xml:"PackageSize"` ProjectURL string `xml:"ProjectUrl,omitempty"` + Published nuget.TypedValue[time.Time] `xml:"Published"` ReleaseNotes string `xml:"ReleaseNotes,omitempty"` RequireLicenseAcceptance nuget.TypedValue[bool] `xml:"RequireLicenseAcceptance"` + Tags string `xml:"Tags,omitempty"` Title string `xml:"Title"` + Version string `xml:"Version"` + VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"` } type FeedEntry struct { @@ -86,28 +95,54 @@ func TestPackageNuGet(t *testing.T) { readToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadPackage) badToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadNotification) - packageName := "test.package" + packageName := "test.package" // id + packageID := packageName packageVersion := "1.0.3" packageAuthors := "KN4CK3R" packageDescription := "Gitea Test Package" + symbolFilename := "test.pdb" symbolID := "d910bb6948bd4c6cb40155bcf52c3c94" + packageCopyright := "Package Copyright" + packageIconURL := "https://gitea.io/favicon.png" + packageLanguage := "Package Language" + packageLicenseURL := "https://gitea.io/license" + packageMinClientVersion := "1.0.0.0" + packageOwners := "Package Owners" + packageProjectURL := "https://gitea.io" + packageReleaseNotes := "Package Release Notes" + packageTags := "tag_1 tag_2 tag_3" + packageTitle := "Package Title" + packageDevelopmentDependency := true + packageRequireLicenseAcceptance := true + createNuspec := func(id, version string) string { return `<?xml version="1.0" encoding="utf-8"?> -<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> - <metadata> - <id>` + id + `</id> - <version>` + version + `</version> - <authors>` + packageAuthors + `</authors> - <description>` + packageDescription + `</description> - <dependencies> - <group targetFramework=".NETStandard2.0"> - <dependency id="Microsoft.CSharp" version="4.5.0" /> - </group> - </dependencies> - </metadata> -</package>` + <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> + <metadata minClientVersion="` + packageMinClientVersion + `"> + <authors>` + packageAuthors + `</authors> + <copyright>` + packageCopyright + `</copyright> + <description>` + packageDescription + `</description> + <developmentDependency>true</developmentDependency> + <iconUrl>` + packageIconURL + `</iconUrl> + <id>` + id + `</id> + <language>` + packageLanguage + `</language> + <licenseUrl>` + packageLicenseURL + `</licenseUrl> + <owners>` + packageOwners + `</owners> + <projectUrl>` + packageProjectURL + `</projectUrl> + <releaseNotes>` + packageReleaseNotes + `</releaseNotes> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <tags>` + packageTags + `</tags> + <title>` + packageTitle + `</title> + <version>` + version + `</version> + <dependencies> + <group targetFramework=".NETStandard2.0"> + <dependency id="Microsoft.CSharp" version="4.5.0" /> + </group> + </dependencies> + </metadata> + </package>` } createPackage := func(id, version string) *bytes.Buffer { @@ -393,7 +428,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) - assert.Equal(t, int64(412), pb.Size) + assert.Equal(t, int64(610), pb.Size) case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion): assert.False(t, pf.IsLead) @@ -405,7 +440,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) - assert.Equal(t, int64(427), pb.Size) + assert.Equal(t, int64(996), pb.Size) case symbolFilename: assert.False(t, pf.IsLead) @@ -736,10 +771,24 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) var result FeedEntry decodeXML(t, resp, &result) - assert.Equal(t, packageName, result.Properties.Title) - assert.Equal(t, packageVersion, result.Properties.Version) assert.Equal(t, packageAuthors, result.Properties.Authors) assert.Equal(t, packageDescription, result.Properties.Description) + assert.Equal(t, packageID, result.Properties.ID) + assert.Equal(t, packageVersion, result.Properties.Version) + + assert.Equal(t, packageCopyright, result.Properties.Copyright) + assert.Equal(t, packageDevelopmentDependency, result.Properties.DevelopmentDependency.Value) + assert.Equal(t, packageIconURL, result.Properties.IconURL) + assert.Equal(t, packageLanguage, result.Properties.Language) + assert.Equal(t, packageLicenseURL, result.Properties.LicenseURL) + assert.Equal(t, packageMinClientVersion, result.Properties.MinClientVersion) + assert.Equal(t, packageOwners, result.Properties.Owners) + assert.Equal(t, packageProjectURL, result.Properties.ProjectURL) + assert.Equal(t, packageReleaseNotes, result.Properties.ReleaseNotes) + assert.Equal(t, packageRequireLicenseAcceptance, result.Properties.RequireLicenseAcceptance.Value) + assert.Equal(t, packageTags, result.Properties.Tags) + assert.Equal(t, packageTitle, result.Properties.Title) + assert.Equal(t, "Microsoft.CSharp:4.5.0:.NETStandard2.0", result.Properties.Dependencies) }) diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 469bd1fc6c..bd1959f64e 100644 --- a/tests/integration/api_packages_rpm_test.go +++ b/tests/integration/api_packages_rpm_test.go @@ -157,9 +157,14 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + // download the package without the file name req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)) resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, content, resp.Body.Bytes()) + // download the package with a file name (it can be anything) + req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s/any-file-name", groupURL, packageName, packageVersion, packageArchitecture)) + resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, content, resp.Body.Bytes()) }) @@ -447,7 +452,8 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, pub, err := openpgp.ReadArmoredKeyRing(gpgResp.Body) require.NoError(t, err) - req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)) + rpmFileName := fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture) + req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture, rpmFileName)) resp := MakeRequest(t, req, http.StatusOK) _, sigs, err := rpmutils.Verify(resp.Body, pub) diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 786addbd76..f10b098885 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -15,9 +15,9 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" - container_model "code.gitea.io/gitea/models/packages/container" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + container_module "code.gitea.io/gitea/modules/packages/container" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -538,7 +538,7 @@ func TestPackageCleanup(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, pbs) - _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion) + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion) assert.NoError(t, err) err = packages_cleanup_service.CleanupTask(db.DefaultContext, duration) @@ -548,7 +548,7 @@ func TestPackageCleanup(t *testing.T) { assert.NoError(t, err) assert.Empty(t, pbs) - _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion) + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion) assert.ErrorIs(t, err, packages_model.ErrPackageNotExist) }) @@ -636,12 +636,16 @@ func TestPackageCleanup(t *testing.T) { }, { Name: "Mixed", - Versions: []version{ - {Version: "keep", ShouldExist: true, Created: time.Now().Add(time.Duration(10000)).Unix()}, - {Version: "dummy", ShouldExist: true, Created: 1}, - {Version: "test-3", ShouldExist: true}, - {Version: "test-4", ShouldExist: false, Created: 1}, - }, + Versions: func(limit, removeDays int) []version { + aa := []version{ + {Version: "keep", ShouldExist: true, Created: time.Now().Add(time.Duration(10000)).Unix()}, + {Version: "dummy", ShouldExist: true, Created: 1}, + } + for i := range limit { + aa = append(aa, version{Version: fmt.Sprintf("test-%v", i+3), ShouldExist: util.Iif(i < removeDays, true, false), Created: time.Now().AddDate(0, 0, -i).Unix()}) + } + return aa + }(220, 7), Rule: &packages_model.PackageCleanupRule{ Enabled: true, KeepCount: 1, @@ -686,7 +690,7 @@ func TestPackageCleanup(t *testing.T) { err = packages_service.DeletePackageVersionAndReferences(db.DefaultContext, pv) assert.NoError(t, err) } else { - assert.ErrorIs(t, err, packages_model.ErrPackageNotExist) + assert.ErrorIs(t, err, packages_model.ErrPackageNotExist, v.Version) } } diff --git a/tests/integration/api_repo_archive_test.go b/tests/integration/api_repo_archive_test.go index e698148d84..97c2c0d54b 100644 --- a/tests/integration/api_repo_archive_test.go +++ b/tests/integration/api_repo_archive_test.go @@ -12,7 +12,9 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/tests" @@ -58,9 +60,12 @@ func TestAPIDownloadArchive(t *testing.T) { link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name)) MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest) + + t.Run("GitHubStyle", testAPIDownloadArchiveGitHubStyle) + t.Run("PrivateRepo", testAPIDownloadArchivePrivateRepo) } -func TestAPIDownloadArchive2(t *testing.T) { +func testAPIDownloadArchiveGitHubStyle(t *testing.T) { defer tests.PrepareTestEnv(t)() repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) @@ -95,7 +100,13 @@ func TestAPIDownloadArchive2(t *testing.T) { bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) assert.Len(t, bs, 382) +} - link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name)) - MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest) +func testAPIDownloadArchivePrivateRepo(t *testing.T) { + _ = repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), &repo_model.Repository{ID: 1, IsPrivate: true}, "is_private") + MakeRequest(t, NewRequest(t, "HEAD", "/api/v1/repos/user2/repo1/archive/master.zip"), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "HEAD", "/api/v1/repos/user2/repo1/zipball/master"), http.StatusNotFound) + _ = repo_model.UpdateRepoUnitPublicAccess(t.Context(), &repo_model.RepoUnit{RepoID: 1, Type: unit.TypeCode, AnonymousAccessMode: perm.AccessModeRead}) + MakeRequest(t, NewRequest(t, "HEAD", "/api/v1/repos/user2/repo1/archive/master.zip"), http.StatusOK) + MakeRequest(t, NewRequest(t, "HEAD", "/api/v1/repos/user2/repo1/zipball/master"), http.StatusOK) } diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index 0a7f37facb..df0fc3dd05 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -130,7 +130,7 @@ func BenchmarkAPICreateFileSmall(b *testing.B) { repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo b.ResetTimer() - for n := 0; n < b.N; n++ { + for n := 0; b.Loop(); n++ { treePath := fmt.Sprintf("update/file%d.txt", n) _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) } @@ -145,7 +145,7 @@ func BenchmarkAPICreateFileMedium(b *testing.B) { repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo b.ResetTimer() - for n := 0; n < b.N; n++ { + for n := 0; b.Loop(); n++ { treePath := fmt.Sprintf("update/file%d.txt", n) copy(data, treePath) _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go index 47730cd933..9dd47f93e6 100644 --- a/tests/integration/api_repo_file_delete_test.go +++ b/tests/integration/api_repo_file_delete_test.go @@ -20,20 +20,22 @@ import ( func getDeleteFileOptions() *api.DeleteFileOptions { return &api.DeleteFileOptions{ - FileOptions: api.FileOptions{ - BranchName: "master", - NewBranchName: "master", - Message: "Removing the file new/file.txt", - Author: api.Identity{ - Name: "John Doe", - Email: "johndoe@example.com", - }, - Committer: api.Identity{ - Name: "Jane Doe", - Email: "janedoe@example.com", + FileOptionsWithSHA: api.FileOptionsWithSHA{ + FileOptions: api.FileOptions{ + BranchName: "master", + NewBranchName: "master", + Message: "Removing the file new/file.txt", + Author: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + Committer: api.Identity{ + Name: "Jane Doe", + Email: "janedoe@example.com", + }, }, + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", }, - SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", } } @@ -110,7 +112,7 @@ func TestAPIDeleteFile(t *testing.T) { deleteFileOptions.SHA = "badsha" req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions). AddTokenAuth(token2) - MakeRequest(t, req, http.StatusBadRequest) + MakeRequest(t, req, http.StatusUnprocessableEntity) // Test creating a file in repo16 by user4 who does not have write access fileID++ diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index 1605cfbd0b..6a7f7529a0 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -27,7 +27,7 @@ func getUpdateFileOptions() *api.UpdateFileOptions { content := "This is updated text" contentEncoded := base64.StdEncoding.EncodeToString([]byte(content)) return &api.UpdateFileOptions{ - DeleteFileOptions: api.DeleteFileOptions{ + FileOptionsWithSHA: api.FileOptionsWithSHA{ FileOptions: api.FileOptions{ BranchName: "master", NewBranchName: "master", diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go index a7de97c052..9517db4c87 100644 --- a/tests/integration/api_repo_get_contents_test.go +++ b/tests/integration/api_repo_get_contents_test.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/url" + "slices" "testing" "time" @@ -20,9 +21,9 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" repo_service "code.gitea.io/gitea/services/repository" - "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string) *api.ContentsResponse { @@ -54,7 +55,11 @@ func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string) } func TestAPIGetContents(t *testing.T) { - onGiteaRun(t, testAPIGetContents) + onGiteaRun(t, func(t *testing.T, u *url.URL) { + testAPIGetContentsRefFormats(t) + testAPIGetContents(t, u) + testAPIGetContentsExt(t) + }) } func testAPIGetContents(t *testing.T, u *url.URL) { @@ -76,20 +81,20 @@ func testAPIGetContents(t *testing.T, u *url.URL) { // Get the commit ID of the default branch gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo1) - assert.NoError(t, err) + require.NoError(t, err) defer gitRepo.Close() // Make a new branch in repo1 newBranch := "test_branch" err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch) - assert.NoError(t, err) + require.NoError(t, err) commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch) - assert.NoError(t, err) + require.NoError(t, err) // Make a new tag in repo1 newTag := "test_tag" err = gitRepo.CreateTag(newTag, commitID) - assert.NoError(t, err) + require.NoError(t, err) /*** END SETUP ***/ // ref is default ref @@ -99,7 +104,6 @@ func testAPIGetContents(t *testing.T, u *url.URL) { resp := MakeRequest(t, req, http.StatusOK) var contentsResponse api.ContentsResponse DecodeJSON(t, resp, &contentsResponse) - assert.NotNil(t, contentsResponse) lastCommit, _ := gitRepo.GetCommitByPath("README.md") expectedContentsResponse := getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) assert.Equal(t, *expectedContentsResponse, contentsResponse) @@ -109,7 +113,6 @@ func testAPIGetContents(t *testing.T, u *url.URL) { req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsResponse) - assert.NotNil(t, contentsResponse) expectedContentsResponse = getExpectedContentsResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String()) assert.Equal(t, *expectedContentsResponse, contentsResponse) @@ -119,7 +122,6 @@ func testAPIGetContents(t *testing.T, u *url.URL) { req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsResponse) - assert.NotNil(t, contentsResponse) branchCommit, _ := gitRepo.GetBranchCommit(ref) lastCommit, _ = branchCommit.GetCommitByPath("README.md") expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) @@ -131,7 +133,6 @@ func testAPIGetContents(t *testing.T, u *url.URL) { req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsResponse) - assert.NotNil(t, contentsResponse) tagCommit, _ := gitRepo.GetTagCommit(ref) lastCommit, _ = tagCommit.GetCommitByPath("README.md") expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) @@ -143,7 +144,6 @@ func testAPIGetContents(t *testing.T, u *url.URL) { req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsResponse) - assert.NotNil(t, contentsResponse) expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, commitID) assert.Equal(t, *expectedContentsResponse, contentsResponse) @@ -168,9 +168,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { MakeRequest(t, req, http.StatusOK) } -func TestAPIGetContentsRefFormats(t *testing.T) { - defer tests.PrepareTestEnv(t)() - +func testAPIGetContentsRefFormats(t *testing.T) { file := "README.md" sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d" content := "# repo1\n\nDescription for repo1" @@ -203,3 +201,76 @@ func TestAPIGetContentsRefFormats(t *testing.T) { // FIXME: this is an incorrect behavior, non-existing branch falls back to default branch _ = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/README.md?ref=no-such"), http.StatusOK) } + +func testAPIGetContentsExt(t *testing.T) { + session := loginUser(t, "user2") + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + t.Run("DirContents", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check") + resp := MakeRequest(t, req, http.StatusOK) + var contentsResponse api.ContentsExtResponse + DecodeJSON(t, resp, &contentsResponse) + assert.Nil(t, contentsResponse.FileContents) + assert.Equal(t, "README.md", contentsResponse.DirContents[0].Name) + assert.Nil(t, contentsResponse.DirContents[0].Encoding) + assert.Nil(t, contentsResponse.DirContents[0].Content) + + // "includes=file_content" shouldn't affect directory listing + req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check&includes=file_content") + resp = MakeRequest(t, req, http.StatusOK) + contentsResponse = api.ContentsExtResponse{} + DecodeJSON(t, resp, &contentsResponse) + assert.Nil(t, contentsResponse.FileContents) + assert.Equal(t, "README.md", contentsResponse.DirContents[0].Name) + assert.Nil(t, contentsResponse.DirContents[0].Encoding) + assert.Nil(t, contentsResponse.DirContents[0].Content) + + req = NewRequestf(t, "GET", "/api/v1/repos/user2/lfs/contents-ext?includes=file_content,lfs_metadata").AddTokenAuth(token2) + resp = session.MakeRequest(t, req, http.StatusOK) + contentsResponse = api.ContentsExtResponse{} + DecodeJSON(t, resp, &contentsResponse) + assert.Nil(t, contentsResponse.FileContents) + respFileIdx := slices.IndexFunc(contentsResponse.DirContents, func(response *api.ContentsResponse) bool { return response.Name == "jpeg.jpg" }) + require.NotEqual(t, -1, respFileIdx) + respFile := contentsResponse.DirContents[respFileIdx] + assert.Equal(t, "jpeg.jpg", respFile.Name) + assert.Nil(t, respFile.Encoding) + assert.Nil(t, respFile.Content) + assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize) + assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid) + }) + t.Run("FileContents", func(t *testing.T) { + // by default, no file content is returned + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs/README.md?ref=sub-home-md-img-check") + resp := MakeRequest(t, req, http.StatusOK) + var contentsResponse api.ContentsExtResponse + DecodeJSON(t, resp, &contentsResponse) + assert.Nil(t, contentsResponse.DirContents) + assert.Equal(t, "README.md", contentsResponse.FileContents.Name) + assert.Nil(t, contentsResponse.FileContents.Encoding) + assert.Nil(t, contentsResponse.FileContents.Content) + + // file content is only returned when `includes=file_content` + req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs/README.md?ref=sub-home-md-img-check&includes=file_content") + resp = MakeRequest(t, req, http.StatusOK) + contentsResponse = api.ContentsExtResponse{} + DecodeJSON(t, resp, &contentsResponse) + assert.Nil(t, contentsResponse.DirContents) + assert.Equal(t, "README.md", contentsResponse.FileContents.Name) + assert.NotNil(t, contentsResponse.FileContents.Encoding) + assert.NotNil(t, contentsResponse.FileContents.Content) + + req = NewRequestf(t, "GET", "/api/v1/repos/user2/lfs/contents-ext/jpeg.jpg?includes=file_content").AddTokenAuth(token2) + resp = session.MakeRequest(t, req, http.StatusOK) + contentsResponse = api.ContentsExtResponse{} + DecodeJSON(t, resp, &contentsResponse) + assert.Nil(t, contentsResponse.DirContents) + assert.NotNil(t, contentsResponse.FileContents) + respFile := contentsResponse.FileContents + assert.Equal(t, "jpeg.jpg", respFile.Name) + assert.NotNil(t, respFile.Encoding) + assert.NotNil(t, respFile.Content) + assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize) + assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid) + }) +} diff --git a/tests/integration/api_repo_languages_test.go b/tests/integration/api_repo_languages_test.go index 1045aef57d..6347a43b4e 100644 --- a/tests/integration/api_repo_languages_test.go +++ b/tests/integration/api_repo_languages_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "code.gitea.io/gitea/modules/test" + "github.com/stretchr/testify/assert" ) @@ -32,7 +34,8 @@ func TestRepoLanguages(t *testing.T) { "content": "package main", "commit_choice": "direct", }) - session.MakeRequest(t, req, http.StatusSeeOther) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.NotEmpty(t, test.RedirectURL(resp)) // let gitea calculate language stats time.Sleep(time.Second) diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go index 6b42b83bc5..ec6a3a3b57 100644 --- a/tests/integration/api_repo_lfs_test.go +++ b/tests/integration/api_repo_lfs_test.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -226,9 +227,7 @@ func TestAPILFSBatch(t *testing.T) { t.Run("FileTooBig", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - - oldMaxFileSize := setting.LFS.MaxFileSize - setting.LFS.MaxFileSize = 2 + defer test.MockVariableValue(&setting.LFS.MaxFileSize, 2)() req := newRequest(t, &lfs.BatchRequest{ Operation: "upload", @@ -243,8 +242,6 @@ func TestAPILFSBatch(t *testing.T) { assert.NotNil(t, br.Objects[0].Error) assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) assert.Equal(t, "Size must be less than or equal to 2", br.Objects[0].Error.Message) - - setting.LFS.MaxFileSize = oldMaxFileSize }) t.Run("AddMeta", func(t *testing.T) { diff --git a/tests/integration/api_repo_license_test.go b/tests/integration/api_repo_license_test.go index 52d3085694..fb4450a2bd 100644 --- a/tests/integration/api_repo_license_test.go +++ b/tests/integration/api_repo_license_test.go @@ -12,12 +12,13 @@ import ( auth_model "code.gitea.io/gitea/models/auth" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" ) var testLicenseContent = ` -Copyright (c) 2024 Gitea +Copyright (c) 2024 Gitea Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -48,7 +49,8 @@ func TestAPIRepoLicense(t *testing.T) { "content": testLicenseContent, "commit_choice": "direct", }) - session.MakeRequest(t, req, http.StatusSeeOther) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.NotEmpty(t, test.RedirectURL(resp)) // let gitea update repo license time.Sleep(time.Second) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 672c2a2c8b..a2c3a467c6 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -586,7 +586,7 @@ func TestAPIRepoTransfer(t *testing.T) { // cleanup repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - _ = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID) + _ = repo_service.DeleteRepositoryDirectly(db.DefaultContext, repo.ID) } func transfer(t *testing.T) *repo_model.Repository { diff --git a/tests/integration/api_repo_variables_test.go b/tests/integration/api_repo_variables_test.go index 7847962b07..b5c88af279 100644 --- a/tests/integration/api_repo_variables_test.go +++ b/tests/integration/api_repo_variables_test.go @@ -35,11 +35,11 @@ func TestAPIRepoVariables(t *testing.T) { }, { Name: "_", - ExpectedStatus: http.StatusNoContent, + ExpectedStatus: http.StatusCreated, }, { Name: "TEST_VAR", - ExpectedStatus: http.StatusNoContent, + ExpectedStatus: http.StatusCreated, }, { Name: "test_var", @@ -81,7 +81,7 @@ func TestAPIRepoVariables(t *testing.T) { req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ Value: "initial_val", }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + MakeRequest(t, req, http.StatusCreated) cases := []struct { Name string @@ -138,7 +138,7 @@ func TestAPIRepoVariables(t *testing.T) { req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ Value: "initial_val", }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + MakeRequest(t, req, http.StatusCreated) req = NewRequest(t, "DELETE", url).AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) diff --git a/tests/integration/api_user_variables_test.go b/tests/integration/api_user_variables_test.go index 367b83e7d4..d430c9e21d 100644 --- a/tests/integration/api_user_variables_test.go +++ b/tests/integration/api_user_variables_test.go @@ -29,11 +29,11 @@ func TestAPIUserVariables(t *testing.T) { }, { Name: "_", - ExpectedStatus: http.StatusNoContent, + ExpectedStatus: http.StatusCreated, }, { Name: "TEST_VAR", - ExpectedStatus: http.StatusNoContent, + ExpectedStatus: http.StatusCreated, }, { Name: "test_var", @@ -75,7 +75,7 @@ func TestAPIUserVariables(t *testing.T) { req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ Value: "initial_val", }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + MakeRequest(t, req, http.StatusCreated) cases := []struct { Name string @@ -132,7 +132,7 @@ func TestAPIUserVariables(t *testing.T) { req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ Value: "initial_val", }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + MakeRequest(t, req, http.StatusCreated) req = NewRequest(t, "DELETE", url).AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go index c00e88b88b..24f0c03bed 100644 --- a/tests/integration/auth_ldap_test.go +++ b/tests/integration/auth_ldap_test.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" @@ -437,8 +438,8 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) { Name: gitLDAPUser.UserName, }) usersOrgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ - UserID: user.ID, - IncludePrivate: true, + UserID: user.ID, + IncludeVisibility: structs.VisibleTypePrivate, }) assert.NoError(t, err) allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID) diff --git a/tests/integration/cmd_keys_test.go b/tests/integration/cmd_keys_test.go index 61f11c58b0..3878302ef0 100644 --- a/tests/integration/cmd_keys_test.go +++ b/tests/integration/cmd_keys_test.go @@ -13,7 +13,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func Test_CmdKeys(t *testing.T) { @@ -36,18 +36,21 @@ func Test_CmdKeys(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out := new(bytes.Buffer) - app := cli.NewApp() - app.Writer = out - app.Commands = []*cli.Command{cmd.CmdKeys} + var stdout, stderr bytes.Buffer + app := &cli.Command{ + Writer: &stdout, + ErrWriter: &stderr, + Commands: []*cli.Command{cmd.CmdKeys}, + } cmd.CmdKeys.HideHelp = true - err := app.Run(append([]string{"prog"}, tt.args...)) + err := app.Run(t.Context(), append([]string{"prog"}, tt.args...)) if tt.wantErr { assert.Error(t, err) + assert.Equal(t, tt.expectedOutput, stderr.String()) } else { assert.NoError(t, err) + assert.Equal(t, tt.expectedOutput, stdout.String()) } - assert.Equal(t, tt.expectedOutput, out.String()) }) } }) diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index a5936d86de..ac47ed0094 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -7,6 +7,7 @@ import ( "bytes" "fmt" "io" + "maps" "mime/multipart" "net/http" "net/http/httptest" @@ -19,289 +20,278 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestCreateFile(t *testing.T) { +func TestEditor(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { - session := loginUser(t, "user2") - testCreateFile(t, session, "user2", "repo1", "master", "test.txt", "Content") + sessionUser2 := loginUser(t, "user2") + t.Run("EditFileNotAllowed", testEditFileNotAllowed) + t.Run("DiffPreview", testEditorDiffPreview) + t.Run("CreateFile", testEditorCreateFile) + t.Run("EditFile", func(t *testing.T) { + testEditFile(t, sessionUser2, "user2", "repo1", "master", "README.md", "Hello, World (direct)\n") + testEditFileToNewBranch(t, sessionUser2, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (commit-to-new-branch)\n") + }) + t.Run("PatchFile", testEditorPatchFile) + t.Run("DeleteFile", func(t *testing.T) { + viewLink := "/user2/repo1/src/branch/branch2/README.md" + sessionUser2.MakeRequest(t, NewRequest(t, "GET", viewLink), http.StatusOK) + testEditorActionPostRequest(t, sessionUser2, "/user2/repo1/_delete/branch2/README.md", map[string]string{"commit_choice": "direct"}) + sessionUser2.MakeRequest(t, NewRequest(t, "GET", viewLink), http.StatusNotFound) + }) + t.Run("ForkToEditFile", func(t *testing.T) { + testForkToEditFile(t, loginUser(t, "user4"), "user4", "user2", "repo1", "master", "README.md") + }) + t.Run("WebGitCommitEmail", testEditorWebGitCommitEmail) + t.Run("ProtectedBranch", testEditorProtectedBranch) }) } -func testCreateFile(t *testing.T, session *TestSession, user, repo, branch, filePath, content string) *httptest.ResponseRecorder { - // Request editor page - newURL := fmt.Sprintf("/%s/%s/_new/%s/", user, repo, branch) - req := NewRequest(t, "GET", newURL) - resp := session.MakeRequest(t, req, http.StatusOK) - - doc := NewHTMLParser(t, resp.Body) - lastCommit := doc.GetInputValueByName("last_commit") - assert.NotEmpty(t, lastCommit) +func testEditorCreateFile(t *testing.T) { + session := loginUser(t, "user2") + testCreateFile(t, session, "user2", "repo1", "master", "test.txt", "Content") + testEditorActionPostRequestError(t, session, "/user2/repo1/_new/master/", map[string]string{ + "tree_path": "test.txt", + "commit_choice": "direct", + "new_branch_name": "master", + }, `A file named "test.txt" already exists in this repository.`) + testEditorActionPostRequestError(t, session, "/user2/repo1/_new/master/", map[string]string{ + "tree_path": "test.txt", + "commit_choice": "commit-to-new-branch", + "new_branch_name": "master", + }, `Branch "master" already exists in this repository.`) +} - // Save new file to master branch - req = NewRequestWithValues(t, "POST", newURL, map[string]string{ - "_csrf": doc.GetCSRF(), - "last_commit": lastCommit, +func testCreateFile(t *testing.T, session *TestSession, user, repo, branch, filePath, content string) { + testEditorActionEdit(t, session, user, repo, "_new", branch, "", map[string]string{ "tree_path": filePath, "content": content, "commit_choice": "direct", }) - return session.MakeRequest(t, req, http.StatusSeeOther) } -func TestCreateFileOnProtectedBranch(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - session := loginUser(t, "user2") - - csrf := GetUserCSRFToken(t, session) - // Change master branch to protected - req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{ - "_csrf": csrf, - "rule_name": "master", - "enable_push": "true", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - // Check if master branch has been locked successfully - flashMsg := session.GetCookieFlashMessage() - assert.Equal(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg) - - // Request editor page - req = NewRequest(t, "GET", "/user2/repo1/_new/master/") - resp := session.MakeRequest(t, req, http.StatusOK) - - doc := NewHTMLParser(t, resp.Body) - lastCommit := doc.GetInputValueByName("last_commit") - assert.NotEmpty(t, lastCommit) - - // Save new file to master branch - req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ - "_csrf": doc.GetCSRF(), - "last_commit": lastCommit, - "tree_path": "test.txt", - "content": "Content", - "commit_choice": "direct", - }) - - resp = session.MakeRequest(t, req, http.StatusOK) - // Check body for error message - assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch "master".") - - // remove the protected branch - csrf = GetUserCSRFToken(t, session) - - // Change master branch to protected - req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/1/delete", map[string]string{ - "_csrf": csrf, - }) - - resp = session.MakeRequest(t, req, http.StatusOK) - - res := make(map[string]string) - assert.NoError(t, json.NewDecoder(resp.Body).Decode(&res)) - assert.Equal(t, "/user2/repo1/settings/branches", res["redirect"]) - - // Check if master branch has been locked successfully - flashMsg = session.GetCookieFlashMessage() - assert.Equal(t, `Removing branch protection rule "1" failed.`, flashMsg.ErrorMsg) +func testEditorProtectedBranch(t *testing.T) { + session := loginUser(t, "user2") + // Change the "master" branch to "protected" + req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "rule_name": "master", + "enable_push": "true", }) + session.MakeRequest(t, req, http.StatusSeeOther) + flashMsg := session.GetCookieFlashMessage() + assert.Equal(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg) + + // Try to commit a file to the "master" branch and it should fail + resp := testEditorActionPostRequest(t, session, "/user2/repo1/_new/master/", map[string]string{"tree_path": "test-protected-branch.txt", "commit_choice": "direct"}) + assert.Equal(t, http.StatusBadRequest, resp.Code) + assert.Equal(t, `Cannot commit to protected branch "master".`, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) } -func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) *httptest.ResponseRecorder { - // Get to the 'edit this file' page - req := NewRequest(t, "GET", path.Join(user, repo, "_edit", branch, filePath)) +func testEditorActionPostRequest(t *testing.T, session *TestSession, requestPath string, params map[string]string) *httptest.ResponseRecorder { + req := NewRequest(t, "GET", requestPath) resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - lastCommit := htmlDoc.GetInputValueByName("last_commit") - assert.NotEmpty(t, lastCommit) - - // Submit the edits - req = NewRequestWithValues(t, "POST", path.Join(user, repo, "_edit", branch, filePath), - map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - "last_commit": lastCommit, - "tree_path": filePath, - "content": newContent, - "commit_choice": "direct", - }, - ) - session.MakeRequest(t, req, http.StatusSeeOther) + form := map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "last_commit": htmlDoc.GetInputValueByName("last_commit"), + } + maps.Copy(form, params) + req = NewRequestWithValues(t, "POST", requestPath, form) + return session.MakeRequest(t, req, NoExpectedStatus) +} - // Verify the change - req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", branch, filePath)) - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Equal(t, newContent, resp.Body.String()) +func testEditorActionPostRequestError(t *testing.T, session *TestSession, requestPath string, params map[string]string, errorMessage string) { + resp := testEditorActionPostRequest(t, session, requestPath, params) + assert.Equal(t, http.StatusBadRequest, resp.Code) + assert.Equal(t, errorMessage, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) +} +func testEditorActionEdit(t *testing.T, session *TestSession, user, repo, editorAction, branch, filePath string, params map[string]string) *httptest.ResponseRecorder { + params["tree_path"] = util.IfZero(params["tree_path"], filePath) + newBranchName := util.Iif(params["commit_choice"] == "direct", branch, params["new_branch_name"]) + resp := testEditorActionPostRequest(t, session, fmt.Sprintf("/%s/%s/%s/%s/%s", user, repo, editorAction, branch, filePath), params) + assert.Equal(t, http.StatusOK, resp.Code) + assert.NotEmpty(t, test.RedirectURL(resp)) + req := NewRequest(t, "GET", path.Join(user, repo, "raw/branch", newBranchName, params["tree_path"])) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, params["content"], resp.Body.String()) return resp } -func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, branch, targetBranch, filePath, newContent string) *httptest.ResponseRecorder { - // Get to the 'edit this file' page - req := NewRequest(t, "GET", path.Join(user, repo, "_edit", branch, filePath)) - resp := session.MakeRequest(t, req, http.StatusOK) - - htmlDoc := NewHTMLParser(t, resp.Body) - lastCommit := htmlDoc.GetInputValueByName("last_commit") - assert.NotEmpty(t, lastCommit) - - // Submit the edits - req = NewRequestWithValues(t, "POST", path.Join(user, repo, "_edit", branch, filePath), - map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - "last_commit": lastCommit, - "tree_path": filePath, - "content": newContent, - "commit_choice": "commit-to-new-branch", - "new_branch_name": targetBranch, - }, - ) - session.MakeRequest(t, req, http.StatusSeeOther) - - // Verify the change - req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", targetBranch, filePath)) - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Equal(t, newContent, resp.Body.String()) +func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) { + testEditorActionEdit(t, session, user, repo, "_edit", branch, filePath, map[string]string{ + "content": newContent, + "commit_choice": "direct", + }) +} - return resp +func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, branch, targetBranch, filePath, newContent string) { + testEditorActionEdit(t, session, user, repo, "_edit", branch, filePath, map[string]string{ + "content": newContent, + "commit_choice": "commit-to-new-branch", + "new_branch_name": targetBranch, + }) } -func TestEditFile(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - session := loginUser(t, "user2") - testEditFile(t, session, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n") +func testEditorDiffPreview(t *testing.T) { + session := loginUser(t, "user2") + req := NewRequestWithValues(t, "POST", "/user2/repo1/_preview/master/README.md", map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "content": "Hello, World (Edited)\n", }) + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), `<span class="added-code">Hello, World (Edited)</span>`) } -func TestEditFileToNewBranch(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - session := loginUser(t, "user2") - testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") +func testEditorPatchFile(t *testing.T) { + session := loginUser(t, "user2") + pathContentCommon := `diff --git a/patch-file-1.txt b/patch-file-1.txt +new file mode 100644 +index 0000000000..aaaaaaaaaa +--- /dev/null ++++ b/patch-file-1.txt +@@ -0,0 +1 @@ ++` + testEditorActionPostRequest(t, session, "/user2/repo1/_diffpatch/master/", map[string]string{ + "content": pathContentCommon + "patched content\n", + "commit_choice": "commit-to-new-branch", + "new_branch_name": "patched-branch", + }) + resp := MakeRequest(t, NewRequest(t, "GET", "/user2/repo1/raw/branch/patched-branch/patch-file-1.txt"), http.StatusOK) + assert.Equal(t, "patched content\n", resp.Body.String()) + + // patch again, it should fail + resp = testEditorActionPostRequest(t, session, "/user2/repo1/_diffpatch/patched-branch/", map[string]string{ + "content": pathContentCommon + "another patched content\n", + "commit_choice": "commit-to-new-branch", + "new_branch_name": "patched-branch-1", }) + assert.Equal(t, "Unable to apply patch", test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) } -func TestWebGitCommitEmail(t *testing.T) { - onGiteaRun(t, func(t *testing.T, _ *url.URL) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - require.True(t, user.KeepEmailPrivate) - - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - gitRepo, _ := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) - defer gitRepo.Close() - getLastCommit := func(t *testing.T) *git.Commit { - c, err := gitRepo.GetBranchCommit("master") - require.NoError(t, err) - return c +func testEditorWebGitCommitEmail(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + require.True(t, user.KeepEmailPrivate) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + gitRepo, _ := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) + defer gitRepo.Close() + getLastCommit := func(t *testing.T) *git.Commit { + c, err := gitRepo.GetBranchCommit("master") + require.NoError(t, err) + return c + } + + session := loginUser(t, user.Name) + + makeReq := func(t *testing.T, link string, params map[string]string, expectedUserName, expectedEmail string) *httptest.ResponseRecorder { + lastCommit := getLastCommit(t) + params["_csrf"] = GetUserCSRFToken(t, session) + params["last_commit"] = lastCommit.ID.String() + params["commit_choice"] = "direct" + req := NewRequestWithValues(t, "POST", link, params) + resp := session.MakeRequest(t, req, NoExpectedStatus) + newCommit := getLastCommit(t) + if expectedUserName == "" { + require.Equal(t, lastCommit.ID.String(), newCommit.ID.String()) + respErr := test.ParseJSONError(resp.Body.Bytes()) + assert.Equal(t, translation.NewLocale("en-US").TrString("repo.editor.invalid_commit_email"), respErr.ErrorMessage) + } else { + require.NotEqual(t, lastCommit.ID.String(), newCommit.ID.String()) + assert.Equal(t, expectedUserName, newCommit.Author.Name) + assert.Equal(t, expectedEmail, newCommit.Author.Email) + assert.Equal(t, expectedUserName, newCommit.Committer.Name) + assert.Equal(t, expectedEmail, newCommit.Committer.Email) } + return resp + } + + uploadFile := func(t *testing.T, name, content string) string { + body := &bytes.Buffer{} + uploadForm := multipart.NewWriter(body) + file, _ := uploadForm.CreateFormFile("file", name) + _, _ = io.Copy(file, strings.NewReader(content)) + _ = uploadForm.WriteField("_csrf", GetUserCSRFToken(t, session)) + _ = uploadForm.Close() + + req := NewRequestWithBody(t, "POST", "/user2/repo1/upload-file", body) + req.Header.Add("Content-Type", uploadForm.FormDataContentType()) + resp := session.MakeRequest(t, req, http.StatusOK) - session := loginUser(t, user.Name) - - makeReq := func(t *testing.T, link string, params map[string]string, expectedUserName, expectedEmail string) *httptest.ResponseRecorder { - lastCommit := getLastCommit(t) - params["_csrf"] = GetUserCSRFToken(t, session) - params["last_commit"] = lastCommit.ID.String() - params["commit_choice"] = "direct" - req := NewRequestWithValues(t, "POST", link, params) - resp := session.MakeRequest(t, req, NoExpectedStatus) - newCommit := getLastCommit(t) - if expectedUserName == "" { - require.Equal(t, lastCommit.ID.String(), newCommit.ID.String()) - htmlDoc := NewHTMLParser(t, resp.Body) - errMsg := htmlDoc.doc.Find(".ui.negative.message").Text() - assert.Contains(t, errMsg, translation.NewLocale("en-US").Tr("repo.editor.invalid_commit_email")) - } else { - require.NotEqual(t, lastCommit.ID.String(), newCommit.ID.String()) - assert.Equal(t, expectedUserName, newCommit.Author.Name) - assert.Equal(t, expectedEmail, newCommit.Author.Email) - assert.Equal(t, expectedUserName, newCommit.Committer.Name) - assert.Equal(t, expectedEmail, newCommit.Committer.Email) - } - return resp - } + respMap := map[string]string{} + DecodeJSON(t, resp, &respMap) + return respMap["uuid"] + } + + t.Run("EmailInactive", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 35, UID: user.ID}) + require.False(t, email.IsActivated) + makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{ + "tree_path": "README.md", + "content": "test content", + "commit_email": email.Email, + }, "", "") + }) - uploadFile := func(t *testing.T, name, content string) string { - body := &bytes.Buffer{} - uploadForm := multipart.NewWriter(body) - file, _ := uploadForm.CreateFormFile("file", name) - _, _ = io.Copy(file, strings.NewReader(content)) - _ = uploadForm.WriteField("_csrf", GetUserCSRFToken(t, session)) - _ = uploadForm.Close() - - req := NewRequestWithBody(t, "POST", "/user2/repo1/upload-file", body) - req.Header.Add("Content-Type", uploadForm.FormDataContentType()) - resp := session.MakeRequest(t, req, http.StatusOK) - - respMap := map[string]string{} - DecodeJSON(t, resp, &respMap) - return respMap["uuid"] - } + t.Run("EmailInvalid", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 1, IsActivated: true}) + require.NotEqual(t, email.UID, user.ID) + makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{ + "tree_path": "README.md", + "content": "test content", + "commit_email": email.Email, + }, "", "") + }) - t.Run("EmailInactive", func(t *testing.T) { + testWebGit := func(t *testing.T, linkForKeepPrivate string, paramsForKeepPrivate map[string]string, linkForChosenEmail string, paramsForChosenEmail map[string]string) (resp1, resp2 *httptest.ResponseRecorder) { + t.Run("DefaultEmailKeepPrivate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 35, UID: user.ID}) - require.False(t, email.IsActivated) - makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{ - "tree_path": "README.md", - "content": "test content", - "commit_email": email.Email, - }, "", "") + paramsForKeepPrivate["commit_email"] = "" + resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "user2@noreply.example.org") }) - - t.Run("EmailInvalid", func(t *testing.T) { + t.Run("ChooseEmail", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 1, IsActivated: true}) - require.NotEqual(t, email.UID, user.ID) - makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{ - "tree_path": "README.md", - "content": "test content", - "commit_email": email.Email, - }, "", "") - }) - - testWebGit := func(t *testing.T, linkForKeepPrivate string, paramsForKeepPrivate map[string]string, linkForChosenEmail string, paramsForChosenEmail map[string]string) (resp1, resp2 *httptest.ResponseRecorder) { - t.Run("DefaultEmailKeepPrivate", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - paramsForKeepPrivate["commit_email"] = "" - resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "user2@noreply.example.org") - }) - t.Run("ChooseEmail", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - paramsForChosenEmail["commit_email"] = "user2@example.com" - resp2 = makeReq(t, linkForChosenEmail, paramsForChosenEmail, "User Two", "user2@example.com") - }) - return resp1, resp2 - } - - t.Run("Edit", func(t *testing.T) { - testWebGit(t, - "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for keep private"}, - "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for chosen email"}, - ) + paramsForChosenEmail["commit_email"] = "user2@example.com" + resp2 = makeReq(t, linkForChosenEmail, paramsForChosenEmail, "User Two", "user2@example.com") }) + return resp1, resp2 + } + + t.Run("Edit", func(t *testing.T) { + testWebGit(t, + "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for keep private"}, + "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for chosen email"}, + ) + }) - t.Run("UploadDelete", func(t *testing.T) { - file1UUID := uploadFile(t, "file1", "File 1") - file2UUID := uploadFile(t, "file2", "File 2") - testWebGit(t, - "/user2/repo1/_upload/master", map[string]string{"files": file1UUID}, - "/user2/repo1/_upload/master", map[string]string{"files": file2UUID}, - ) - testWebGit(t, - "/user2/repo1/_delete/master/file1", map[string]string{}, - "/user2/repo1/_delete/master/file2", map[string]string{}, - ) - }) + t.Run("UploadDelete", func(t *testing.T) { + file1UUID := uploadFile(t, "file1", "File 1") + file2UUID := uploadFile(t, "file2", "File 2") + testWebGit(t, + "/user2/repo1/_upload/master", map[string]string{"files": file1UUID}, + "/user2/repo1/_upload/master", map[string]string{"files": file2UUID}, + ) + testWebGit(t, + "/user2/repo1/_delete/master/file1", map[string]string{}, + "/user2/repo1/_delete/master/file2", map[string]string{}, + ) + }) - t.Run("ApplyPatchCherryPick", func(t *testing.T) { - testWebGit(t, - "/user2/repo1/_diffpatch/master", map[string]string{ - "tree_path": "__dummy__", - "content": `diff --git a/patch-file-1.txt b/patch-file-1.txt + t.Run("ApplyPatchCherryPick", func(t *testing.T) { + testWebGit(t, + "/user2/repo1/_diffpatch/master", map[string]string{ + "tree_path": "__dummy__", + "content": `diff --git a/patch-file-1.txt b/patch-file-1.txt new file mode 100644 index 0000000000..aaaaaaaaaa --- /dev/null @@ -309,10 +299,10 @@ index 0000000000..aaaaaaaaaa @@ -0,0 +1 @@ +File 1 `, - }, - "/user2/repo1/_diffpatch/master", map[string]string{ - "tree_path": "__dummy__", - "content": `diff --git a/patch-file-2.txt b/patch-file-2.txt + }, + "/user2/repo1/_diffpatch/master", map[string]string{ + "tree_path": "__dummy__", + "content": `diff --git a/patch-file-2.txt b/patch-file-2.txt new file mode 100644 index 0000000000..bbbbbbbbbb --- /dev/null @@ -320,20 +310,146 @@ index 0000000000..bbbbbbbbbb @@ -0,0 +1 @@ +File 2 `, - }, - ) - - commit1, err := gitRepo.GetCommitByPath("patch-file-1.txt") - require.NoError(t, err) - commit2, err := gitRepo.GetCommitByPath("patch-file-2.txt") - require.NoError(t, err) - resp1, _ := testWebGit(t, - "/user2/repo1/_cherrypick/"+commit1.ID.String()+"/master", map[string]string{"revert": "true"}, - "/user2/repo1/_cherrypick/"+commit2.ID.String()+"/master", map[string]string{"revert": "true"}, - ) - - // By the way, test the "cherrypick" page: a successful revert redirects to the main branch - assert.Equal(t, "/user2/repo1/src/branch/master", resp1.Header().Get("Location")) - }) + }, + ) + + commit1, err := gitRepo.GetCommitByPath("patch-file-1.txt") + require.NoError(t, err) + commit2, err := gitRepo.GetCommitByPath("patch-file-2.txt") + require.NoError(t, err) + resp1, _ := testWebGit(t, + "/user2/repo1/_cherrypick/"+commit1.ID.String()+"/master", map[string]string{"revert": "true"}, + "/user2/repo1/_cherrypick/"+commit2.ID.String()+"/master", map[string]string{"revert": "true"}, + ) + + // By the way, test the "cherrypick" page: a successful revert redirects to the main branch + assert.Equal(t, "/user2/repo1/src/branch/master", test.RedirectURL(resp1)) }) } + +func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, branch, filePath string) { + forkToEdit := func(t *testing.T, session *TestSession, owner, repo, operation, branch, filePath string) { + // visit the base repo, see the "Add File" button + req := NewRequest(t, "GET", path.Join(owner, repo)) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, htmlDoc, ".repo-add-file", 1) + + // attempt to edit a file, see the guideline page + req = NewRequest(t, "GET", path.Join(owner, repo, operation, branch, filePath)) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "Fork Repository to Propose Changes") + + // fork the repository + req = NewRequestWithValues(t, "POST", path.Join(owner, repo, "_fork", branch), map[string]string{"_csrf": GetUserCSRFToken(t, session)}) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.JSONEq(t, `{"redirect":""}`, resp.Body.String()) + } + + t.Run("ForkButArchived", func(t *testing.T) { + // Fork repository because we can't edit it + forkToEdit(t, session, owner, repo, "_edit", branch, filePath) + + // Archive the repository + req := NewRequestWithValues(t, "POST", path.Join(user, repo, "settings"), + map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "repo_name": repo, + "action": "archive", + }, + ) + session.MakeRequest(t, req, http.StatusSeeOther) + + // Check editing archived repository is disabled + req = NewRequest(t, "GET", path.Join(owner, repo, "_edit", branch, filePath)).SetHeader("Accept", "text/html") + resp := session.MakeRequest(t, req, http.StatusNotFound) + assert.Contains(t, resp.Body.String(), "You have forked this repository but your fork is not editable.") + + // Unfork the repository + req = NewRequestWithValues(t, "POST", path.Join(user, repo, "settings"), + map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "repo_name": repo, + "action": "convert_fork", + }, + ) + session.MakeRequest(t, req, http.StatusSeeOther) + }) + + // Fork repository again, and check the existence of the forked repo with unique name + forkToEdit(t, session, owner, repo, "_edit", branch, filePath) + session.MakeRequest(t, NewRequestf(t, "GET", "/%s/%s-1", user, repo), http.StatusOK) + + t.Run("CheckBaseRepoForm", func(t *testing.T) { + // the base repo's edit form should have the correct action and upload links (pointing to the forked repo) + req := NewRequest(t, "GET", path.Join(owner, repo, "_upload", branch, filePath)+"?foo=bar") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + uploadForm := htmlDoc.doc.Find(".form-fetch-action") + formAction := uploadForm.AttrOr("action", "") + assert.Equal(t, fmt.Sprintf("/%s/%s-1/_upload/%s/%s?from_base_branch=%s&foo=bar", user, repo, branch, filePath, branch), formAction) + uploadLink := uploadForm.Find(".dropzone").AttrOr("data-link-url", "") + assert.Equal(t, fmt.Sprintf("/%s/%s-1/upload-file", user, repo), uploadLink) + newBranchName := uploadForm.Find("input[name=new_branch_name]").AttrOr("value", "") + assert.Equal(t, user+"-patch-1", newBranchName) + commitChoice := uploadForm.Find("input[name=commit_choice][checked]").AttrOr("value", "") + assert.Equal(t, "commit-to-new-branch", commitChoice) + lastCommit := uploadForm.Find("input[name=last_commit]").AttrOr("value", "") + assert.NotEmpty(t, lastCommit) + }) + + t.Run("ViewBaseEditFormAndCommitToFork", func(t *testing.T) { + req := NewRequest(t, "GET", path.Join(owner, repo, "_edit", branch, filePath)) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + editRequestForm := map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "last_commit": htmlDoc.GetInputValueByName("last_commit"), + "tree_path": filePath, + "content": "new content in fork", + "commit_choice": "commit-to-new-branch", + } + // change a file in the forked repo with existing branch name (should fail) + editRequestForm["new_branch_name"] = "master" + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s-1/_edit/%s/%s?from_base_branch=%s", user, repo, branch, filePath, branch), editRequestForm) + resp = session.MakeRequest(t, req, http.StatusBadRequest) + respJSON := test.ParseJSONError(resp.Body.Bytes()) + assert.Equal(t, `Branch "master" already exists in your fork, please choose a new branch name.`, respJSON.ErrorMessage) + + // change a file in the forked repo (should succeed) + newBranchName := htmlDoc.GetInputValueByName("new_branch_name") + editRequestForm["new_branch_name"] = newBranchName + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s-1/_edit/%s/%s?from_base_branch=%s", user, repo, branch, filePath, branch), editRequestForm) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, fmt.Sprintf("/%s/%s/compare/%s...%s/%s-1:%s", owner, repo, branch, user, repo, newBranchName), test.RedirectURL(resp)) + + // check the file in the fork's branch is changed + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s-1/src/branch/%s/%s", user, repo, newBranchName, filePath)) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "new content in fork") + }) +} + +func testEditFileNotAllowed(t *testing.T) { + sessionUser1 := loginUser(t, "user1") // admin, all access + sessionUser4 := loginUser(t, "user4") + // "_cherrypick" has a different route pattern, so skip its test + operations := []string{"_new", "_edit", "_delete", "_upload", "_diffpatch"} + for _, operation := range operations { + t.Run(operation, func(t *testing.T) { + // Branch does not exist + targetLink := path.Join("user2", "repo1", operation, "missing", "README.md") + sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound) + + // Private repository + targetLink = path.Join("user2", "repo2", operation, "master", "Home.md") + sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusOK) + sessionUser4.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound) + + // Empty repository + targetLink = path.Join("org41", "repo61", operation, "master", "README.md") + sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound) + }) + } +} diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index 8cebfaf32a..6a8c70f12f 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -10,7 +10,6 @@ import ( "io" "mime/multipart" "net/http" - "net/http/httptest" "strings" "testing" @@ -30,7 +29,7 @@ import ( "github.com/stretchr/testify/require" ) -func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) *httptest.ResponseRecorder { +func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) { url := fmt.Sprintf("/%s/%s/_new/%s", user, repo, branch) req := NewRequestWithValues(t, "POST", url, map[string]string{ "_csrf": GetUserCSRFToken(t, session), @@ -38,7 +37,8 @@ func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, tree "tree_path": treePath, "content": content, }) - return session.MakeRequest(t, req, http.StatusSeeOther) + resp := session.MakeRequest(t, req, http.StatusOK) + assert.NotEmpty(t, test.RedirectURL(resp)) } func TestEmptyRepo(t *testing.T) { @@ -87,7 +87,7 @@ func TestEmptyRepoAddFile(t *testing.T) { "content": "newly-added-test-file", }) - resp = session.MakeRequest(t, req, http.StatusSeeOther) + resp = session.MakeRequest(t, req, http.StatusOK) redirect := test.RedirectURL(resp) assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/test-file.md", redirect) @@ -154,9 +154,9 @@ func TestEmptyRepoUploadFile(t *testing.T) { "files": respMap["uuid"], "tree_path": "", }) - resp = session.MakeRequest(t, req, http.StatusSeeOther) + resp = session.MakeRequest(t, req, http.StatusOK) redirect := test.RedirectURL(resp) - assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/", redirect) + assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch, redirect) req = NewRequest(t, "GET", redirect) resp = session.MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/ephemeral_actions_runner_deletion_test.go b/tests/integration/ephemeral_actions_runner_deletion_test.go new file mode 100644 index 0000000000..40f8c643a8 --- /dev/null +++ b/tests/integration/ephemeral_actions_runner_deletion_test.go @@ -0,0 +1,77 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "testing" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/util" + repo_service "code.gitea.io/gitea/services/repository" + user_service "code.gitea.io/gitea/services/user" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestEphemeralActionsRunnerDeletion(t *testing.T) { + t.Run("ByTaskCompletion", testEphemeralActionsRunnerDeletionByTaskCompletion) + t.Run("ByRepository", testEphemeralActionsRunnerDeletionByRepository) + t.Run("ByUser", testEphemeralActionsRunnerDeletionByUser) +} + +// Test that the ephemeral runner is deleted when the task is finished +func testEphemeralActionsRunnerDeletionByTaskCompletion(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + _, err := actions_model.GetRunnerByID(t.Context(), 34350) + assert.NoError(t, err) + + task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52}) + assert.Equal(t, actions_model.StatusRunning, task.Status) + + task.Status = actions_model.StatusSuccess + err = actions_model.UpdateTask(t.Context(), task, "status") + assert.NoError(t, err) + + _, err = actions_model.GetRunnerByID(t.Context(), 34350) + assert.ErrorIs(t, err, util.ErrNotExist) +} + +func testEphemeralActionsRunnerDeletionByRepository(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + _, err := actions_model.GetRunnerByID(t.Context(), 34350) + assert.NoError(t, err) + + task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52}) + assert.Equal(t, actions_model.StatusRunning, task.Status) + + err = repo_service.DeleteRepositoryDirectly(t.Context(), task.RepoID, true) + assert.NoError(t, err) + + _, err = actions_model.GetRunnerByID(t.Context(), 34350) + assert.ErrorIs(t, err, util.ErrNotExist) +} + +// Test that the ephemeral runner is deleted when a user is deleted +func testEphemeralActionsRunnerDeletionByUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + _, err := actions_model.GetRunnerByID(t.Context(), 34350) + assert.NoError(t, err) + + task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52}) + assert.Equal(t, actions_model.StatusRunning, task.Status) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + err = user_service.DeleteUser(t.Context(), user, true) + assert.NoError(t, err) + + _, err = actions_model.GetRunnerByID(t.Context(), 34350) + assert.ErrorIs(t, err, util.ErrNotExist) +} diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go index ed60bdb58a..3b0f9589d2 100644 --- a/tests/integration/git_general_test.go +++ b/tests/integration/git_general_test.go @@ -26,6 +26,7 @@ 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/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" @@ -713,7 +714,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { commitID := path.Base(commitURL) - addCommitStatus := func(status api.CommitStatusState) func(*testing.T) { + addCommitStatus := func(status commitstatus.CommitStatusState) func(*testing.T) { return doAPICreateCommitStatus(ctx, commitID, api.CreateStatusOption{ State: status, TargetURL: "http://test.ci/", @@ -723,7 +724,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { } // Call API to add Pending status for commit - t.Run("CreateStatus", addCommitStatus(api.CommitStatusPending)) + t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusPending)) // Cancel not existing auto merge ctx.ExpectedCode = http.StatusNotFound @@ -752,7 +753,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { assert.False(t, pr.HasMerged) // Call API to add Failure status for commit - t.Run("CreateStatus", addCommitStatus(api.CommitStatusFailure)) + t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusFailure)) // Check pr status pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) @@ -760,7 +761,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { assert.False(t, pr.HasMerged) // Call API to add Success status for commit - t.Run("CreateStatus", addCommitStatus(api.CommitStatusSuccess)) + t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusSuccess)) // wait to let gitea merge stuff time.Sleep(time.Second) diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index bac7b4f48b..d716847b54 100644 --- a/tests/integration/git_push_test.go +++ b/tests/integration/git_push_test.go @@ -27,7 +27,7 @@ func TestGitPush(t *testing.T) { func testGitPush(t *testing.T, u *url.URL) { t.Run("Push branches at once", func(t *testing.T) { runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) { - for i := 0; i < 100; i++ { + for i := range 100 { branchName := fmt.Sprintf("branch-%d", i) pushed = append(pushed, branchName) doGitCreateBranch(gitPath, branchName)(t) @@ -40,7 +40,7 @@ func testGitPush(t *testing.T, u *url.URL) { t.Run("Push branches exists", func(t *testing.T) { runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) { - for i := 0; i < 10; i++ { + for i := range 10 { branchName := fmt.Sprintf("branch-%d", i) if i < 5 { pushed = append(pushed, branchName) @@ -54,7 +54,7 @@ func testGitPush(t *testing.T, u *url.URL) { pushed = pushed[:0] // do some changes for the first 5 branches created above - for i := 0; i < 5; i++ { + for i := range 5 { branchName := fmt.Sprintf("branch-%d", i) pushed = append(pushed, branchName) @@ -75,7 +75,7 @@ func testGitPush(t *testing.T, u *url.URL) { t.Run("Push branches one by one", func(t *testing.T) { runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) { - for i := 0; i < 100; i++ { + for i := range 100 { branchName := fmt.Sprintf("branch-%d", i) doGitCreateBranch(gitPath, branchName)(t) doGitPushTestRepository(gitPath, "origin", branchName)(t) @@ -101,14 +101,14 @@ func testGitPush(t *testing.T, u *url.URL) { doGitPushTestRepository(gitPath, "origin", "master")(t) // make sure master is the default branch instead of a branch we are going to delete pushed = append(pushed, "master") - for i := 0; i < 100; i++ { + for i := range 100 { branchName := fmt.Sprintf("branch-%d", i) pushed = append(pushed, branchName) doGitCreateBranch(gitPath, branchName)(t) } doGitPushTestRepository(gitPath, "origin", "--all")(t) - for i := 0; i < 10; i++ { + for i := range 10 { branchName := fmt.Sprintf("branch-%d", i) doGitPushTestRepository(gitPath, "origin", "--delete", branchName)(t) deleted = append(deleted, branchName) @@ -191,7 +191,7 @@ func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gi assert.Equal(t, commitID, branch.CommitID) } - require.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID)) + require.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, repo.ID)) } func TestPushPullRefs(t *testing.T) { diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_ssh_git_test.go index 32de200f63..56f9f87783 100644 --- a/tests/integration/gpg_git_test.go +++ b/tests/integration/gpg_ssh_git_test.go @@ -4,7 +4,10 @@ package integration import ( + "crypto/ed25519" + "crypto/rand" "encoding/base64" + "encoding/pem" "fmt" "net/url" "os" @@ -23,6 +26,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" ) func TestGPGGit(t *testing.T) { @@ -42,6 +46,37 @@ func TestGPGGit(t *testing.T) { defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})() defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})() + testGitSigning(t) +} + +func TestSSHGit(t *testing.T) { + tmpDir := t.TempDir() // use a temp dir to store the SSH keys + err := os.Chmod(tmpDir, 0o700) + assert.NoError(t, err) + + pub, priv, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err, "ed25519.GenerateKey") + sshPubKey, err := ssh.NewPublicKey(pub) + require.NoError(t, err, "ssh.NewPublicKey") + + err = os.WriteFile(tmpDir+"/id_ed25519.pub", ssh.MarshalAuthorizedKey(sshPubKey), 0o600) + require.NoError(t, err, "os.WriteFile id_ed25519.pub") + block, err := ssh.MarshalPrivateKey(priv, "") + require.NoError(t, err, "ssh.MarshalPrivateKey") + err = os.WriteFile(tmpDir+"/id_ed25519", pem.EncodeToMemory(block), 0o600) + require.NoError(t, err, "os.WriteFile id_ed25519") + + defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, tmpDir+"/id_ed25519.pub")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningFormat, "ssh")() + defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})() + defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})() + + testGitSigning(t) +} + +func testGitSigning(t *testing.T) { username := "user2" user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username}) baseAPITestContext := NewAPITestContext(t, username, "repo1") diff --git a/tests/integration/html_helper.go b/tests/integration/html_helper.go index 874fc32228..4d589b32e7 100644 --- a/tests/integration/html_helper.go +++ b/tests/integration/html_helper.go @@ -42,7 +42,7 @@ func (doc *HTMLDoc) GetCSRF() string { return doc.GetInputValueByName("_csrf") } -// AssertHTMLElement check if element by selector exists or does not exist depending on checkExists +// AssertHTMLElement check if the element by selector exists or does not exist depending on checkExists func AssertHTMLElement[T int | bool](t testing.TB, doc *HTMLDoc, selector string, checkExists T) { sel := doc.doc.Find(selector) switch v := any(checkExists).(type) { diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index d5b7bb7a3e..21126b63c5 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -1,7 +1,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//nolint:forbidigo +//nolint:forbidigo // use of print functions is allowed in tests package integration import ( diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index b403b3cf17..b5dca58357 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -76,14 +76,11 @@ func TestViewIssuesSortByType(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) issuesSelection := getIssuesSelection(t, htmlDoc) - expectedNumIssues := unittest.GetCount(t, + expectedNumIssues := min(unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID, PosterID: user.ID}, unittest.Cond("is_closed=?", false), unittest.Cond("is_pull=?", false), - ) - if expectedNumIssues > setting.UI.IssuePagingNum { - expectedNumIssues = setting.UI.IssuePagingNum - } + ), setting.UI.IssuePagingNum) assert.Equal(t, expectedNumIssues, issuesSelection.Length()) issuesSelection.Each(func(_ int, selection *goquery.Selection) { @@ -151,6 +148,22 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content return issueURL } +func testIssueDelete(t *testing.T, session *TestSession, issueURL string) { + req := NewRequestWithValues(t, "POST", path.Join(issueURL, "delete"), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + +func testIssueAssign(t *testing.T, session *TestSession, repoLink string, issueID, assigneeID int64) { + req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/assignee?issue_ids=%d", issueID), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "id": strconv.FormatInt(assigneeID, 10), + "action": "", // empty action means assign + }) + session.MakeRequest(t, req, http.StatusOK) +} + func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 { req := NewRequest(t, "GET", issueURL) resp := session.MakeRequest(t, req, http.StatusOK) @@ -482,10 +495,7 @@ func TestSearchIssues(t *testing.T) { session := loginUser(t, "user2") - expectedIssueCount := 20 // from the fixtures - if expectedIssueCount > setting.UI.IssuePagingNum { - expectedIssueCount = setting.UI.IssuePagingNum - } + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures link, _ := url.Parse("/issues/search") req := NewRequest(t, "GET", link.String()) @@ -576,10 +586,7 @@ func TestSearchIssues(t *testing.T) { func TestSearchIssuesWithLabels(t *testing.T) { defer tests.PrepareTestEnv(t)() - expectedIssueCount := 20 // from the fixtures - if expectedIssueCount > setting.UI.IssuePagingNum { - expectedIssueCount = setting.UI.IssuePagingNum - } + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures session := loginUser(t, "user1") link, _ := url.Parse("/issues/search") diff --git a/tests/integration/lfs_view_test.go b/tests/integration/lfs_view_test.go index 64ffebaa78..c26ece22be 100644 --- a/tests/integration/lfs_view_test.go +++ b/tests/integration/lfs_view_test.go @@ -68,14 +68,15 @@ func TestLFSRender(t *testing.T) { req := NewRequest(t, "GET", "/user2/lfs/src/branch/master/crypt.bin") resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body).doc + doc := NewHTMLParser(t, resp.Body) fileInfo := doc.Find("div.file-info-entry").First().Text() assert.Contains(t, fileInfo, "LFS") - rawLink, exists := doc.Find("div.file-view > div.view-raw > a").Attr("href") - assert.True(t, exists, "Download link should render instead of content because this is a binary file") - assert.Equal(t, "/user2/lfs/media/branch/master/crypt.bin", rawLink, "The download link should use the proper /media link because it's in LFS") + // find new file view container + fileViewContainer := doc.Find("[data-global-init=initRepoFileView]") + assert.Equal(t, "/user2/lfs/media/branch/master/crypt.bin", fileViewContainer.AttrOr("data-raw-file-link", "")) + AssertHTMLElement(t, doc, ".view-raw > .file-view-render-container > .file-view-raw-prompt", 1) }) // check that a directory with a README file shows its text diff --git a/tests/integration/org_count_test.go b/tests/integration/org_count_test.go index fb71e690c2..c48008e627 100644 --- a/tests/integration/org_count_test.go +++ b/tests/integration/org_count_test.go @@ -120,8 +120,8 @@ func doCheckOrgCounts(username string, orgCounts map[string]int, strict bool, ca }) orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ - UserID: user.ID, - IncludePrivate: true, + UserID: user.ID, + IncludeVisibility: api.VisibleTypePrivate, }) assert.NoError(t, err) diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index 9a93455858..3ed7baa5ba 100644 --- a/tests/integration/org_test.go +++ b/tests/integration/org_test.go @@ -10,12 +10,17 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestOrgRepos(t *testing.T) { @@ -40,7 +45,7 @@ func TestOrgRepos(t *testing.T) { sel := htmlDoc.doc.Find("a.name") assert.Len(t, repos, len(sel.Nodes)) - for i := 0; i < len(repos); i++ { + for i := range repos { assert.Equal(t, repos[i], strings.TrimSpace(sel.Eq(i).Text())) } } @@ -217,4 +222,32 @@ func TestTeamSearch(t *testing.T) { session = loginUser(t, user5.Name) req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team") session.MakeRequest(t, req, http.StatusNotFound) + + t.Run("SearchWithPermission", func(t *testing.T) { + ctx := t.Context() + const testOrgID int64 = 500 + const testRepoID int64 = 2000 + testTeam := &organization.Team{OrgID: testOrgID, LowerName: "test_team", AccessMode: perm.AccessModeNone} + require.NoError(t, db.Insert(ctx, testTeam)) + require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: testOrgID, TeamID: testTeam.ID, RepoID: testRepoID})) + require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: testOrgID, TeamID: testTeam.ID, Type: unit.TypeCode, AccessMode: perm.AccessModeRead})) + require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: testOrgID, TeamID: testTeam.ID, Type: unit.TypeIssues, AccessMode: perm.AccessModeWrite})) + + teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, testOrgID, testRepoID, perm.AccessModeRead, unit.TypeCode, unit.TypeIssues) + require.NoError(t, err) + assert.Len(t, teams, 1) // can read "code" or "issues" + + teams, err = organization.GetTeamsWithAccessToAnyRepoUnit(ctx, testOrgID, testRepoID, perm.AccessModeWrite, unit.TypeCode) + require.NoError(t, err) + assert.Empty(t, teams) // cannot write "code" + + teams, err = organization.GetTeamsWithAccessToAnyRepoUnit(ctx, testOrgID, testRepoID, perm.AccessModeWrite, unit.TypeIssues) + require.NoError(t, err) + assert.Len(t, teams, 1) // can write "issues" + + _, _ = db.GetEngine(ctx).ID(testTeam.ID).Update(&organization.Team{AccessMode: perm.AccessModeWrite}) + teams, err = organization.GetTeamsWithAccessToAnyRepoUnit(ctx, testOrgID, testRepoID, perm.AccessModeWrite, unit.TypeCode) + require.NoError(t, err) + assert.Len(t, teams, 1) // team permission is "write", so can write "code" + }) } diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index 13213c254d..43a489d4c4 100644 --- a/tests/integration/project_test.go +++ b/tests/integration/project_test.go @@ -47,7 +47,7 @@ func TestMoveRepoProjectColumns(t *testing.T) { err := project_model.NewProject(db.DefaultContext, &project1) assert.NoError(t, err) - for i := 0; i < 3; i++ { + for i := range 3 { err = project_model.NewColumn(db.DefaultContext, &project_model.Column{ Title: fmt.Sprintf("column %d", i+1), ProjectID: project1.ID, diff --git a/tests/integration/pull_compare_test.go b/tests/integration/pull_compare_test.go index 86bdd1b9e3..f95a2f1690 100644 --- a/tests/integration/pull_compare_test.go +++ b/tests/integration/pull_compare_test.go @@ -13,7 +13,6 @@ import ( issues_model "code.gitea.io/gitea/models/issues" 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/test" repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/tests" @@ -76,10 +75,9 @@ func TestPullCompare(t *testing.T) { assert.Positive(t, editButtonCount, "Expected to find a button to edit a file in the PR diff view but there were none") repoForked := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // delete the head repository and revisit the PR diff view - err := repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, repoForked.ID) + err := repo_service.DeleteRepositoryDirectly(db.DefaultContext, repoForked.ID) assert.NoError(t, err) req = NewRequest(t, "GET", prFilesURL) @@ -161,7 +159,8 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) { "commit_summary": "user2 updated the file", "commit_choice": "direct", }) - user2Session.MakeRequest(t, req, http.StatusSeeOther) + resp = user2Session.MakeRequest(t, req, http.StatusOK) + assert.NotEmpty(t, test.RedirectURL(resp)) } } }) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index cf50d5e639..73b4c22070 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/queue" @@ -768,7 +769,7 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { }() err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "https://gitea.com", Context: "gitea/actions", }) @@ -848,7 +849,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) { }() err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "https://gitea.com", Context: "gitea/actions", }) @@ -977,14 +978,12 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing. }() err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "https://gitea.com", Context: "gitea/actions", }) assert.NoError(t, err) - time.Sleep(2 * time.Second) - // reload pr again pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) assert.False(t, pr.HasMerged) @@ -997,8 +996,6 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing. htmlDoc := NewHTMLParser(t, resp.Body) testSubmitReview(t, approveSession, htmlDoc.GetCSRF(), "user2", "repo1", strconv.Itoa(int(pr.Index)), sha, "approve", http.StatusOK) - time.Sleep(2 * time.Second) - // realod pr again pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) assert.True(t, pr.HasMerged) diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index 4d43847f1b..49326a594a 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -55,20 +56,20 @@ func TestPullCreate_CommitStatus(t *testing.T) { commitID := path.Base(commitURL) - statusList := []api.CommitStatusState{ - api.CommitStatusPending, - api.CommitStatusError, - api.CommitStatusFailure, - api.CommitStatusSuccess, - api.CommitStatusWarning, + statusList := []commitstatus.CommitStatusState{ + commitstatus.CommitStatusPending, + commitstatus.CommitStatusError, + commitstatus.CommitStatusFailure, + commitstatus.CommitStatusSuccess, + commitstatus.CommitStatusWarning, } - statesIcons := map[api.CommitStatusState]string{ - api.CommitStatusPending: "octicon-dot-fill", - api.CommitStatusSuccess: "octicon-check", - api.CommitStatusError: "gitea-exclamation", - api.CommitStatusFailure: "octicon-x", - api.CommitStatusWarning: "gitea-exclamation", + statesIcons := map[commitstatus.CommitStatusState]string{ + commitstatus.CommitStatusPending: "octicon-dot-fill", + commitstatus.CommitStatusSuccess: "octicon-check", + commitstatus.CommitStatusError: "gitea-exclamation", + commitstatus.CommitStatusFailure: "octicon-x", + commitstatus.CommitStatusWarning: "gitea-exclamation", } testCtx := NewAPITestContext(t, "user1", "repo1", auth_model.AccessTokenScopeWriteRepository) @@ -99,7 +100,7 @@ func TestPullCreate_CommitStatus(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"}) css := unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatusSummary{RepoID: repo1.ID, SHA: commitID}) - assert.Equal(t, api.CommitStatusWarning, css.State) + assert.Equal(t, commitstatus.CommitStatusSuccess, css.State) }) } @@ -128,7 +129,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) { session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") - testEditFileToNewBranch(t, session, "user1", "repo1", "status1", "status1", "README.md", "# repo1\n\nDescription for repo1") + testEditFile(t, session, "user1", "repo1", "status1", "README.md", "# repo1\n\nDescription for repo1") url := path.Join("user1", "repo1", "compare", "master...status1") req := NewRequestWithValues(t, "POST", url, diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index 125f0810a7..88a58787af 100644 --- a/tests/integration/release_test.go +++ b/tests/integration/release_test.go @@ -7,7 +7,6 @@ import ( "fmt" "net/http" "testing" - "time" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -68,9 +67,6 @@ func TestViewReleases(t *testing.T) { session := loginUser(t, "user2") req := NewRequest(t, "GET", "/user2/repo1/releases") session.MakeRequest(t, req, http.StatusOK) - - // if CI is to slow this test fail, so lets wait a bit - time.Sleep(time.Millisecond * 100) } func TestViewReleasesNoLogin(t *testing.T) { @@ -118,7 +114,7 @@ func TestCreateReleasePaging(t *testing.T) { session := loginUser(t, "user2") // Create enough releases to have paging - for i := 0; i < 12; i++ { + for i := range 12 { version := fmt.Sprintf("v0.0.%d", i) createNewRelease(t, session, "/user2/repo1", version, version, false, false) } diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index 504d2adacc..0097a7f62e 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -12,6 +12,7 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -76,7 +77,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { // Call API to add status for commit ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ - State: api.CommitStatusState(state), + State: commitstatus.CommitStatusState(state), TargetURL: "http://test.ci/", Description: "", Context: "testci", @@ -120,7 +121,7 @@ func testRepoCommitsWithStatus(t *testing.T, resp, respOne *httptest.ResponseRec assert.NotNil(t, status) if assert.Len(t, statuses, 1) { - assert.Equal(t, api.CommitStatusState(state), statuses[0].State) + assert.Equal(t, commitstatus.CommitStatusState(state), statuses[0].State) assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/statuses/65f1bf27bc3bf70f64657658635e66094edbcb4d", statuses[0].URL) assert.Equal(t, "http://test.ci/", statuses[0].TargetURL) assert.Empty(t, statuses[0].Description) @@ -168,13 +169,13 @@ func TestRepoCommitsStatusParallel(t *testing.T) { assert.NotEmpty(t, commitURL) var wg sync.WaitGroup - for i := 0; i < 10; i++ { + for i := range 10 { wg.Add(1) go func(parentT *testing.T, i int) { parentT.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) { ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) runBody := doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ - State: api.CommitStatusPending, + State: commitstatus.CommitStatusPending, TargetURL: "http://test.ci/", Description: "", Context: "testci", @@ -205,14 +206,14 @@ func TestRepoCommitsStatusMultiple(t *testing.T) { // Call API to add status for commit ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "http://test.ci/", Description: "", Context: "testci", })) t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "http://test.ci/", Description: "", Context: "other_context", diff --git a/tests/integration/repo_fork_test.go b/tests/integration/repo_fork_test.go index a7010af14a..95325eefeb 100644 --- a/tests/integration/repo_fork_test.go +++ b/tests/integration/repo_fork_test.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" org_service "code.gitea.io/gitea/services/org" "code.gitea.io/gitea/tests" @@ -51,7 +52,8 @@ func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkO "repo_name": forkRepoName, "fork_single_branch": forkBranch, }) - session.MakeRequest(t, req, http.StatusSeeOther) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, fmt.Sprintf("/%s/%s", forkOwnerName, forkRepoName), test.RedirectURL(resp)) // Step4: check the existence of the forked repo req = NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName) @@ -82,7 +84,7 @@ func TestRepoForkToOrg(t *testing.T) { func TestForkListLimitedAndPrivateRepos(t *testing.T) { defer tests.PrepareTestEnv(t)() - forkItemSelector := ".repo-fork-item" + forkItemSelector := ".fork-list .flex-item" user1Sess := loginUser(t, "user1") user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"}) diff --git a/tests/integration/repo_merge_upstream_test.go b/tests/integration/repo_merge_upstream_test.go index e928b04e9b..d33d31c646 100644 --- a/tests/integration/repo_merge_upstream_test.go +++ b/tests/integration/repo_merge_upstream_test.go @@ -147,5 +147,37 @@ func TestRepoMergeUpstream(t *testing.T) { return queryMergeUpstreamButtonLink(htmlDoc) == "" }, 5*time.Second, 100*time.Millisecond) }) + + t.Run("FastForwardOnly", func(t *testing.T) { + // Create a clean branch for fast-forward testing + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/test-repo-fork/branches/_new/branch/master", forkUser.Name), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "new_branch_name": "ff-test-branch", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // Add content to base repository that can be fast-forwarded + require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "ff-test.txt", "master", "ff-content-1")) + + // ff_only=true with fast-forward possible (should succeed) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{ + Branch: "ff-test-branch", + FfOnly: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var mergeResp api.MergeUpstreamResponse + DecodeJSON(t, resp, &mergeResp) + assert.Equal(t, "fast-forward", mergeResp.MergeStyle) + + // ff_only=true when fast-forward is not possible (should fail) + require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "another-file.txt", "master", "more-content")) + + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{ + Branch: "fork-branch", + FfOnly: true, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusBadRequest) + }) }) } diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index c04d09af08..adfe07519f 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -27,6 +27,7 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRepoView(t *testing.T) { @@ -41,6 +42,7 @@ func TestRepoView(t *testing.T) { t.Run("BlameFileInRepo", testBlameFileInRepo) t.Run("ViewRepoDirectory", testViewRepoDirectory) t.Run("ViewRepoDirectoryReadme", testViewRepoDirectoryReadme) + t.Run("ViewRepoSymlink", testViewRepoSymlink) t.Run("MarkDownReadmeImage", testMarkDownReadmeImage) t.Run("MarkDownReadmeImageSubfolder", testMarkDownReadmeImageSubfolder) t.Run("GeneratedSourceLink", testGeneratedSourceLink) @@ -412,6 +414,21 @@ func testViewRepoDirectoryReadme(t *testing.T) { missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/") } +func testViewRepoSymlink(t *testing.T) { + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/readme-test/src/branch/symlink") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, htmlDoc, ".entry-symbol-link", true) + followSymbolLinkHref := htmlDoc.Find(".entry-symbol-link").AttrOr("href", "") + require.Equal(t, "/user2/readme-test/src/branch/symlink/README.md?follow_symlink=1", followSymbolLinkHref) + + req = NewRequest(t, "GET", followSymbolLinkHref) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user2/readme-test/src/branch/symlink/some/other/path/awefulcake.txt?follow_symlink=1", resp.Header().Get("Location")) +} + func testMarkDownReadmeImage(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -523,7 +540,7 @@ func TestGenerateRepository(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name}) - err = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, generatedRepo.ID) + err = repo_service.DeleteRepositoryDirectly(db.DefaultContext, generatedRepo.ID) assert.NoError(t, err) // a failed creating because some mock data diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index 13e3d198ea..1da7bc9d3c 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -9,15 +9,16 @@ import ( "net/http" "net/http/httptest" "net/url" + "path" "strings" "testing" - "time" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" @@ -131,19 +132,19 @@ func (m *mockWebhookProvider) Close() { } func Test_WebhookCreate(t *testing.T) { - var payloads []api.CreatePayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.CreatePayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = string(webhook_module.HookEventCreate) - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.CreatePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.CreatePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = string(webhook_module.HookEventCreate) + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -163,19 +164,19 @@ func Test_WebhookCreate(t *testing.T) { } func Test_WebhookDelete(t *testing.T) { - var payloads []api.DeletePayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.DeletePayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "delete" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.DeletePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.DeletePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "delete" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -196,19 +197,19 @@ func Test_WebhookDelete(t *testing.T) { } func Test_WebhookFork(t *testing.T) { - var payloads []api.ForkPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.ForkPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "fork" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.ForkPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.ForkPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "fork" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user1") @@ -228,19 +229,19 @@ func Test_WebhookFork(t *testing.T) { } func Test_WebhookIssueComment(t *testing.T) { - var payloads []api.IssueCommentPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.IssueCommentPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "issue_comment" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.IssueCommentPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.IssueCommentPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "issue_comment" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -312,19 +313,19 @@ func Test_WebhookIssueComment(t *testing.T) { } func Test_WebhookRelease(t *testing.T) { - var payloads []api.ReleasePayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.ReleasePayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "release" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.ReleasePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.ReleasePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "release" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -345,19 +346,19 @@ func Test_WebhookRelease(t *testing.T) { } func Test_WebhookPush(t *testing.T) { - var payloads []api.PushPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.PushPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "push" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.PushPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PushPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "push" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -416,19 +417,19 @@ func Test_WebhookPushDevBranch(t *testing.T) { } func Test_WebhookIssue(t *testing.T) { - var payloads []api.IssuePayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.IssuePayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "issues" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.IssuePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.IssuePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "issues" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -445,6 +446,78 @@ func Test_WebhookIssue(t *testing.T) { assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) assert.Equal(t, "Title1", payloads[0].Issue.Title) assert.Equal(t, "Description1", payloads[0].Issue.Body) + assert.Positive(t, payloads[0].Issue.Created.Unix()) + assert.Positive(t, payloads[0].Issue.Updated.Unix()) + }) +} + +func Test_WebhookIssueDelete(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.IssuePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.IssuePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "issue" + }, http.StatusOK) + defer provider.Close() + + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues") + issueURL := testNewIssue(t, session, "user2", "repo1", "Title1", "Description1") + + // 2. trigger the webhook + testIssueDelete(t, session, issueURL) + + // 3. validate the webhook is triggered + assert.Equal(t, "issue", triggeredEvent) + require.Len(t, payloads, 2) + assert.EqualValues(t, "deleted", payloads[1].Action) + assert.Equal(t, "repo1", payloads[1].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[1].Issue.Repo.FullName) + assert.Equal(t, "Title1", payloads[1].Issue.Title) + assert.Equal(t, "Description1", payloads[1].Issue.Body) + }) +} + +func Test_WebhookIssueAssign(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.PullRequestPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PullRequestPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "pull_request_assign" + }, http.StatusOK) + defer provider.Close() + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_assign") + + // 2. trigger the webhook, issue 2 is a pull request + testIssueAssign(t, session, repo1.Link(), 2, user2.ID) + + // 3. validate the webhook is triggered + assert.Equal(t, "pull_request_assign", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "assigned", payloads[0].Action) + assert.Equal(t, "repo1", payloads[0].PullRequest.Base.Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName) + assert.Equal(t, "issue2", payloads[0].PullRequest.Title) + assert.Equal(t, "content for the second issue", payloads[0].PullRequest.Body) + assert.Equal(t, user2.ID, payloads[0].PullRequest.Assignee.ID) }) } @@ -521,19 +594,19 @@ func Test_WebhookIssueMilestone(t *testing.T) { } func Test_WebhookPullRequest(t *testing.T) { - var payloads []api.PullRequestPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.PullRequestPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "pull_request" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.PullRequestPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PullRequestPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "pull_request" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -557,20 +630,58 @@ func Test_WebhookPullRequest(t *testing.T) { }) } -func Test_WebhookPullRequestComment(t *testing.T) { - var payloads []api.IssueCommentPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.IssueCommentPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "pull_request_comment" - }, http.StatusOK) - defer provider.Close() +func Test_WebhookPullRequestDelete(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.PullRequestPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PullRequestPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "pull_request" + }, http.StatusOK) + defer provider.Close() + + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request") + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + issueURL := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") + + // 2. trigger the webhook + testIssueDelete(t, session, path.Join(repo1.Link(), "pulls", issueURL)) + + // 3. validate the webhook is triggered + assert.Equal(t, "pull_request", triggeredEvent) + require.Len(t, payloads, 2) + assert.EqualValues(t, "deleted", payloads[1].Action) + assert.Equal(t, "repo1", payloads[1].PullRequest.Base.Repository.Name) + assert.Equal(t, "user2/repo1", payloads[1].PullRequest.Base.Repository.FullName) + assert.Equal(t, 0, *payloads[1].PullRequest.Additions) + assert.Equal(t, 0, *payloads[1].PullRequest.ChangedFiles) + assert.Equal(t, 0, *payloads[1].PullRequest.Deletions) + }) +} + +func Test_WebhookPullRequestComment(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.IssueCommentPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.IssueCommentPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "pull_request_comment" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -596,19 +707,19 @@ func Test_WebhookPullRequestComment(t *testing.T) { } func Test_WebhookWiki(t *testing.T) { - var payloads []api.WikiPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.WikiPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "wiki" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.WikiPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.WikiPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "wiki" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -628,19 +739,19 @@ func Test_WebhookWiki(t *testing.T) { } func Test_WebhookRepository(t *testing.T) { - var payloads []api.RepositoryPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.RepositoryPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "repository" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.RepositoryPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.RepositoryPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "repository" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user1") @@ -660,19 +771,19 @@ func Test_WebhookRepository(t *testing.T) { } func Test_WebhookPackage(t *testing.T) { - var payloads []api.PackagePayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.PackagePayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "package" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.PackagePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PackagePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "package" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user1") @@ -697,24 +808,24 @@ func Test_WebhookPackage(t *testing.T) { } func Test_WebhookStatus(t *testing.T) { - var payloads []api.CommitStatusPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status") - assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository") - assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status") - assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository") - assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status") - content, _ := io.ReadAll(r.Body) - var payload api.CommitStatusPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "status" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.CommitStatusPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status") + assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository") + assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status") + assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository") + assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status") + content, _ := io.ReadAll(r.Body) + var payload api.CommitStatusPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "status" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -732,7 +843,7 @@ func Test_WebhookStatus(t *testing.T) { // update a status for a commit via API doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "http://test.ci/", Description: "", Context: "testci", @@ -750,16 +861,16 @@ func Test_WebhookStatus(t *testing.T) { } func Test_WebhookStatus_NoWrongTrigger(t *testing.T) { - var trigger string - provider := newMockWebhookProvider(func(r *http.Request) { - assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status") - assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status") - assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status") - trigger = "push" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var trigger string + provider := newMockWebhookProvider(func(r *http.Request) { + assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status") + assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status") + assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status") + trigger = "push" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") @@ -775,22 +886,22 @@ func Test_WebhookStatus_NoWrongTrigger(t *testing.T) { } func Test_WebhookWorkflowJob(t *testing.T) { - var payloads []api.WorkflowJobPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_job", "X-GitHub-Event-Type should contain workflow_job") - assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_job", "X-Gitea-Event-Type should contain workflow_job") - assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_job", "X-Gogs-Event-Type should contain workflow_job") - content, _ := io.ReadAll(r.Body) - var payload api.WorkflowJobPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "workflow_job" - }, http.StatusOK) - defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.WorkflowJobPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_job", "X-GitHub-Event-Type should contain workflow_job") + assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_job", "X-Gitea-Event-Type should contain workflow_job") + assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_job", "X-Gogs-Event-Type should contain workflow_job") + content, _ := io.ReadAll(r.Body) + var payload api.WorkflowJobPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "workflow_job" + }, http.StatusOK) + defer provider.Close() + // 1. create a new webhook with special webhook for repo1 user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, "user2") @@ -850,8 +961,7 @@ jobs: // 4. Execute a single Job task := runner.fetchTask(t) outcome := &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - execTime: time.Millisecond, + result: runnerv1.Result_RESULT_SUCCESS, } runner.execTask(t, task, outcome) @@ -872,8 +982,7 @@ jobs: assert.Equal(t, commitID, payloads[3].WorkflowJob.HeadSha) assert.Equal(t, "repo1", payloads[3].Repo.Name) assert.Equal(t, "user2/repo1", payloads[3].Repo.FullName) - assert.Contains(t, payloads[3].WorkflowJob.URL, fmt.Sprintf("/actions/runs/%d/jobs/%d", payloads[3].WorkflowJob.RunID, payloads[3].WorkflowJob.ID)) - assert.Contains(t, payloads[3].WorkflowJob.URL, payloads[3].WorkflowJob.RunURL) + assert.Contains(t, payloads[3].WorkflowJob.URL, fmt.Sprintf("/actions/jobs/%d", payloads[3].WorkflowJob.ID)) assert.Contains(t, payloads[3].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 0)) assert.Len(t, payloads[3].WorkflowJob.Steps, 1) @@ -887,8 +996,7 @@ jobs: // 6. Execute a single Job task = runner.fetchTask(t) outcome = &mockTaskOutcome{ - result: runnerv1.Result_RESULT_FAILURE, - execTime: time.Millisecond, + result: runnerv1.Result_RESULT_FAILURE, } runner.execTask(t, task, outcome) @@ -910,9 +1018,207 @@ jobs: assert.Equal(t, commitID, payloads[6].WorkflowJob.HeadSha) assert.Equal(t, "repo1", payloads[6].Repo.Name) assert.Equal(t, "user2/repo1", payloads[6].Repo.FullName) - assert.Contains(t, payloads[6].WorkflowJob.URL, fmt.Sprintf("/actions/runs/%d/jobs/%d", payloads[6].WorkflowJob.RunID, payloads[6].WorkflowJob.ID)) - assert.Contains(t, payloads[6].WorkflowJob.URL, payloads[6].WorkflowJob.RunURL) + assert.Contains(t, payloads[6].WorkflowJob.URL, fmt.Sprintf("/actions/jobs/%d", payloads[6].WorkflowJob.ID)) assert.Contains(t, payloads[6].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 1)) assert.Len(t, payloads[6].WorkflowJob.Steps, 2) }) } + +type workflowRunWebhook struct { + URL string + payloads []api.WorkflowRunPayload + triggeredEvent string +} + +func Test_WebhookWorkflowRun(t *testing.T) { + webhookData := &workflowRunWebhook{} + provider := newMockWebhookProvider(func(r *http.Request) { + assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_run", "X-GitHub-Event-Type should contain workflow_run") + assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_run", "X-Gitea-Event-Type should contain workflow_run") + assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_run", "X-Gogs-Event-Type should contain workflow_run") + content, _ := io.ReadAll(r.Body) + var payload api.WorkflowRunPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + webhookData.payloads = append(webhookData.payloads, payload) + webhookData.triggeredEvent = "workflow_run" + }, http.StatusOK) + defer provider.Close() + webhookData.URL = provider.URL() + + tests := []struct { + name string + callback func(t *testing.T, webhookData *workflowRunWebhook) + }{ + { + name: "WorkflowRun", + callback: testWebhookWorkflowRun, + }, + { + name: "WorkflowRunDepthLimit", + callback: testWebhookWorkflowRunDepthLimit, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + webhookData.payloads = nil + webhookData.triggeredEvent = "" + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + test.callback(t, webhookData) + }) + }) + } +} + +func testWebhookWorkflowRun(t *testing.T, webhookData *workflowRunWebhook) { + // 1. create a new webhook with special webhook for repo1 + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run") + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + + gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1) + assert.NoError(t, err) + + runner := newMockRunner() + runner.registerAsRepoRunner(t, "user2", "repo1", "mock-runner", []string{"ubuntu-latest"}, false) + + // 2.1 add workflow_run workflow file to the repo + + opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+"dispatch.yml", ` +on: + workflow_run: + workflows: ["Push"] + types: + - completed +jobs: + dispatch: + runs-on: ubuntu-latest + steps: + - run: echo 'test the webhook' +`) + createWorkflowFile(t, token, "user2", "repo1", ".gitea/workflows/dispatch.yml", opts) + + // 2.2 trigger the webhooks + + // add workflow file to the repo + // init the workflow + wfTreePath := ".gitea/workflows/push.yml" + wfFileContent := `name: Push +on: push +jobs: + wf1-job: + runs-on: ubuntu-latest + steps: + - run: echo 'test the webhook' + wf2-job: + runs-on: ubuntu-latest + needs: wf1-job + steps: + - run: echo 'cmd 1' + - run: echo 'cmd 2' +` + opts = getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts) + + commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) + assert.NoError(t, err) + + // 3. validate the webhook is triggered + assert.Equal(t, "workflow_run", webhookData.triggeredEvent) + assert.Len(t, webhookData.payloads, 1) + assert.Equal(t, "requested", webhookData.payloads[0].Action) + assert.Equal(t, "queued", webhookData.payloads[0].WorkflowRun.Status) + assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[0].WorkflowRun.HeadBranch) + assert.Equal(t, commitID, webhookData.payloads[0].WorkflowRun.HeadSha) + assert.Equal(t, "repo1", webhookData.payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", webhookData.payloads[0].Repo.FullName) + + // 4. Execute two Jobs + task := runner.fetchTask(t) + outcome := &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + } + runner.execTask(t, task, outcome) + + task = runner.fetchTask(t) + outcome = &mockTaskOutcome{ + result: runnerv1.Result_RESULT_FAILURE, + } + runner.execTask(t, task, outcome) + + // 7. validate the webhook is triggered + assert.Equal(t, "workflow_run", webhookData.triggeredEvent) + assert.Len(t, webhookData.payloads, 3) + assert.Equal(t, "completed", webhookData.payloads[1].Action) + assert.Equal(t, "push", webhookData.payloads[1].WorkflowRun.Event) + + // 3. validate the webhook is triggered + assert.Equal(t, "workflow_run", webhookData.triggeredEvent) + assert.Len(t, webhookData.payloads, 3) + assert.Equal(t, "requested", webhookData.payloads[2].Action) + assert.Equal(t, "queued", webhookData.payloads[2].WorkflowRun.Status) + assert.Equal(t, "workflow_run", webhookData.payloads[2].WorkflowRun.Event) + assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[2].WorkflowRun.HeadBranch) + assert.Equal(t, commitID, webhookData.payloads[2].WorkflowRun.HeadSha) + assert.Equal(t, "repo1", webhookData.payloads[2].Repo.Name) + assert.Equal(t, "user2/repo1", webhookData.payloads[2].Repo.FullName) +} + +func testWebhookWorkflowRunDepthLimit(t *testing.T, webhookData *workflowRunWebhook) { + // 1. create a new webhook with special webhook for repo1 + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run") + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + + gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1) + assert.NoError(t, err) + + // 2. trigger the webhooks + + // add workflow file to the repo + // init the workflow + wfTreePath := ".gitea/workflows/push.yml" + wfFileContent := `name: Endless Loop +on: + push: + workflow_run: + types: + - requested +jobs: + dispatch: + runs-on: ubuntu-latest + steps: + - run: echo 'test the webhook' +` + opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts) + + commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) + assert.NoError(t, err) + + // 3. validate the webhook is triggered + assert.Equal(t, "workflow_run", webhookData.triggeredEvent) + // 1x push + 5x workflow_run requested chain + assert.Len(t, webhookData.payloads, 6) + for i := range 6 { + assert.Equal(t, "requested", webhookData.payloads[i].Action) + assert.Equal(t, "queued", webhookData.payloads[i].WorkflowRun.Status) + assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[i].WorkflowRun.HeadBranch) + assert.Equal(t, commitID, webhookData.payloads[i].WorkflowRun.HeadSha) + if i == 0 { + assert.Equal(t, "push", webhookData.payloads[i].WorkflowRun.Event) + } else { + assert.Equal(t, "workflow_run", webhookData.payloads[i].WorkflowRun.Event) + } + assert.Equal(t, "repo1", webhookData.payloads[i].Repo.Name) + assert.Equal(t, "user2/repo1", webhookData.payloads[i].Repo.FullName) + } +} diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go index ce55a2f943..b63d06a866 100644 --- a/tests/integration/repofiles_change_test.go +++ b/tests/integration/repofiles_change_test.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/contexttest" files_service "code.gitea.io/gitea/services/repository/files" @@ -58,6 +59,40 @@ func getUpdateRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang } } +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 getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions { return &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ @@ -248,6 +283,114 @@ func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA } } +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: util.ToPointer("2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c"), + lfsSize: util.ToPointer(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: util.ToPointer("7b6b2c88dba9f760a1a58469b67fee2b698ef7e9399c4ca4f34a14ccbe39f623"), + lfsSize: util.ToPointer(int64(27)), + }, + } + + var responses []*api.ContentsResponse + for _, detail := range details { + selfURL := setting.AppURL + "api/v1/repos/user2/lfs/contents/" + detail.filename + "?ref=master" + htmlURL := setting.AppURL + "user2/lfs/src/branch/master/" + detail.filename + gitURL := setting.AppURL + "api/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: lastCommitSHA, + Type: "file", + Size: detail.size, + Encoding: util.ToPointer("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 + "api/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: "user2@noreply.example.org", + }, + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + }, + Parents: []*api.CommitMeta{ + { + URL: setting.AppURL + "api/v1/repos/user2/lfs/git/commits/73cf03db6ece34e12bf91e8853dc58f678f2f82d", + SHA: "73cf03db6ece34e12bf91e8853dc58f678f2f82d", + }, + }, + Message: "Rename files\n", + Tree: &api.CommitMeta{ + URL: setting.AppURL + "api/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 onGiteaRun(t, func(t *testing.T, u *url.URL) { @@ -369,6 +512,38 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) { }) } +func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) { + onGiteaRun(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(git.DefaultContext, repo, ctx.Doer, opts) + + // asserts + assert.NoError(t, err) + gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo) + defer gitRepo.Close() + + commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch) + lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdateRename(commit.ID.String(), lastCommit.ID.String()) + for _, file := range filesResponse.Files { + file.LastCommitterDate, file.LastAuthorDate = time.Time{}, time.Time{} // 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 diff --git a/tests/integration/setting_test.go b/tests/integration/setting_test.go index 9dad9ca716..64fd28c5e9 100644 --- a/tests/integration/setting_test.go +++ b/tests/integration/setting_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -92,8 +93,7 @@ func TestSettingShowUserEmailProfile(t *testing.T) { func TestSettingLandingPage(t *testing.T) { defer tests.PrepareTestEnv(t)() - - landingPage := setting.LandingPageURL + defer test.MockVariableValue(&setting.LandingPageURL)() setting.LandingPageURL = setting.LandingPageHome req := NewRequest(t, "GET", "/") @@ -113,6 +113,4 @@ func TestSettingLandingPage(t *testing.T) { req = NewRequest(t, "GET", "/") resp = MakeRequest(t, req, http.StatusSeeOther) assert.Equal(t, "/user/login", resp.Header().Get("Location")) - - setting.LandingPageURL = landingPage } diff --git a/tests/integration/signup_test.go b/tests/integration/signup_test.go index e86851352e..c08f57d33e 100644 --- a/tests/integration/signup_test.go +++ b/tests/integration/signup_test.go @@ -22,8 +22,7 @@ import ( func TestSignup(t *testing.T) { defer tests.PrepareTestEnv(t)() - - setting.Service.EnableCaptcha = false + defer test.MockVariableValue(&setting.Service.EnableCaptcha, false)() req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ "user_name": "exampleUser", @@ -40,9 +39,8 @@ func TestSignup(t *testing.T) { func TestSignupAsRestricted(t *testing.T) { defer tests.PrepareTestEnv(t)() - - setting.Service.EnableCaptcha = false - setting.Service.DefaultUserIsRestricted = true + defer test.MockVariableValue(&setting.Service.EnableCaptcha, false)() + defer test.MockVariableValue(&setting.Service.DefaultUserIsRestricted, true)() req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ "user_name": "restrictedUser", @@ -62,8 +60,7 @@ func TestSignupAsRestricted(t *testing.T) { func TestSignupEmailValidation(t *testing.T) { defer tests.PrepareTestEnv(t)() - - setting.Service.EnableCaptcha = false + defer test.MockVariableValue(&setting.Service.EnableCaptcha, false)() tests := []struct { email string diff --git a/tests/integration/ssh_key_test.go b/tests/integration/ssh_key_test.go index fbdda9b3af..b34a986be3 100644 --- a/tests/integration/ssh_key_test.go +++ b/tests/integration/ssh_key_test.go @@ -27,7 +27,7 @@ func doCheckRepositoryEmptyStatus(ctx APITestContext, isEmpty bool) func(*testin func doAddChangesToCheckout(dstPath, filename string) func(*testing.T) { return func(t *testing.T) { - assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now())), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), fmt.Appendf(nil, "# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now()), 0o644)) assert.NoError(t, git.AddChanges(dstPath, true)) signature := git.Signature{ Email: "test@example.com", diff --git a/tests/integration/timetracking_test.go b/tests/integration/timetracking_test.go index 8985dfdbce..4e8109be96 100644 --- a/tests/integration/timetracking_test.go +++ b/tests/integration/timetracking_test.go @@ -46,11 +46,11 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo AssertHTMLElement(t, htmlDoc, ".issue-add-time", canTrackTime) issueLink := path.Join(user, repo, "issues", issue) - req = NewRequestWithValues(t, "POST", path.Join(issueLink, "times", "stopwatch", "toggle"), map[string]string{ + reqStart := NewRequestWithValues(t, "POST", path.Join(issueLink, "times", "stopwatch", "start"), map[string]string{ "_csrf": htmlDoc.GetCSRF(), }) if canTrackTime { - session.MakeRequest(t, req, http.StatusOK) + session.MakeRequest(t, reqStart, http.StatusOK) req = NewRequest(t, "GET", issueLink) resp = session.MakeRequest(t, req, http.StatusOK) @@ -65,10 +65,10 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo // Sleep for 1 second to not get wrong order for stopping timer time.Sleep(time.Second) - req = NewRequestWithValues(t, "POST", path.Join(issueLink, "times", "stopwatch", "toggle"), map[string]string{ + reqStop := NewRequestWithValues(t, "POST", path.Join(issueLink, "times", "stopwatch", "stop"), map[string]string{ "_csrf": htmlDoc.GetCSRF(), }) - session.MakeRequest(t, req, http.StatusOK) + session.MakeRequest(t, reqStop, http.StatusOK) req = NewRequest(t, "GET", issueLink) resp = session.MakeRequest(t, req, http.StatusOK) @@ -77,6 +77,6 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo events = htmlDoc.doc.Find(".event > span.text") assert.Contains(t, events.Last().Text(), "worked for ") } else { - session.MakeRequest(t, req, http.StatusNotFound) + session.MakeRequest(t, reqStart, http.StatusNotFound) } } diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go index 2c9a916ec2..34692d9cab 100644 --- a/tests/integration/user_test.go +++ b/tests/integration/user_test.go @@ -257,8 +257,8 @@ func TestListStopWatches(t *testing.T) { } func TestUserLocationMapLink(t *testing.T) { - setting.Service.UserLocationMapURL = "https://example/foo/" defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.Service.UserLocationMapURL, "https://example/foo/")() session := loginUser(t, "user2") req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ diff --git a/tests/integration/webfinger_test.go b/tests/integration/webfinger_test.go index 2afaed4a45..757d442cd2 100644 --- a/tests/integration/webfinger_test.go +++ b/tests/integration/webfinger_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -20,11 +21,7 @@ import ( func TestWebfinger(t *testing.T) { defer tests.PrepareTestEnv(t)() - - setting.Federation.Enabled = true - defer func() { - setting.Federation.Enabled = false - }() + defer test.MockVariableValue(&setting.Federation.Enabled, true)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/workflow_run_api_check_test.go b/tests/integration/workflow_run_api_check_test.go new file mode 100644 index 0000000000..6a80bb5118 --- /dev/null +++ b/tests/integration/workflow_run_api_check_test.go @@ -0,0 +1,167 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIWorkflowRun(t *testing.T) { + t.Run("AdminRuns", func(t *testing.T) { + testAPIWorkflowRunBasic(t, "/api/v1/admin/actions", "User1", 802, auth_model.AccessTokenScopeReadAdmin, auth_model.AccessTokenScopeReadRepository) + }) + t.Run("UserRuns", func(t *testing.T) { + testAPIWorkflowRunBasic(t, "/api/v1/user/actions", "User2", 803, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + }) + t.Run("OrgRuns", func(t *testing.T) { + testAPIWorkflowRunBasic(t, "/api/v1/orgs/org3/actions", "User1", 802, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadRepository) + }) + t.Run("RepoRuns", func(t *testing.T) { + testAPIWorkflowRunBasic(t, "/api/v1/repos/org3/repo5/actions", "User2", 802, auth_model.AccessTokenScopeReadRepository) + }) +} + +func testAPIWorkflowRunBasic(t *testing.T, apiRootURL, userUsername string, runID int64, scope ...auth_model.AccessTokenScope) { + defer tests.PrepareTestEnv(t)() + token := getUserToken(t, userUsername, scope...) + + apiRunsURL := fmt.Sprintf("%s/%s", apiRootURL, "runs") + req := NewRequest(t, "GET", apiRunsURL).AddTokenAuth(token) + runnerListResp := MakeRequest(t, req, http.StatusOK) + runnerList := api.ActionWorkflowRunsResponse{} + DecodeJSON(t, runnerListResp, &runnerList) + + foundRun := false + + for _, run := range runnerList.Entries { + // Verify filtering works + verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", run.Status, "", "", "", "") + verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, run.Conclusion, "", "", "", "", "") + verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", run.HeadBranch, "", "") + verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", run.Event, "", "", "") + verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", "", run.TriggerActor.UserName, "") + verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", "", run.TriggerActor.UserName, run.HeadSha) + + // Verify run url works + req := NewRequest(t, "GET", run.URL).AddTokenAuth(token) + runResp := MakeRequest(t, req, http.StatusOK) + apiRun := api.ActionWorkflowRun{} + DecodeJSON(t, runResp, &apiRun) + assert.Equal(t, run.ID, apiRun.ID) + assert.Equal(t, run.Status, apiRun.Status) + assert.Equal(t, run.Conclusion, apiRun.Conclusion) + assert.Equal(t, run.Event, apiRun.Event) + + // Verify jobs list works + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s", run.URL, "jobs")).AddTokenAuth(token) + jobsResp := MakeRequest(t, req, http.StatusOK) + jobList := api.ActionWorkflowJobsResponse{} + DecodeJSON(t, jobsResp, &jobList) + + if run.ID == runID { + foundRun = true + assert.Len(t, jobList.Entries, 1) + for _, job := range jobList.Entries { + // Check the jobs list of the run + verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", run.URL, "jobs"), token, job.ID, "", job.Status) + verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", run.URL, "jobs"), token, job.ID, job.Conclusion, "") + // Check the run independent job list + verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", apiRootURL, "jobs"), token, job.ID, "", job.Status) + verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", apiRootURL, "jobs"), token, job.ID, job.Conclusion, "") + + // Verify job url works + req := NewRequest(t, "GET", job.URL).AddTokenAuth(token) + jobsResp := MakeRequest(t, req, http.StatusOK) + apiJob := api.ActionWorkflowJob{} + DecodeJSON(t, jobsResp, &apiJob) + assert.Equal(t, job.ID, apiJob.ID) + assert.Equal(t, job.RunID, apiJob.RunID) + assert.Equal(t, job.Status, apiJob.Status) + assert.Equal(t, job.Conclusion, apiJob.Conclusion) + } + } + } + assert.True(t, foundRun, "Expected to find run with ID %d", runID) +} + +func verifyWorkflowRunCanbeFoundWithStatusFilter(t *testing.T, runAPIURL, token string, id int64, conclusion, status, event, branch, actor, headSHA string) { + filter := url.Values{} + if conclusion != "" { + filter.Add("status", conclusion) + } + if status != "" { + filter.Add("status", status) + } + if event != "" { + filter.Set("event", event) + } + if branch != "" { + filter.Set("branch", branch) + } + if actor != "" { + filter.Set("actor", actor) + } + if headSHA != "" { + filter.Set("head_sha", headSHA) + } + req := NewRequest(t, "GET", runAPIURL+"?"+filter.Encode()).AddTokenAuth(token) + runResp := MakeRequest(t, req, http.StatusOK) + runList := api.ActionWorkflowRunsResponse{} + DecodeJSON(t, runResp, &runList) + + found := false + for _, run := range runList.Entries { + if conclusion != "" { + assert.Equal(t, conclusion, run.Conclusion) + } + if status != "" { + assert.Equal(t, status, run.Status) + } + if event != "" { + assert.Equal(t, event, run.Event) + } + if branch != "" { + assert.Equal(t, branch, run.HeadBranch) + } + if actor != "" { + assert.Equal(t, actor, run.Actor.UserName) + } + found = found || run.ID == id + } + assert.True(t, found, "Expected to find run with ID %d", id) +} + +func verifyWorkflowJobCanbeFoundWithStatusFilter(t *testing.T, runAPIURL, token string, id int64, conclusion, status string) { + filter := conclusion + if filter == "" { + filter = status + } + if filter == "" { + return + } + req := NewRequest(t, "GET", runAPIURL+"?status="+filter).AddTokenAuth(token) + jobListResp := MakeRequest(t, req, http.StatusOK) + jobList := api.ActionWorkflowJobsResponse{} + DecodeJSON(t, jobListResp, &jobList) + + found := false + for _, job := range jobList.Entries { + if conclusion != "" { + assert.Equal(t, conclusion, job.Conclusion) + } else { + assert.Equal(t, status, job.Status) + } + found = found || job.ID == id + } + assert.True(t, found, "Expected to find job with ID %d", id) +} |