diff options
Diffstat (limited to 'tests/integration')
178 files changed, 8527 insertions, 2421 deletions
diff --git a/tests/integration/actions_delete_run_test.go b/tests/integration/actions_delete_run_test.go new file mode 100644 index 0000000000..22f9a1f740 --- /dev/null +++ b/tests/integration/actions_delete_run_test.go @@ -0,0 +1,181 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + "time" + + actions_model "code.gitea.io/gitea/models/actions" + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/routers/web/repo/actions" + + runnerv1 "code.gitea.io/actions-proto-go/runner/v1" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestActionsDeleteRun(t *testing.T) { + now := time.Now() + testCase := struct { + treePath string + fileContent string + outcomes map[string]*mockTaskOutcome + expectedStatuses map[string]string + }{ + treePath: ".gitea/workflows/test1.yml", + fileContent: `name: test1 +on: + push: + paths: + - .gitea/workflows/test1.yml +jobs: + job1: + runs-on: ubuntu-latest + steps: + - run: echo job1 + job2: + runs-on: ubuntu-latest + steps: + - run: echo job2 + job3: + runs-on: ubuntu-latest + steps: + - run: echo job3 +`, + outcomes: map[string]*mockTaskOutcome{ + "job1": { + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(4 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(5 * time.Second)), + Content: "job1", + }, + { + Time: timestamppb.New(now.Add(6 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, + }, + }, + "job2": { + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(4 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(5 * time.Second)), + Content: "job2", + }, + { + Time: timestamppb.New(now.Add(6 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, + }, + }, + "job3": { + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(4 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(5 * time.Second)), + Content: "job3", + }, + { + Time: timestamppb.New(now.Add(6 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, + }, + }, + }, + expectedStatuses: map[string]string{ + "job1": actions_model.StatusSuccess.String(), + "job2": actions_model.StatusSuccess.String(), + "job3": actions_model.StatusSuccess.String(), + }, + } + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + apiRepo := createActionsTestRepo(t, token, "actions-delete-run-test", false) + runner := newMockRunner() + runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + + opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+testCase.treePath, testCase.fileContent) + createWorkflowFile(t, token, user2.Name, apiRepo.Name, testCase.treePath, opts) + + runIndex := "" + for i := 0; i < len(testCase.outcomes); i++ { + task := runner.fetchTask(t) + jobName := getTaskJobNameByTaskID(t, token, user2.Name, apiRepo.Name, task.Id) + outcome := testCase.outcomes[jobName] + assert.NotNil(t, outcome) + runner.execTask(t, task, outcome) + runIndex = task.Context.GetFields()["run_number"].GetStringValue() + assert.Equal(t, "1", runIndex) + } + + for i := 0; i < len(testCase.outcomes); i++ { + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d", user2.Name, apiRepo.Name, runIndex, i), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + resp := session.MakeRequest(t, req, http.StatusOK) + var listResp actions.ViewResponse + err := json.Unmarshal(resp.Body.Bytes(), &listResp) + assert.NoError(t, err) + assert.Len(t, listResp.State.Run.Jobs, 3) + + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, apiRepo.Name, runIndex, i)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + } + + req := NewRequestWithValues(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s", user2.Name, apiRepo.Name, runIndex), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/delete", user2.Name, apiRepo.Name, runIndex), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/delete", user2.Name, apiRepo.Name, runIndex), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestWithValues(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s", user2.Name, apiRepo.Name, runIndex), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusNotFound) + + for i := 0; i < len(testCase.outcomes); i++ { + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d", user2.Name, apiRepo.Name, runIndex, i), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, apiRepo.Name, runIndex, i)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + } + }) +} diff --git a/tests/integration/actions_job_test.go b/tests/integration/actions_job_test.go index a0c06b06fd..4f4456a4e5 100644 --- a/tests/integration/actions_job_test.go +++ b/tests/integration/actions_job_test.go @@ -4,12 +4,12 @@ package integration import ( - "context" "encoding/base64" "fmt" "net/http" "net/url" "reflect" + "strconv" "testing" "time" @@ -22,8 +22,10 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + actions_service "code.gitea.io/gitea/services/actions" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" + "connectrpc.com/connect" "github.com/stretchr/testify/assert" ) @@ -37,7 +39,7 @@ func TestJobWithNeeds(t *testing.T) { { treePath: ".gitea/workflows/job-with-needs.yml", fileContent: `name: job-with-needs -on: +on: push: paths: - '.gitea/workflows/job-with-needs.yml' @@ -68,7 +70,7 @@ jobs: { treePath: ".gitea/workflows/job-with-needs-fail.yml", fileContent: `name: job-with-needs-fail -on: +on: push: paths: - '.gitea/workflows/job-with-needs-fail.yml' @@ -96,7 +98,7 @@ jobs: { treePath: ".gitea/workflows/job-with-needs-fail-if.yml", fileContent: `name: job-with-needs-fail-if -on: +on: push: paths: - '.gitea/workflows/job-with-needs-fail-if.yml' @@ -133,12 +135,12 @@ jobs: apiRepo := createActionsTestRepo(t, token, "actions-jobs-with-needs", false) runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}) + runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) for _, tc := range testCases { - t.Run(fmt.Sprintf("test %s", tc.treePath), func(t *testing.T) { + t.Run("test "+tc.treePath, func(t *testing.T) { // create the workflow file - opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent) + opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+tc.treePath, tc.fileContent) fileResp := createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, opts) // fetch and execute task @@ -165,9 +167,6 @@ jobs: } }) } - - httpContext := NewAPITestContext(t, user2.Name, apiRepo.Name, auth_model.AccessTokenScopeWriteRepository) - doAPIDeleteRepository(httpContext)(t) }) } @@ -181,7 +180,7 @@ func TestJobNeedsMatrix(t *testing.T) { { treePath: ".gitea/workflows/jobs-outputs-with-matrix.yml", fileContent: `name: jobs-outputs-with-matrix -on: +on: push: paths: - '.gitea/workflows/jobs-outputs-with-matrix.yml' @@ -200,7 +199,7 @@ jobs: id: gen_output run: | version="${{ matrix.version }}" - echo "output_${version}=${version}" >> "$GITHUB_OUTPUT" + echo "output_${version}=${version}" >> "$GITHUB_OUTPUT" job2: runs-on: ubuntu-latest needs: [job1] @@ -247,7 +246,7 @@ jobs: { treePath: ".gitea/workflows/jobs-outputs-with-matrix-failure.yml", fileContent: `name: jobs-outputs-with-matrix-failure -on: +on: push: paths: - '.gitea/workflows/jobs-outputs-with-matrix-failure.yml' @@ -266,7 +265,7 @@ jobs: id: gen_output run: | version="${{ matrix.version }}" - echo "output_${version}=${version}" >> "$GITHUB_OUTPUT" + echo "output_${version}=${version}" >> "$GITHUB_OUTPUT" job2: runs-on: ubuntu-latest if: ${{ always() }} @@ -319,11 +318,11 @@ jobs: apiRepo := createActionsTestRepo(t, token, "actions-jobs-outputs-with-matrix", false) runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}) + runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) for _, tc := range testCases { - t.Run(fmt.Sprintf("test %s", tc.treePath), func(t *testing.T) { - opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent) + t.Run("test "+tc.treePath, func(t *testing.T) { + opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+tc.treePath, tc.fileContent) createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, opts) for i := 0; i < len(tc.outcomes); i++ { @@ -347,9 +346,6 @@ jobs: } }) } - - httpContext := NewAPITestContext(t, user2.Name, apiRepo.Name, auth_model.AccessTokenScopeWriteRepository) - doAPIDeleteRepository(httpContext)(t) }) } @@ -364,7 +360,95 @@ func TestActionsGiteaContext(t *testing.T) { user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) runner := newMockRunner() - runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}) + runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + + // init the workflow + wfTreePath := ".gitea/workflows/pull.yml" + wfFileContent := `name: Pull Request +on: pull_request +jobs: + wf1-job: + runs-on: ubuntu-latest + steps: + - run: echo 'test the pull' +` + opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) + // user2 creates a pull request + doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: "user2/patch-1", + Message: "create user2-patch.txt", + Author: api.Identity{ + Name: user2.Name, + Email: user2.Email, + }, + Committer: api.Identity{ + Name: user2.Name, + Email: user2.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")), + })(t) + apiPull, err := doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "user2/patch-1")(t) + assert.NoError(t, err) + task := runner.fetchTask(t) + gtCtx := task.Context.GetFields() + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) + actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) + actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) + assert.NoError(t, actionRun.LoadAttributes(t.Context())) + + assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) + assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) + assert.Equal(t, apiPull.Base.Ref, gtCtx["base_ref"].GetStringValue()) + runEvent := map[string]any{} + assert.NoError(t, json.Unmarshal([]byte(actionRun.EventPayload), &runEvent)) + assert.True(t, reflect.DeepEqual(gtCtx["event"].GetStructValue().AsMap(), runEvent)) + assert.Equal(t, actionRun.TriggerEvent, gtCtx["event_name"].GetStringValue()) + assert.Equal(t, apiPull.Head.Ref, gtCtx["head_ref"].GetStringValue()) + assert.Equal(t, actionRunJob.JobID, gtCtx["job"].GetStringValue()) + assert.Equal(t, actionRun.Ref, gtCtx["ref"].GetStringValue()) + assert.Equal(t, (git.RefName(actionRun.Ref)).ShortName(), gtCtx["ref_name"].GetStringValue()) + assert.False(t, gtCtx["ref_protected"].GetBoolValue()) + assert.Equal(t, string((git.RefName(actionRun.Ref)).RefType()), gtCtx["ref_type"].GetStringValue()) + assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) + assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) + assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) + assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) + assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) + assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) + assert.Equal(t, actionRun.WorkflowID, gtCtx["workflow"].GetStringValue()) + assert.Equal(t, setting.Actions.DefaultActionsURL.URL(), gtCtx["gitea_default_actions_url"].GetStringValue()) + token := gtCtx["token"].GetStringValue() + assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) + }) +} + +// Ephemeral +func TestActionsGiteaContextEphemeral(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2Session := loginUser(t, user2.Name) + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + + runner := newMockRunner() + runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, true) + + // verify CleanupEphemeralRunners does not remove this runner + err := actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) // init the workflow wfTreePath := ".gitea/workflows/pull.yml" @@ -375,8 +459,12 @@ jobs: runs-on: ubuntu-latest steps: - run: echo 'test the pull' + wf2-job: + runs-on: ubuntu-latest + steps: + - run: echo 'test the pull' ` - opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, fmt.Sprintf("create %s", wfTreePath), wfFileContent) + opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) // user2 creates a pull request doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ @@ -405,7 +493,7 @@ jobs: actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) - assert.NoError(t, actionRun.LoadAttributes(context.Background())) + assert.NoError(t, actionRun.LoadAttributes(t.Context())) assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) @@ -423,9 +511,9 @@ jobs: assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRunJob.RunID), gtCtx["run_id"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRun.Index), gtCtx["run_number"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRunJob.Attempt), gtCtx["run_attempt"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) @@ -434,7 +522,62 @@ jobs: token := gtCtx["token"].GetStringValue() assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) - doAPIDeleteRepository(user2APICtx)(t) + // verify CleanupEphemeralRunners does not remove this runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + resp, err := runner.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{ + TasksVersion: 0, + })) + assert.NoError(t, err) + assert.Nil(t, resp.Msg.Task) + + // verify CleanupEphemeralRunners does not remove this runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + _, err = runner.client.runnerServiceClient.UpdateTask(t.Context(), connect.NewRequest(&runnerv1.UpdateTaskRequest{ + State: &runnerv1.TaskState{ + Id: actionTask.ID, + Result: runnerv1.Result_RESULT_SUCCESS, + }, + })) + assert.NoError(t, err) + + resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{ + TasksVersion: 0, + })) + assert.Error(t, err) + assert.Nil(t, resp) + + resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{ + TasksVersion: 0, + })) + assert.Error(t, err) + assert.Nil(t, resp) + + // create a runner that picks a job and get force cancelled + runnerToBeRemoved := newMockRunner() + runnerToBeRemoved.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner-to-be-removed", []string{"ubuntu-latest"}, true) + + taskToStopAPIObj := runnerToBeRemoved.fetchTask(t) + + taskToStop := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: taskToStopAPIObj.Id}) + + // verify CleanupEphemeralRunners does not remove the custom crafted runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + runnerToRemove := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: taskToStop.RunnerID}) + + err = actions_model.StopTask(t.Context(), taskToStop.ID, actions_model.StatusFailure) + assert.NoError(t, err) + + // verify CleanupEphemeralRunners does remove the custom crafted runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: runnerToRemove.ID}) }) } diff --git a/tests/integration/actions_log_test.go b/tests/integration/actions_log_test.go index fd055fc4c4..503bda97c9 100644 --- a/tests/integration/actions_log_test.go +++ b/tests/integration/actions_log_test.go @@ -7,10 +7,12 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" "testing" "time" + actions_model "code.gitea.io/gitea/models/actions" auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -29,13 +31,13 @@ func TestDownloadTaskLogs(t *testing.T) { testCases := []struct { treePath string fileContent string - outcome *mockTaskOutcome + outcome []*mockTaskOutcome zstdEnabled bool }{ { treePath: ".gitea/workflows/download-task-logs-zstd.yml", fileContent: `name: download-task-logs-zstd -on: +on: push: paths: - '.gitea/workflows/download-task-logs-zstd.yml' @@ -44,21 +46,44 @@ jobs: runs-on: ubuntu-latest steps: - run: echo job1 with zstd enabled + job2: + runs-on: ubuntu-latest + steps: + - run: echo job2 with zstd enabled `, - outcome: &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(1 * time.Second)), - Content: " \U0001F433 docker create image", + outcome: []*mockTaskOutcome{ + { + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(1 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(2 * time.Second)), + Content: "job1 zstd enabled", + }, + { + Time: timestamppb.New(now.Add(3 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, }, - { - Time: timestamppb.New(now.Add(2 * time.Second)), - Content: "job1 zstd enabled", - }, - { - Time: timestamppb.New(now.Add(3 * time.Second)), - Content: "\U0001F3C1 Job succeeded", + }, + { + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(1 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(2 * time.Second)), + Content: "job2 zstd enabled", + }, + { + Time: timestamppb.New(now.Add(3 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, }, }, }, @@ -67,7 +92,7 @@ jobs: { treePath: ".gitea/workflows/download-task-logs-no-zstd.yml", fileContent: `name: download-task-logs-no-zstd -on: +on: push: paths: - '.gitea/workflows/download-task-logs-no-zstd.yml' @@ -76,21 +101,44 @@ jobs: runs-on: ubuntu-latest steps: - run: echo job1 with zstd disabled + job2: + runs-on: ubuntu-latest + steps: + - run: echo job2 with zstd disabled `, - outcome: &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job1 zstd disabled", + outcome: []*mockTaskOutcome{ + { + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(4 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(5 * time.Second)), + Content: "job1 zstd disabled", + }, + { + Time: timestamppb.New(now.Add(6 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", + }, + { + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(4 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(5 * time.Second)), + Content: "job2 zstd disabled", + }, + { + Time: timestamppb.New(now.Add(6 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, }, }, }, @@ -105,10 +153,10 @@ jobs: apiRepo := createActionsTestRepo(t, token, "actions-download-task-logs", false) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}) + runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) for _, tc := range testCases { - t.Run(fmt.Sprintf("test %s", tc.treePath), func(t *testing.T) { + t.Run("test "+tc.treePath, func(t *testing.T) { var resetFunc func() if tc.zstdEnabled { resetFunc = test.MockVariableValue(&setting.Actions.LogCompression, "zstd") @@ -119,41 +167,60 @@ jobs: } // create the workflow file - opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent) + opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+tc.treePath, tc.fileContent) createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts) - // fetch and execute task - task := runner.fetchTask(t) - runner.execTask(t, task, tc.outcome) + // fetch and execute tasks + for jobIndex, outcome := range tc.outcome { + task := runner.fetchTask(t) + runner.execTask(t, task, outcome) - // check whether the log file exists - logFileName := fmt.Sprintf("%s/%02x/%d.log", repo.FullName(), task.Id%256, task.Id) - if setting.Actions.LogCompression.IsZstd() { - logFileName += ".zst" - } - _, err := storage.Actions.Stat(logFileName) - assert.NoError(t, err) + // check whether the log file exists + logFileName := fmt.Sprintf("%s/%02x/%d.log", repo.FullName(), task.Id%256, task.Id) + if setting.Actions.LogCompression.IsZstd() { + logFileName += ".zst" + } + _, err := storage.Actions.Stat(logFileName) + assert.NoError(t, err) - // download task logs and check content - runIndex := task.Context.GetFields()["run_number"].GetStringValue() - req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/0/logs", user2.Name, repo.Name, runIndex)). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n") - assert.Len(t, logTextLines, len(tc.outcome.logRows)) - for idx, lr := range tc.outcome.logRows { - assert.Equal( - t, - fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), - logTextLines[idx], - ) - } + // download task logs and check content + runIndex := task.Context.GetFields()["run_number"].GetStringValue() + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, repo.Name, runIndex, jobIndex)). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n") + assert.Len(t, logTextLines, len(outcome.logRows)) + for idx, lr := range outcome.logRows { + assert.Equal( + t, + fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), + logTextLines[idx], + ) + } + + runID, _ := strconv.ParseInt(task.Context.GetFields()["run_id"].GetStringValue(), 10, 64) + jobs, err := actions_model.GetRunJobsByRunID(t.Context(), runID) + assert.NoError(t, err) + assert.Len(t, jobs, len(tc.outcome)) + jobID := jobs[jobIndex].ID + + // download task logs from API and check content + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, jobID)). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + logTextLines = strings.Split(strings.TrimSpace(resp.Body.String()), "\n") + assert.Len(t, logTextLines, len(outcome.logRows)) + for idx, lr := range outcome.logRows { + assert.Equal( + t, + fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), + logTextLines[idx], + ) + } + } resetFunc() }) } - - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - doAPIDeleteRepository(httpContext)(t) }) } diff --git a/tests/integration/actions_runner_modify_test.go b/tests/integration/actions_runner_modify_test.go new file mode 100644 index 0000000000..7d711bae49 --- /dev/null +++ b/tests/integration/actions_runner_modify_test.go @@ -0,0 +1,150 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + 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/tests" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestActionsRunnerModify(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + ctx := t.Context() + + require.NoError(t, db.DeleteAllRecords("action_runner")) + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + _ = actions_model.CreateRunner(ctx, &actions_model.ActionRunner{OwnerID: user2.ID, Name: "user2-runner", TokenHash: "a", UUID: "a"}) + user2Runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{OwnerID: user2.ID, Name: "user2-runner"}) + userWebURL := "/user/settings/actions/runners" + + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) + require.NoError(t, actions_model.CreateRunner(ctx, &actions_model.ActionRunner{OwnerID: org3.ID, Name: "org3-runner", TokenHash: "b", UUID: "b"})) + org3Runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{OwnerID: org3.ID, Name: "org3-runner"}) + orgWebURL := "/org/org3/settings/actions/runners" + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + _ = actions_model.CreateRunner(ctx, &actions_model.ActionRunner{RepoID: repo1.ID, Name: "repo1-runner", TokenHash: "c", UUID: "c"}) + repo1Runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{RepoID: repo1.ID, Name: "repo1-runner"}) + repoWebURL := "/user2/repo1/settings/actions/runners" + + _ = actions_model.CreateRunner(ctx, &actions_model.ActionRunner{Name: "global-runner", TokenHash: "d", UUID: "d"}) + globalRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{Name: "global-runner"}) + adminWebURL := "/-/admin/actions/runners" + + sessionAdmin := loginUser(t, "user1") + sessionUser2 := loginUser(t, user2.Name) + + doUpdate := func(t *testing.T, sess *TestSession, baseURL string, id int64, description string, expectedStatus int) { + req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d", baseURL, id), map[string]string{ + "_csrf": GetUserCSRFToken(t, sess), + "description": description, + }) + sess.MakeRequest(t, req, expectedStatus) + } + + doDelete := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) { + req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id), map[string]string{ + "_csrf": GetUserCSRFToken(t, sess), + }) + sess.MakeRequest(t, req, expectedStatus) + } + + assertDenied := func(t *testing.T, sess *TestSession, baseURL string, id int64) { + doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusNotFound) + doDelete(t, sess, baseURL, id, http.StatusNotFound) + v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) + assert.Empty(t, v.Description) + } + + assertSuccess := func(t *testing.T, sess *TestSession, baseURL string, id int64) { + doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusSeeOther) + v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) + assert.Equal(t, "ChangedDescription", v.Description) + doDelete(t, sess, baseURL, id, http.StatusOK) + unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: id}) + } + + t.Run("UpdateUserRunner", func(t *testing.T) { + theRunner := user2Runner + t.Run("FromOrg", func(t *testing.T) { + assertDenied(t, sessionAdmin, orgWebURL, theRunner.ID) + }) + t.Run("FromRepo", func(t *testing.T) { + assertDenied(t, sessionAdmin, repoWebURL, theRunner.ID) + }) + t.Run("FromAdmin", func(t *testing.T) { + t.Skip("Admin can update any runner (not right but not too bad)") + assertDenied(t, sessionAdmin, adminWebURL, theRunner.ID) + }) + }) + + t.Run("UpdateOrgRunner", func(t *testing.T) { + theRunner := org3Runner + t.Run("FromRepo", func(t *testing.T) { + assertDenied(t, sessionAdmin, repoWebURL, theRunner.ID) + }) + t.Run("FromUser", func(t *testing.T) { + assertDenied(t, sessionAdmin, userWebURL, theRunner.ID) + }) + t.Run("FromAdmin", func(t *testing.T) { + t.Skip("Admin can update any runner (not right but not too bad)") + assertDenied(t, sessionAdmin, adminWebURL, theRunner.ID) + }) + }) + + t.Run("UpdateRepoRunner", func(t *testing.T) { + theRunner := repo1Runner + t.Run("FromOrg", func(t *testing.T) { + assertDenied(t, sessionAdmin, orgWebURL, theRunner.ID) + }) + t.Run("FromUser", func(t *testing.T) { + assertDenied(t, sessionAdmin, userWebURL, theRunner.ID) + }) + t.Run("FromAdmin", func(t *testing.T) { + t.Skip("Admin can update any runner (not right but not too bad)") + assertDenied(t, sessionAdmin, adminWebURL, theRunner.ID) + }) + }) + + t.Run("UpdateGlobalRunner", func(t *testing.T) { + theRunner := globalRunner + t.Run("FromOrg", func(t *testing.T) { + assertDenied(t, sessionAdmin, orgWebURL, theRunner.ID) + }) + t.Run("FromUser", func(t *testing.T) { + assertDenied(t, sessionAdmin, userWebURL, theRunner.ID) + }) + t.Run("FromRepo", func(t *testing.T) { + assertDenied(t, sessionAdmin, repoWebURL, theRunner.ID) + }) + }) + + t.Run("UpdateSuccess", func(t *testing.T) { + t.Run("User", func(t *testing.T) { + assertSuccess(t, sessionUser2, userWebURL, user2Runner.ID) + }) + t.Run("Org", func(t *testing.T) { + assertSuccess(t, sessionAdmin, orgWebURL, org3Runner.ID) + }) + t.Run("Repo", func(t *testing.T) { + assertSuccess(t, sessionUser2, repoWebURL, repo1Runner.ID) + }) + t.Run("Admin", func(t *testing.T) { + assertSuccess(t, sessionAdmin, adminWebURL, globalRunner.ID) + }) + }) +} diff --git a/tests/integration/actions_runner_test.go b/tests/integration/actions_runner_test.go index 355ea1705e..6cc5a10e0f 100644 --- a/tests/integration/actions_runner_test.go +++ b/tests/integration/actions_runner_test.go @@ -37,7 +37,7 @@ func newMockRunner() *mockRunner { } func newMockRunnerClient(uuid, token string) *mockRunnerClient { - baseURL := fmt.Sprintf("%sapi/actions", setting.AppURL) + baseURL := setting.AppURL + "api/actions" opt := connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc { return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { @@ -60,26 +60,27 @@ func newMockRunnerClient(uuid, token string) *mockRunnerClient { } func (r *mockRunner) doPing(t *testing.T) { - resp, err := r.client.pingServiceClient.Ping(context.Background(), connect.NewRequest(&pingv1.PingRequest{ + resp, err := r.client.pingServiceClient.Ping(t.Context(), connect.NewRequest(&pingv1.PingRequest{ Data: "mock-runner", })) assert.NoError(t, err) assert.Equal(t, "Hello, mock-runner!", resp.Msg.Data) } -func (r *mockRunner) doRegister(t *testing.T, name, token string, labels []string) { +func (r *mockRunner) doRegister(t *testing.T, name, token string, labels []string, ephemeral bool) { r.doPing(t) - resp, err := r.client.runnerServiceClient.Register(context.Background(), connect.NewRequest(&runnerv1.RegisterRequest{ - Name: name, - Token: token, - Version: "mock-runner-version", - Labels: labels, + resp, err := r.client.runnerServiceClient.Register(t.Context(), connect.NewRequest(&runnerv1.RegisterRequest{ + Name: name, + Token: token, + Version: "mock-runner-version", + Labels: labels, + Ephemeral: ephemeral, })) assert.NoError(t, err) r.client = newMockRunnerClient(resp.Msg.Runner.Uuid, resp.Msg.Runner.Token) } -func (r *mockRunner) registerAsRepoRunner(t *testing.T, ownerName, repoName, runnerName string, labels []string) { +func (r *mockRunner) registerAsRepoRunner(t *testing.T, ownerName, repoName, runnerName string, labels []string, ephemeral bool) { session := loginUser(t, ownerName) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/registration-token", ownerName, repoName)).AddTokenAuth(token) @@ -88,7 +89,7 @@ func (r *mockRunner) registerAsRepoRunner(t *testing.T, ownerName, repoName, run Token string `json:"token"` } DecodeJSON(t, resp, ®istrationToken) - r.doRegister(t, runnerName, registrationToken.Token, labels) + r.doRegister(t, runnerName, registrationToken.Token, labels, ephemeral) } func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1.Task { @@ -99,7 +100,7 @@ func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1 ddl := time.Now().Add(fetchTimeout) var task *runnerv1.Task for time.Now().Before(ddl) { - resp, err := r.client.runnerServiceClient.FetchTask(context.Background(), connect.NewRequest(&runnerv1.FetchTaskRequest{ + resp, err := r.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{ TasksVersion: 0, })) assert.NoError(t, err) @@ -114,15 +115,14 @@ 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) { for idx, lr := range outcome.logRows { - resp, err := r.client.runnerServiceClient.UpdateLog(context.Background(), connect.NewRequest(&runnerv1.UpdateLogRequest{ + resp, err := r.client.runnerServiceClient.UpdateLog(t.Context(), connect.NewRequest(&runnerv1.UpdateLogRequest{ TaskId: task.Id, Index: int64(idx), Rows: []*runnerv1.LogRow{lr}, @@ -133,7 +133,7 @@ func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, outcome *mockTa } sentOutputKeys := make([]string, 0, len(outcome.outputs)) for outputKey, outputValue := range outcome.outputs { - resp, err := r.client.runnerServiceClient.UpdateTask(context.Background(), connect.NewRequest(&runnerv1.UpdateTaskRequest{ + resp, err := r.client.runnerServiceClient.UpdateTask(t.Context(), connect.NewRequest(&runnerv1.UpdateTaskRequest{ State: &runnerv1.TaskState{ Id: task.Id, Result: runnerv1.Result_RESULT_UNSPECIFIED, @@ -144,8 +144,7 @@ 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(context.Background(), connect.NewRequest(&runnerv1.UpdateTaskRequest{ + resp, err := r.client.runnerServiceClient.UpdateTask(t.Context(), connect.NewRequest(&runnerv1.UpdateTaskRequest{ State: &runnerv1.TaskState{ Id: task.Id, Result: outcome.result, diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 2c76aa826f..088491d570 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -4,7 +4,9 @@ package integration import ( + "encoding/base64" "fmt" + "net/http" "net/url" "strings" "testing" @@ -20,12 +22,15 @@ 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" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" release_service "code.gitea.io/gitea/services/release" @@ -72,21 +77,31 @@ func TestPullRequestTargetEvent(t *testing.T) { addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, baseRepo, user2, &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ { - Operation: "create", - TreePath: ".gitea/workflows/pr.yml", - ContentReader: strings.NewReader("name: test\non:\n pull_request_target:\n paths:\n - 'file_*.txt'\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"), + Operation: "create", + TreePath: ".gitea/workflows/pr.yml", + ContentReader: strings.NewReader(`name: test +on: + pull_request_target: + paths: + - 'file_*.txt' +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), }, }, Message: "add workflow", OldBranch: "main", NewBranch: "main", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -109,12 +124,12 @@ func TestPullRequestTargetEvent(t *testing.T) { OldBranch: "main", NewBranch: "fork-branch-1", Author: &files_service.IdentityOptions{ - Name: user4.Name, - Email: user4.Email, + GitUserName: user4.Name, + GitUserEmail: user4.Email, }, Committer: &files_service.IdentityOptions{ - Name: user4.Name, - Email: user4.Email, + GitUserName: user4.Name, + GitUserEmail: user4.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -164,12 +179,12 @@ func TestPullRequestTargetEvent(t *testing.T) { OldBranch: "main", NewBranch: "fork-branch-2", Author: &files_service.IdentityOptions{ - Name: user4.Name, - Email: user4.Email, + GitUserName: user4.Name, + GitUserEmail: user4.Email, }, Committer: &files_service.IdentityOptions{ - Name: user4.Name, - Email: user4.Email, + GitUserName: user4.Name, + GitUserEmail: user4.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -228,21 +243,31 @@ func TestSkipCI(t *testing.T) { addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ { - Operation: "create", - TreePath: ".gitea/workflows/pr.yml", - ContentReader: strings.NewReader("name: test\non:\n push:\n branches: [master]\n pull_request:\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"), + Operation: "create", + TreePath: ".gitea/workflows/pr.yml", + ContentReader: strings.NewReader(`name: test +on: + push: + branches: [master] + pull_request: +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), }, }, Message: "add workflow", OldBranch: "master", NewBranch: "master", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -264,16 +289,16 @@ func TestSkipCI(t *testing.T) { ContentReader: strings.NewReader("bar"), }, }, - Message: fmt.Sprintf("%s add bar", setting.Actions.SkipWorkflowStrings[0]), + Message: setting.Actions.SkipWorkflowStrings[0] + " add bar", OldBranch: "master", NewBranch: "master", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -299,12 +324,12 @@ func TestSkipCI(t *testing.T) { OldBranch: "master", NewBranch: "test-skip-ci", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -347,21 +372,29 @@ func TestCreateDeleteRefEvent(t *testing.T) { addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ { - Operation: "create", - TreePath: ".gitea/workflows/createdelete.yml", - ContentReader: strings.NewReader("name: test\non:\n [create,delete]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"), + Operation: "create", + TreePath: ".gitea/workflows/createdelete.yml", + ContentReader: strings.NewReader(`name: test +on: + [create,delete] +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), }, }, Message: "add workflow", OldBranch: "main", NewBranch: "main", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -461,21 +494,30 @@ func TestPullRequestCommitStatusEvent(t *testing.T) { addWorkflow, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ { - Operation: "create", - TreePath: ".gitea/workflows/pr.yml", - ContentReader: strings.NewReader("name: test\non:\n pull_request:\n types: [assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, milestoned, demilestoned, review_requested, review_request_removed]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"), + Operation: "create", + TreePath: ".gitea/workflows/pr.yml", + ContentReader: strings.NewReader(`name: test +on: + pull_request: + types: [assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, milestoned, demilestoned, review_requested, review_request_removed] +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), }, }, Message: "add workflow", OldBranch: "main", NewBranch: "main", Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -576,12 +618,12 @@ func TestPullRequestCommitStatusEvent(t *testing.T) { OldBranch: testBranch, NewBranch: testBranch, Author: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Committer: &files_service.IdentityOptions{ - Name: user2.Name, - Email: user2.Email, + GitUserName: user2.Name, + GitUserEmail: user2.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -592,12 +634,12 @@ func TestPullRequestCommitStatusEvent(t *testing.T) { 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 } @@ -635,19 +677,930 @@ func TestPullRequestCommitStatusEvent(t *testing.T) { } 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, }) assert.NoError(t, err) } + +func TestWorkflowDispatchPublicApi(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-event", + Description: "test workflow-dispatch ci event", + 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/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + 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) + values := url.Values{} + values.Set("ref", "main") + req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + }) +} + +func TestWorkflowDispatchPublicApiWithInputs(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-event", + Description: "test workflow-dispatch ci event", + 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/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + 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) + values := url.Values{} + values.Set("ref", "main") + values.Set("inputs[myinput]", "val0") + values.Set("inputs[myinput3]", "true") + req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + dispatchPayload := &api.WorkflowDispatchPayload{} + err = json.Unmarshal([]byte(run.EventPayload), dispatchPayload) + assert.NoError(t, err) + assert.Contains(t, dispatchPayload.Inputs, "myinput") + assert.Contains(t, dispatchPayload.Inputs, "myinput2") + assert.Contains(t, dispatchPayload.Inputs, "myinput3") + assert.Equal(t, "val0", dispatchPayload.Inputs["myinput"]) + assert.Equal(t, "def2", dispatchPayload.Inputs["myinput2"]) + assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) + }) +} + +func TestWorkflowDispatchPublicApiJSON(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-event", + Description: "test workflow-dispatch ci event", + 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/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + 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) + inputs := &api.CreateActionWorkflowDispatch{ + Ref: "main", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + }) +} + +func TestWorkflowDispatchPublicApiWithInputsJSON(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-event", + Description: "test workflow-dispatch ci event", + 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/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + 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) + inputs := &api.CreateActionWorkflowDispatch{ + Ref: "main", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + dispatchPayload := &api.WorkflowDispatchPayload{} + err = json.Unmarshal([]byte(run.EventPayload), dispatchPayload) + assert.NoError(t, err) + assert.Contains(t, dispatchPayload.Inputs, "myinput") + assert.Contains(t, dispatchPayload.Inputs, "myinput2") + assert.Contains(t, dispatchPayload.Inputs, "myinput3") + assert.Equal(t, "val0", dispatchPayload.Inputs["myinput"]) + assert.Equal(t, "def2", dispatchPayload.Inputs["myinput2"]) + assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) + }) +} + +func TestWorkflowDispatchPublicApiWithInputsNonDefaultBranchJSON(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-event", + Description: "test workflow-dispatch ci event", + 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/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + 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) + + // add workflow file to the repo + addWorkflowToBaseResp, err = files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "update", + TreePath: ".gitea/workflows/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "dispatch", + 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 dispatch branch + gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo) + assert.NoError(t, err) + defer gitRepo.Close() + commit, err := gitRepo.GetBranchCommit("dispatch") + assert.NoError(t, err) + inputs := &api.CreateActionWorkflowDispatch{ + Ref: "refs/heads/dispatch", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + 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", + CommitSHA: commit.ID.String(), + }) + assert.NotNil(t, run) + dispatchPayload := &api.WorkflowDispatchPayload{} + err = json.Unmarshal([]byte(run.EventPayload), dispatchPayload) + assert.NoError(t, err) + assert.Contains(t, dispatchPayload.Inputs, "myinput") + assert.Contains(t, dispatchPayload.Inputs, "myinput2") + assert.Contains(t, dispatchPayload.Inputs, "myinput3") + assert.Equal(t, "val0", dispatchPayload.Inputs["myinput"]) + assert.Equal(t, "def2", dispatchPayload.Inputs["myinput2"]) + assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) + }) +} + +func TestWorkflowApi(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-api", + Description: "test workflow apis", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + workflows := &api.ActionWorkflowResponse{} + json.NewDecoder(resp.Body).Decode(workflows) + assert.Empty(t, workflows.Workflows) + + // 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/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + 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) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + json.NewDecoder(resp.Body).Decode(workflows) + assert.Len(t, workflows.Workflows, 1) + assert.Equal(t, "dispatch.yml", workflows.Workflows[0].Name) + assert.Equal(t, ".gitea/workflows/dispatch.yml", workflows.Workflows[0].Path) + assert.Equal(t, ".gitea/workflows/dispatch.yml", workflows.Workflows[0].Path) + assert.Equal(t, "active", workflows.Workflows[0].State) + + // Use a hardcoded api path + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/%s", repo.FullName(), workflows.Workflows[0].ID)). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + workflow := &api.ActionWorkflow{} + json.NewDecoder(resp.Body).Decode(workflow) + assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) + assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) + assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) + assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) + assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) + assert.Equal(t, workflows.Workflows[0].State, workflow.State) + + // Use the provided url instead of the hardcoded one + req = NewRequest(t, "GET", workflows.Workflows[0].URL). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + workflow = &api.ActionWorkflow{} + json.NewDecoder(resp.Body).Decode(workflow) + assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) + assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) + assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) + assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) + assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) + assert.Equal(t, workflows.Workflows[0].State, workflow.State) + + // Disable the workflow + req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/disable"). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + // Use the provided url instead of the hardcoded one + req = NewRequest(t, "GET", workflows.Workflows[0].URL). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + workflow = &api.ActionWorkflow{} + json.NewDecoder(resp.Body).Decode(workflow) + assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) + assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) + assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) + assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) + assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) + assert.Equal(t, "disabled_manually", workflow.State) + + inputs := &api.CreateActionWorkflowDispatch{ + Ref: "main", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + // Since the workflow is disabled, so the response code is 403 forbidden + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusForbidden) + + // Enable the workflow again + req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/enable"). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + // Use the provided url instead of the hardcoded one + req = NewRequest(t, "GET", workflows.Workflows[0].URL). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + workflow = &api.ActionWorkflow{} + json.NewDecoder(resp.Body).Decode(workflow) + assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) + assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) + assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) + assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) + assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) + assert.Equal(t, workflows.Workflows[0].State, workflow.State) + + req = NewRequest(t, "GET", workflows.Workflows[0].URL). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + workflow = &api.ActionWorkflow{} + json.NewDecoder(resp.Body).Decode(workflow) + assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) + assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) + assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) + assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) + assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) + assert.Equal(t, workflows.Workflows[0].State, workflow.State) + + // 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) + inputs = &api.CreateActionWorkflowDispatch{ + Ref: "main", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + dispatchPayload := &api.WorkflowDispatchPayload{} + err = json.Unmarshal([]byte(run.EventPayload), dispatchPayload) + assert.NoError(t, err) + assert.Contains(t, dispatchPayload.Inputs, "myinput") + assert.Contains(t, dispatchPayload.Inputs, "myinput2") + assert.Contains(t, dispatchPayload.Inputs, "myinput3") + assert.Equal(t, "val0", dispatchPayload.Inputs["myinput"]) + assert.Equal(t, "def2", dispatchPayload.Inputs["myinput2"]) + assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) + }) +} + +func TestClosePullRequestWithPath(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + // user2 is the owner of the base repo + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2Token := getTokenForLoggedInUser(t, loginUser(t, user2.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + // user4 is the owner of the fork repo + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user4Token := getTokenForLoggedInUser(t, loginUser(t, user4.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + // create the base repo + apiBaseRepo := createActionsTestRepo(t, user2Token, "close-pull-request-with-path", false) + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + + // init the workflow + wfTreePath := ".gitea/workflows/pull.yml" + wfFileContent := `name: Pull Request +on: + pull_request: + types: + - closed + paths: + - 'app/**' +jobs: + echo: + runs-on: ubuntu-latest + steps: + - run: echo 'Hello World' +` + opts1 := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts1) + + // user4 forks the repo + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name), + &api.CreateForkOption{ + Name: util.ToPointer("close-pull-request-with-path-fork"), + }).AddTokenAuth(user4Token) + resp := MakeRequest(t, req, http.StatusAccepted) + var apiForkRepo api.Repository + DecodeJSON(t, resp, &apiForkRepo) + forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID}) + user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository) + + // user4 creates a pull request to add file "app/main.go" + doAPICreateFile(user4APICtx, "app/main.go", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: "user4/add-main", + Message: "create main.go", + Author: api.Identity{ + Name: user4.Name, + Email: user4.Email, + }, + Committer: api.Identity{ + Name: user4.Name, + Email: user4.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("// main.go")), + })(t) + apiPull, err := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":user4/add-main")(t) + assert.NoError(t, err) + + doAPIMergePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, apiPull.Index)(t) + + pullRequest := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID}) + + // load and compare ActionRun + assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: baseRepo.ID})) + actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID}) + assert.Equal(t, actions_module.GithubEventPullRequest, actionRun.TriggerEvent) + 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/actions_variables_test.go b/tests/integration/actions_variables_test.go new file mode 100644 index 0000000000..c0d0bd371b --- /dev/null +++ b/tests/integration/actions_variables_test.go @@ -0,0 +1,148 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + 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/tests" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestActionsVariables(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + ctx := t.Context() + + require.NoError(t, db.DeleteAllRecords("action_variable")) + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + _, _ = actions_model.InsertVariable(ctx, user2.ID, 0, "VAR", "user2-var", "user2-var-description") + user2Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{OwnerID: user2.ID, Name: "VAR"}) + userWebURL := "/user/settings/actions/variables" + + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) + _, _ = actions_model.InsertVariable(ctx, org3.ID, 0, "VAR", "org3-var", "org3-var-description") + org3Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{OwnerID: org3.ID, Name: "VAR"}) + orgWebURL := "/org/org3/settings/actions/variables" + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + _, _ = actions_model.InsertVariable(ctx, 0, repo1.ID, "VAR", "repo1-var", "repo1-var-description") + repo1Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{RepoID: repo1.ID, Name: "VAR"}) + repoWebURL := "/user2/repo1/settings/actions/variables" + + _, _ = actions_model.InsertVariable(ctx, 0, 0, "VAR", "global-var", "global-var-description") + globalVar := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{Name: "VAR", Data: "global-var"}) + adminWebURL := "/-/admin/actions/variables" + + sessionAdmin := loginUser(t, "user1") + sessionUser2 := loginUser(t, user2.Name) + + doUpdate := func(t *testing.T, sess *TestSession, baseURL string, id int64, data string, expectedStatus int) { + req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/edit", baseURL, id), map[string]string{ + "_csrf": GetUserCSRFToken(t, sess), + "name": "VAR", + "data": data, + }) + sess.MakeRequest(t, req, expectedStatus) + } + + doDelete := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) { + req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id), map[string]string{ + "_csrf": GetUserCSRFToken(t, sess), + }) + sess.MakeRequest(t, req, expectedStatus) + } + + assertDenied := func(t *testing.T, sess *TestSession, baseURL string, id int64) { + doUpdate(t, sess, baseURL, id, "ChangedData", http.StatusNotFound) + doDelete(t, sess, baseURL, id, http.StatusNotFound) + v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{ID: id}) + assert.Contains(t, v.Data, "-var") + } + + assertSuccess := func(t *testing.T, sess *TestSession, baseURL string, id int64) { + doUpdate(t, sess, baseURL, id, "ChangedData", http.StatusOK) + v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{ID: id}) + assert.Equal(t, "ChangedData", v.Data) + doDelete(t, sess, baseURL, id, http.StatusOK) + unittest.AssertNotExistsBean(t, &actions_model.ActionVariable{ID: id}) + } + + t.Run("UpdateUserVar", func(t *testing.T) { + theVar := user2Var + t.Run("FromOrg", func(t *testing.T) { + assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) + }) + t.Run("FromRepo", func(t *testing.T) { + assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) + }) + t.Run("FromAdmin", func(t *testing.T) { + assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) + }) + }) + + t.Run("UpdateOrgVar", func(t *testing.T) { + theVar := org3Var + t.Run("FromRepo", func(t *testing.T) { + assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) + }) + t.Run("FromUser", func(t *testing.T) { + assertDenied(t, sessionAdmin, userWebURL, theVar.ID) + }) + t.Run("FromAdmin", func(t *testing.T) { + assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) + }) + }) + + t.Run("UpdateRepoVar", func(t *testing.T) { + theVar := repo1Var + t.Run("FromOrg", func(t *testing.T) { + assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) + }) + t.Run("FromUser", func(t *testing.T) { + assertDenied(t, sessionAdmin, userWebURL, theVar.ID) + }) + t.Run("FromAdmin", func(t *testing.T) { + assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) + }) + }) + + t.Run("UpdateGlobalVar", func(t *testing.T) { + theVar := globalVar + t.Run("FromOrg", func(t *testing.T) { + assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) + }) + t.Run("FromUser", func(t *testing.T) { + assertDenied(t, sessionAdmin, userWebURL, theVar.ID) + }) + t.Run("FromRepo", func(t *testing.T) { + assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) + }) + }) + + t.Run("UpdateSuccess", func(t *testing.T) { + t.Run("User", func(t *testing.T) { + assertSuccess(t, sessionUser2, userWebURL, user2Var.ID) + }) + t.Run("Org", func(t *testing.T) { + assertSuccess(t, sessionAdmin, orgWebURL, org3Var.ID) + }) + t.Run("Repo", func(t *testing.T) { + assertSuccess(t, sessionUser2, repoWebURL, repo1Var.ID) + }) + t.Run("Admin", func(t *testing.T) { + assertSuccess(t, sessionAdmin, adminWebURL, globalVar.ID) + }) + }) +} diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go index d5d7e70bc7..95e03ab750 100644 --- a/tests/integration/admin_user_test.go +++ b/tests/integration/admin_user_test.go @@ -4,6 +4,7 @@ package integration import ( + "fmt" "net/http" "strconv" "testing" @@ -72,12 +73,37 @@ func TestAdminDeleteUser(t *testing.T) { session := loginUser(t, "user1") - csrf := GetUserCSRFToken(t, session) - req := NewRequestWithValues(t, "POST", "/-/admin/users/8/delete", map[string]string{ - "_csrf": csrf, - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - assertUserDeleted(t, 8) - unittest.CheckConsistencyFor(t, &user_model.User{}) + usersToDelete := []struct { + userID int64 + purge bool + }{ + { + userID: 2, + purge: true, + }, + { + userID: 8, + }, + } + + for _, entry := range usersToDelete { + t.Run(fmt.Sprintf("DeleteUser%d", entry.userID), func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: entry.userID}) + assert.NotNil(t, user) + + var query string + if entry.purge { + query = "?purge=true" + } + + csrf := GetUserCSRFToken(t, session) + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/-/admin/users/%d/delete%s", entry.userID, query), map[string]string{ + "_csrf": csrf, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + assertUserDeleted(t, entry.userID) + unittest.CheckConsistencyFor(t, &user_model.User{}) + }) + } } diff --git a/tests/integration/api_actions_artifact_v4_test.go b/tests/integration/api_actions_artifact_v4_test.go index 8821472801..3db8bbb82e 100644 --- a/tests/integration/api_actions_artifact_v4_test.go +++ b/tests/integration/api_actions_artifact_v4_test.go @@ -8,13 +8,20 @@ import ( "crypto/sha256" "encoding/hex" "encoding/xml" + "fmt" "io" "net/http" "strings" "testing" "time" + auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/storage" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/actions" actions_service "code.gitea.io/gitea/services/actions" @@ -334,6 +341,206 @@ func TestActionsArtifactV4DownloadSingle(t *testing.T) { assert.Equal(t, body, resp.Body.String()) } +func TestActionsArtifactV4RunDownloadSinglePublicApi(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifact can be listed and found by name + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/792/artifacts?name=artifact-v4-download", repo.FullName()), nil). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var listResp api.ActionArtifactsResponse + err := json.Unmarshal(resp.Body.Bytes(), &listResp) + assert.NoError(t, err) + assert.NotEmpty(t, listResp.Entries[0].ArchiveDownloadURL) + assert.Equal(t, "artifact-v4-download", listResp.Entries[0].Name) + + // confirm artifact blob storage url can be retrieved + req = NewRequestWithBody(t, "GET", listResp.Entries[0].ArchiveDownloadURL, nil). + AddTokenAuth(token) + + resp = MakeRequest(t, req, http.StatusFound) + + // confirm artifact can be downloaded and has expected content + req = NewRequestWithBody(t, "GET", resp.Header().Get("Location"), nil). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + + body := strings.Repeat("D", 1024) + assert.Equal(t, body, resp.Body.String()) +} + +func TestActionsArtifactV4DownloadSinglePublicApi(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifact can be listed and found by name + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts?name=artifact-v4-download", repo.FullName()), nil). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var listResp api.ActionArtifactsResponse + err := json.Unmarshal(resp.Body.Bytes(), &listResp) + assert.NoError(t, err) + assert.NotEmpty(t, listResp.Entries[0].ArchiveDownloadURL) + assert.Equal(t, "artifact-v4-download", listResp.Entries[0].Name) + + // confirm artifact blob storage url can be retrieved + req = NewRequestWithBody(t, "GET", listResp.Entries[0].ArchiveDownloadURL, nil). + AddTokenAuth(token) + + resp = MakeRequest(t, req, http.StatusFound) + + blobLocation := resp.Header().Get("Location") + + // confirm artifact can be downloaded without token and has expected content + req = NewRequestWithBody(t, "GET", blobLocation, nil) + resp = MakeRequest(t, req, http.StatusOK) + body := strings.Repeat("D", 1024) + assert.Equal(t, body, resp.Body.String()) + + // confirm artifact can not be downloaded without query + req = NewRequestWithBody(t, "GET", blobLocation, nil) + req.URL.RawQuery = "" + _ = MakeRequest(t, req, http.StatusUnauthorized) +} + +func TestActionsArtifactV4DownloadSinglePublicApiPrivateRepo(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifact can be listed and found by name + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts?name=artifact-v4-download", repo.FullName()), nil). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var listResp api.ActionArtifactsResponse + err := json.Unmarshal(resp.Body.Bytes(), &listResp) + assert.NoError(t, err) + assert.Equal(t, int64(23), listResp.Entries[0].ID) + assert.NotEmpty(t, listResp.Entries[0].ArchiveDownloadURL) + assert.Equal(t, "artifact-v4-download", listResp.Entries[0].Name) + + // confirm artifact blob storage url can be retrieved + req = NewRequestWithBody(t, "GET", listResp.Entries[0].ArchiveDownloadURL, nil). + AddTokenAuth(token) + + resp = MakeRequest(t, req, http.StatusFound) + + blobLocation := resp.Header().Get("Location") + // confirm artifact can be downloaded without token and has expected content + req = NewRequestWithBody(t, "GET", blobLocation, nil) + resp = MakeRequest(t, req, http.StatusOK) + body := strings.Repeat("D", 1024) + assert.Equal(t, body, resp.Body.String()) + + // confirm artifact can not be downloaded without query + req = NewRequestWithBody(t, "GET", blobLocation, nil) + req.URL.RawQuery = "" + _ = MakeRequest(t, req, http.StatusUnauthorized) +} + +func TestActionsArtifactV4ListAndGetPublicApi(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifact can be listed + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts", repo.FullName()), nil). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var listResp api.ActionArtifactsResponse + err := json.Unmarshal(resp.Body.Bytes(), &listResp) + assert.NoError(t, err) + + for _, artifact := range listResp.Entries { + assert.Contains(t, artifact.URL, fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), artifact.ID)) + assert.Contains(t, artifact.ArchiveDownloadURL, fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), artifact.ID)) + req = NewRequestWithBody(t, "GET", listResp.Entries[0].URL, nil). + AddTokenAuth(token) + + resp = MakeRequest(t, req, http.StatusOK) + var artifactResp api.ActionArtifact + err := json.Unmarshal(resp.Body.Bytes(), &artifactResp) + assert.NoError(t, err) + + assert.Equal(t, artifact.ID, artifactResp.ID) + assert.Equal(t, artifact.Name, artifactResp.Name) + assert.Equal(t, artifact.SizeInBytes, artifactResp.SizeInBytes) + assert.Equal(t, artifact.URL, artifactResp.URL) + assert.Equal(t, artifact.ArchiveDownloadURL, artifactResp.ArchiveDownloadURL) + } +} + +func TestActionsArtifactV4GetArtifactMismatchedRepoNotFound(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifacts of wrong repo is not visible + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) +} + +func TestActionsArtifactV4DownloadArtifactMismatchedRepoNotFound(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifacts of wrong repo is not visible + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) +} + +func TestActionsArtifactV4DownloadArtifactCorrectRepoFound(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifacts of correct repo is visible + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusFound) +} + +func TestActionsArtifactV4DownloadRawArtifactCorrectRepoMissingSignatureUnauthorized(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm cannot use the raw artifact endpoint even with a correct access token + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip/raw", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnauthorized) +} + func TestActionsArtifactV4Delete(t *testing.T) { defer prepareTestEnvActionsArtifacts(t)() @@ -350,4 +557,72 @@ func TestActionsArtifactV4Delete(t *testing.T) { var deleteResp actions.DeleteArtifactResponse protojson.Unmarshal(resp.Body.Bytes(), &deleteResp) assert.True(t, deleteResp.Ok) + + // confirm artifact is no longer accessible by GetSignedArtifactURL + req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL", toProtoJSON(&actions.GetSignedArtifactURLRequest{ + Name: "artifact-v4-download", + WorkflowRunBackendId: "792", + WorkflowJobRunBackendId: "193", + })). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNotFound) + + // confirm artifact is no longer enumerateable by ListArtifacts and returns length == 0 without error + req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts", toProtoJSON(&actions.ListArtifactsRequest{ + NameFilter: wrapperspb.String("artifact-v4-download"), + WorkflowRunBackendId: "792", + WorkflowJobRunBackendId: "193", + })).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + var listResp actions.ListArtifactsResponse + protojson.Unmarshal(resp.Body.Bytes(), &listResp) + assert.Empty(t, listResp.Artifacts) +} + +func TestActionsArtifactV4DeletePublicApi(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // confirm artifacts exists + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + // delete artifact by id + req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + // confirm artifacts has been deleted + req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) +} + +func TestActionsArtifactV4DeletePublicApiNotAllowedReadScope(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // confirm artifacts exists + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + // try delete artifact by id + req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + // confirm artifacts has not been deleted + req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) } diff --git a/tests/integration/api_actions_delete_run_test.go b/tests/integration/api_actions_delete_run_test.go new file mode 100644 index 0000000000..5b41702c57 --- /dev/null +++ b/tests/integration/api_actions_delete_run_test.go @@ -0,0 +1,98 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestAPIActionsDeleteRunCheckPermission(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + testAPIActionsDeleteRun(t, repo, token, http.StatusNotFound) +} + +func TestAPIActionsDeleteRun(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + testAPIActionsDeleteRunListArtifacts(t, repo, token, 2) + testAPIActionsDeleteRunListTasks(t, repo, token, true) + testAPIActionsDeleteRun(t, repo, token, http.StatusNoContent) + + testAPIActionsDeleteRunListArtifacts(t, repo, token, 0) + testAPIActionsDeleteRunListTasks(t, repo, token, false) + testAPIActionsDeleteRun(t, repo, token, http.StatusNotFound) +} + +func TestAPIActionsDeleteRunRunning(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793", repo.FullName())). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusBadRequest) +} + +func testAPIActionsDeleteRun(t *testing.T, repo *repo_model.Repository, token string, expected int) { + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795", repo.FullName())). + AddTokenAuth(token) + MakeRequest(t, req, expected) +} + +func testAPIActionsDeleteRunListArtifacts(t *testing.T, repo *repo_model.Repository, token string, artifacts int) { + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/artifacts", repo.FullName())). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var listResp api.ActionArtifactsResponse + err := json.Unmarshal(resp.Body.Bytes(), &listResp) + assert.NoError(t, err) + assert.Len(t, listResp.Entries, artifacts) +} + +func testAPIActionsDeleteRunListTasks(t *testing.T, repo *repo_model.Repository, token string, expected bool) { + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/tasks", repo.FullName())). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var listResp api.ActionTaskResponse + err := json.Unmarshal(resp.Body.Bytes(), &listResp) + assert.NoError(t, err) + findTask1 := false + findTask2 := false + for _, entry := range listResp.Entries { + if entry.ID == 53 { + findTask1 = true + continue + } + if entry.ID == 54 { + findTask2 = true + continue + } + } + assert.Equal(t, expected, findTask1) + assert.Equal(t, expected, findTask2) +} diff --git a/tests/integration/api_actions_runner_test.go b/tests/integration/api_actions_runner_test.go new file mode 100644 index 0000000000..fb9ba5b0c2 --- /dev/null +++ b/tests/integration/api_actions_runner_test.go @@ -0,0 +1,342 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "slices" + "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" + "github.com/stretchr/testify/require" +) + +func TestAPIActionsRunner(t *testing.T) { + t.Run("AdminRunner", testActionsRunnerAdmin) + t.Run("UserRunner", testActionsRunnerUser) + t.Run("OwnerRunner", testActionsRunnerOwner) + t.Run("RepoRunner", testActionsRunnerRepo) +} + +func testActionsRunnerAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + req := NewRequest(t, "POST", "/api/v1/admin/actions/runners/registration-token").AddTokenAuth(token) + tokenResp := MakeRequest(t, req, http.StatusOK) + var registrationToken struct { + Token string `json:"token"` + } + DecodeJSON(t, tokenResp, ®istrationToken) + assert.NotEmpty(t, registrationToken.Token) + + req = NewRequest(t, "GET", "/api/v1/admin/actions/runners").AddTokenAuth(token) + runnerListResp := MakeRequest(t, req, http.StatusOK) + runnerList := api.ActionRunnersResponse{} + DecodeJSON(t, runnerListResp, &runnerList) + + idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 }) + require.NotEqual(t, -1, idx) + expectedRunner := runnerList.Entries[idx] + assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name) + 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 all returned runners can be requested and deleted + for _, runnerEntry := range runnerList.Entries { + // Verify get the runner by id + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token) + runnerResp := MakeRequest(t, req, http.StatusOK) + + runner := api.ActionRunner{} + DecodeJSON(t, runnerResp, &runner) + + assert.Equal(t, runnerEntry.Name, runner.Name) + assert.Equal(t, runnerEntry.ID, runner.ID) + assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral) + assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels) + + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + // Verify runner deletion + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + } +} + +func testActionsRunnerUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + userUsername := "user1" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteUser) + req := NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(token) + tokenResp := MakeRequest(t, req, http.StatusOK) + var registrationToken struct { + Token string `json:"token"` + } + DecodeJSON(t, tokenResp, ®istrationToken) + assert.NotEmpty(t, registrationToken.Token) + + req = NewRequest(t, "GET", "/api/v1/user/actions/runners").AddTokenAuth(token) + runnerListResp := MakeRequest(t, req, http.StatusOK) + runnerList := api.ActionRunnersResponse{} + DecodeJSON(t, runnerListResp, &runnerList) + + assert.Len(t, runnerList.Entries, 1) + assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name) + assert.Equal(t, int64(34346), 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) + + // Verify get the runner by id + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + runnerResp := MakeRequest(t, req, http.StatusOK) + + runner := api.ActionRunner{} + DecodeJSON(t, runnerResp, &runner) + + assert.Equal(t, "runner_to_be_deleted-user", runner.Name) + assert.Equal(t, int64(34346), runner.ID) + assert.False(t, runner.Ephemeral) + assert.Len(t, runner.Labels, 2) + assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name) + assert.Equal(t, "linux", runner.Labels[1].Name) + + // Verify delete the runner by id + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + // Verify runner deletion + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) +} + +func testActionsRunnerOwner(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("GetRunner", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization) + // Verify get the runner by id with read scope + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token) + runnerResp := MakeRequest(t, req, http.StatusOK) + + runner := api.ActionRunner{} + DecodeJSON(t, runnerResp, &runner) + + assert.Equal(t, "runner_to_be_deleted-org", runner.Name) + assert.Equal(t, int64(34347), runner.ID) + assert.False(t, runner.Ephemeral) + assert.Len(t, runner.Labels, 2) + assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name) + assert.Equal(t, "linux", runner.Labels[1].Name) + }) + + t.Run("Access", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequest(t, "POST", "/api/v1/orgs/org3/actions/runners/registration-token").AddTokenAuth(token) + tokenResp := MakeRequest(t, req, http.StatusOK) + var registrationToken struct { + Token string `json:"token"` + } + DecodeJSON(t, tokenResp, ®istrationToken) + assert.NotEmpty(t, registrationToken.Token) + + req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners").AddTokenAuth(token) + runnerListResp := MakeRequest(t, req, http.StatusOK) + runnerList := api.ActionRunnersResponse{} + DecodeJSON(t, runnerListResp, &runnerList) + + 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", expectedRunner.ID)).AddTokenAuth(token) + runnerResp := MakeRequest(t, req, http.StatusOK) + + runner := api.ActionRunner{} + DecodeJSON(t, runnerResp, &runner) + + assert.Equal(t, "runner_to_be_deleted-org", runner.Name) + assert.Equal(t, int64(34347), runner.ID) + assert.False(t, runner.Ephemeral) + assert.Len(t, runner.Labels, 2) + assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name) + 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", 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", expectedRunner.ID)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("DeleteReadScopeForbidden", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization) + + // Verify delete the runner by id is forbidden with read scope + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("GetRepoScopeForbidden", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository) + // Verify get the runner by id with read scope + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("GetAdminRunner", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization) + // Verify get a runner by id of different entity is not found + // runner.EditableInContext(ownerID, repoID) false + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("DeleteAdminRunner", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization) + // Verify delete a runner by id of different entity is not found + // runner.EditableInContext(ownerID, repoID) false + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) +} + +func testActionsRunnerRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("GetRunner", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository) + // Verify get the runner by id with read scope + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token) + runnerResp := MakeRequest(t, req, http.StatusOK) + + runner := api.ActionRunner{} + DecodeJSON(t, runnerResp, &runner) + + assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name) + assert.Equal(t, int64(34348), runner.ID) + assert.False(t, runner.Ephemeral) + assert.Len(t, runner.Labels, 2) + assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name) + assert.Equal(t, "linux", runner.Labels[1].Name) + }) + + t.Run("Access", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository) + req := NewRequest(t, "POST", "/api/v1/repos/user2/repo1/actions/runners/registration-token").AddTokenAuth(token) + tokenResp := MakeRequest(t, req, http.StatusOK) + var registrationToken struct { + Token string `json:"token"` + } + DecodeJSON(t, tokenResp, ®istrationToken) + assert.NotEmpty(t, registrationToken.Token) + + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/actions/runners").AddTokenAuth(token) + runnerListResp := MakeRequest(t, req, http.StatusOK) + runnerList := api.ActionRunnersResponse{} + DecodeJSON(t, runnerListResp, &runnerList) + + assert.Len(t, runnerList.Entries, 1) + assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name) + assert.Equal(t, int64(34348), 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) + + // Verify get the runner by id + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + runnerResp := MakeRequest(t, req, http.StatusOK) + + runner := api.ActionRunner{} + DecodeJSON(t, runnerResp, &runner) + + assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name) + assert.Equal(t, int64(34348), runner.ID) + assert.False(t, runner.Ephemeral) + assert.Len(t, runner.Labels, 2) + assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name) + assert.Equal(t, "linux", runner.Labels[1].Name) + + // Verify delete the runner by id + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + // Verify runner deletion + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("DeleteReadScopeForbidden", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository) + + // Verify delete the runner by id is forbidden with read scope + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("GetOrganizationScopeForbidden", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization) + // Verify get the runner by id with read scope + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("GetAdminRunnerNotFound", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository) + // Verify get a runner by id of different entity is not found + // runner.EditableInContext(ownerID, repoID) false + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("DeleteAdminRunnerNotFound", func(t *testing.T) { + userUsername := "user2" + token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository) + // Verify delete a runner by id of different entity is not found + // runner.EditableInContext(ownerID, repoID) false + 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_activitypub_person_test.go b/tests/integration/api_activitypub_person_test.go index 42a2a09072..9bb1f2736e 100644 --- a/tests/integration/api_activitypub_person_test.go +++ b/tests/integration/api_activitypub_person_test.go @@ -4,32 +4,31 @@ package integration import ( - "context" "fmt" "net/http" "net/http/httptest" - "net/url" "testing" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/tests" ap "github.com/go-ap/activitypub" "github.com/stretchr/testify/assert" ) func TestActivityPubPerson(t *testing.T) { - setting.Federation.Enabled = true - testWebRoutes = routers.NormalRoutes() - defer func() { - setting.Federation.Enabled = false - testWebRoutes = routers.NormalRoutes() - }() + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.Federation.Enabled, true)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() + + t.Run("ExistingPerson", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - onGiteaRun(t, func(*testing.T, *url.URL) { userID := 2 username := "user2" req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/activitypub/user-id/%v", userID)) @@ -57,49 +56,26 @@ func TestActivityPubPerson(t *testing.T) { assert.NotNil(t, pubKeyPem) assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", pubKeyPem) }) -} - -func TestActivityPubMissingPerson(t *testing.T) { - setting.Federation.Enabled = true - testWebRoutes = routers.NormalRoutes() - defer func() { - setting.Federation.Enabled = false - testWebRoutes = routers.NormalRoutes() - }() - - onGiteaRun(t, func(*testing.T, *url.URL) { + t.Run("MissingPerson", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", "/api/v1/activitypub/user-id/999999999") resp := MakeRequest(t, req, http.StatusNotFound) assert.Contains(t, resp.Body.String(), "user does not exist") }) -} - -func TestActivityPubPersonInbox(t *testing.T) { - setting.Federation.Enabled = true - testWebRoutes = routers.NormalRoutes() - defer func() { - setting.Federation.Enabled = false - testWebRoutes = routers.NormalRoutes() - }() - - srv := httptest.NewServer(testWebRoutes) - defer srv.Close() + t.Run("MissingPersonInbox", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + srv := httptest.NewServer(testWebRoutes) + defer srv.Close() + defer test.MockVariableValue(&setting.AppURL, srv.URL+"/")() - onGiteaRun(t, func(*testing.T, *url.URL) { - appURL := setting.AppURL - setting.AppURL = srv.URL + "/" - defer func() { - setting.Database.LogSQL = false - setting.AppURL = appURL - }() username1 := "user1" - ctx := context.Background() + ctx := t.Context() user1, err := user_model.GetUserByName(ctx, username1) assert.NoError(t, err) - user1url := fmt.Sprintf("%s/api/v1/activitypub/user-id/1#main-key", srv.URL) + user1url := srv.URL + "/api/v1/activitypub/user-id/1#main-key" c, err := activitypub.NewClient(db.DefaultContext, user1, user1url) assert.NoError(t, err) - user2inboxurl := fmt.Sprintf("%s/api/v1/activitypub/user-id/2/inbox", srv.URL) + user2inboxurl := srv.URL + "/api/v1/activitypub/user-id/2/inbox" // Signed request succeeds resp, err := c.Post([]byte{}, user2inboxurl) diff --git a/tests/integration/api_admin_org_test.go b/tests/integration/api_admin_org_test.go index a29d0ba1d7..b2d77456c4 100644 --- a/tests/integration/api_admin_org_test.go +++ b/tests/integration/api_admin_org_test.go @@ -5,7 +5,6 @@ package integration import ( "net/http" - "net/url" "strings" "testing" @@ -19,10 +18,12 @@ import ( ) func TestAPIAdminOrgCreate(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + t.Run("CreateOrg", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() org := api.CreateOrgOption{ UserName: "user2_org", FullName: "User2's organization", @@ -51,13 +52,8 @@ func TestAPIAdminOrgCreate(t *testing.T) { FullName: org.FullName, }) }) -} - -func TestAPIAdminOrgCreateBadVisibility(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) - + t.Run("CreateBadVisibility", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() org := api.CreateOrgOption{ UserName: "user2_org", FullName: "User2's organization", @@ -70,22 +66,21 @@ func TestAPIAdminOrgCreateBadVisibility(t *testing.T) { AddTokenAuth(token) MakeRequest(t, req, http.StatusUnprocessableEntity) }) -} - -func TestAPIAdminOrgCreateNotAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - nonAdminUsername := "user2" - session := loginUser(t, nonAdminUsername) - token := getTokenForLoggedInUser(t, session) - org := api.CreateOrgOption{ - UserName: "user2_org", - FullName: "User2's organization", - Description: "This organization created by admin for user2", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "public", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) + t.Run("CreateNotAdmin", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + nonAdminUsername := "user2" + session := loginUser(t, nonAdminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + org := api.CreateOrgOption{ + UserName: "user2_org", + FullName: "User2's organization", + Description: "This organization created by admin for user2", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: "public", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) } diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 66209ee4e0..d28a103e59 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -76,7 +76,7 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { var newPublicKey api.PublicKey DecodeJSON(t, resp, &newPublicKey) - token = getUserToken(t, normalUsername) + token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll) req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) @@ -88,7 +88,7 @@ func TestAPISudoUser(t *testing.T) { normalUsername := "user2" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user?sudo=%s", normalUsername)). + req := NewRequest(t, "GET", "/api/v1/user?sudo="+normalUsername). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var user api.User @@ -103,7 +103,7 @@ func TestAPISudoUserForbidden(t *testing.T) { normalUsername := "user2" token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user?sudo=%s", adminUsername)). + req := NewRequest(t, "GET", "/api/v1/user?sudo="+adminUsername). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) } @@ -139,7 +139,7 @@ func TestAPIListUsersNotLoggedIn(t *testing.T) { func TestAPIListUsersNonAdmin(t *testing.T) { defer tests.PrepareTestEnv(t)() nonAdminUsername := "user2" - token := getUserToken(t, nonAdminUsername) + token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll) req := NewRequest(t, "GET", "/api/v1/admin/users"). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) @@ -193,7 +193,7 @@ func TestAPIEditUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") + urlStr := "/api/v1/admin/users/" + "user2" fullNameToChange := "Full Name User 2" req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ @@ -217,7 +217,7 @@ func TestAPIEditUser(t *testing.T) { errMap := make(map[string]any) json.Unmarshal(resp.Body.Bytes(), &errMap) - assert.EqualValues(t, "e-mail invalid [email: ]", errMap["message"].(string)) + assert.Equal(t, "e-mail invalid [email: ]", errMap["message"].(string)) user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"}) assert.False(t, user2.IsRestricted) @@ -374,7 +374,7 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) { adminUsername := "user1" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") + urlStr := "/api/v1/admin/users/" + "user2" newEmail := "user2@example1.com" req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index d64dd97f93..16e1f2812e 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -24,13 +24,13 @@ func testAPIGetBranch(t *testing.T, branchName string, exists bool) { AddTokenAuth(token) resp := MakeRequest(t, req, NoExpectedStatus) if !exists { - assert.EqualValues(t, http.StatusNotFound, resp.Code) + assert.Equal(t, http.StatusNotFound, resp.Code) return } - assert.EqualValues(t, http.StatusOK, resp.Code) + assert.Equal(t, http.StatusOK, resp.Code) var branch api.Branch DecodeJSON(t, resp, &branch) - assert.EqualValues(t, branchName, branch.Name) + assert.Equal(t, branchName, branch.Name) assert.True(t, branch.UserCanPush) assert.True(t, branch.UserCanMerge) } @@ -44,7 +44,7 @@ func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPSta if resp.Code == http.StatusOK { var branchProtection api.BranchProtection DecodeJSON(t, resp, &branchProtection) - assert.EqualValues(t, branchName, branchProtection.RuleName) + assert.Equal(t, branchName, branchProtection.RuleName) return &branchProtection } return nil @@ -60,7 +60,7 @@ func testAPICreateBranchProtection(t *testing.T, branchName string, expectedPrio if resp.Code == http.StatusCreated { var branchProtection api.BranchProtection DecodeJSON(t, resp, &branchProtection) - assert.EqualValues(t, branchName, branchProtection.RuleName) + assert.Equal(t, branchName, branchProtection.RuleName) assert.EqualValues(t, expectedPriority, branchProtection.Priority) } } @@ -74,7 +74,7 @@ func testAPIEditBranchProtection(t *testing.T, branchName string, body *api.Bran if resp.Code == http.StatusOK { var branchProtection api.BranchProtection DecodeJSON(t, resp, &branchProtection) - assert.EqualValues(t, branchName, branchProtection.RuleName) + assert.Equal(t, branchName, branchProtection.RuleName) } } @@ -181,7 +181,7 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran DecodeJSON(t, resp, &branch) if resp.Result().StatusCode == http.StatusCreated { - assert.EqualValues(t, newBranch, branch.Name) + assert.Equal(t, newBranch, branch.Name) } return resp.Result().StatusCode == status @@ -303,7 +303,7 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) { RepoID: 1, }) assert.NoError(t, err) - assert.Len(t, branches, 4) + assert.Len(t, branches, 6) // make a broke repository with no branch on database _, err = db.DeleteByBean(db.DefaultContext, git_model.Branch{RepoID: 1}) @@ -320,7 +320,7 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) { RepoID: 1, }) assert.NoError(t, err) - assert.Len(t, branches, 5) + assert.Len(t, branches, 7) branches, err = db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{ RepoID: 1, diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index 255b8332b2..9842c358f6 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -106,7 +106,7 @@ func TestAPICreateComment(t *testing.T) { var updatedComment api.Comment DecodeJSON(t, resp, &updatedComment) - assert.EqualValues(t, commentBody, updatedComment.Body) + assert.Equal(t, commentBody, updatedComment.Body) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody}) t.Run("BlockedByRepoOwner", func(t *testing.T) { @@ -174,7 +174,7 @@ func TestAPIGetSystemUserComment(t *testing.T) { user_model.NewGhostUser(), user_model.NewActionsUser(), } { - body := fmt.Sprintf("Hello %s", systemUser.Name) + body := "Hello " + systemUser.Name comment, err := issues_model.CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypeComment, Doer: systemUser, @@ -233,8 +233,8 @@ func TestAPIEditComment(t *testing.T) { var updatedComment api.Comment DecodeJSON(t, resp, &updatedComment) - assert.EqualValues(t, comment.ID, updatedComment.ID) - assert.EqualValues(t, newCommentBody, updatedComment.Body) + assert.Equal(t, comment.ID, updatedComment.ID) + assert.Equal(t, newCommentBody, updatedComment.Body) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody}) } diff --git a/tests/integration/api_fork_test.go b/tests/integration/api_fork_test.go index 580bb459e7..2837c3b93e 100644 --- a/tests/integration/api_fork_test.go +++ b/tests/integration/api_fork_test.go @@ -10,6 +10,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" @@ -33,7 +34,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { // fork into a limited org limitedOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22}) - assert.EqualValues(t, api.VisibleTypeLimited, limitedOrg.Visibility) + assert.Equal(t, api.VisibleTypeLimited, limitedOrg.Visibility) ownerTeam1, err := org_model.OrgFromUser(limitedOrg).GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) @@ -48,7 +49,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { user4Sess := loginUser(t, "user4") user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user4"}) privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) - assert.EqualValues(t, api.VisibleTypePrivate, privateOrg.Visibility) + assert.Equal(t, api.VisibleTypePrivate, privateOrg.Visibility) ownerTeam2, err := org_model.OrgFromUser(privateOrg).GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) @@ -69,7 +70,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { DecodeJSON(t, resp, &forks) assert.Empty(t, forks) - assert.EqualValues(t, "0", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "0", resp.Header().Get("X-Total-Count")) }) t.Run("Logged in", func(t *testing.T) { @@ -81,8 +82,8 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { var forks []*api.Repository DecodeJSON(t, resp, &forks) - assert.Len(t, forks, 1) - assert.EqualValues(t, "1", resp.Header().Get("X-Total-Count")) + assert.Len(t, forks, 2) + assert.Equal(t, "2", resp.Header().Get("X-Total-Count")) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user1)) @@ -93,6 +94,34 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { DecodeJSON(t, resp, &forks) assert.Len(t, forks, 2) - assert.EqualValues(t, "2", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "2", resp.Header().Get("X-Total-Count")) }) } + +func TestGetPrivateReposForks(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user1Sess := loginUser(t, "user1") + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) // private repository + privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) + user1Token := getTokenForLoggedInUser(t, user1Sess, auth_model.AccessTokenScopeWriteRepository) + + forkedRepoName := "forked-repo" + // create fork from a private repository + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+repo2.FullName()+"/forks", &api.CreateForkOption{ + Organization: &privateOrg.Name, + Name: &forkedRepoName, + }).AddTokenAuth(user1Token) + MakeRequest(t, req, http.StatusAccepted) + + // test get a private fork without clear permissions + req = NewRequest(t, "GET", "/api/v1/repos/"+repo2.FullName()+"/forks").AddTokenAuth(user1Token) + resp := MakeRequest(t, req, http.StatusOK) + + forks := []*api.Repository{} + DecodeJSON(t, resp, &forks) + assert.Len(t, forks, 1) + assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "forked-repo", forks[0].Name) + assert.Equal(t, privateOrg.Name, forks[0].Owner.UserName) +} diff --git a/tests/integration/api_gitignore_templates_test.go b/tests/integration/api_gitignore_templates_test.go index c58f5eebfe..1c56d51344 100644 --- a/tests/integration/api_gitignore_templates_test.go +++ b/tests/integration/api_gitignore_templates_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -38,7 +37,7 @@ func TestAPIGetGitignoreTemplateInfo(t *testing.T) { // Use the first template for the test templateName := repo_module.Gitignores[0] - urlStr := fmt.Sprintf("/api/v1/gitignore/templates/%s", templateName) + urlStr := "/api/v1/gitignore/templates/" + templateName req := NewRequest(t, "GET", urlStr) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_gpg_keys_test.go b/tests/integration/api_gpg_keys_test.go index ec0dafc2d6..edfb9e6eca 100644 --- a/tests/integration/api_gpg_keys_test.go +++ b/tests/integration/api_gpg_keys_test.go @@ -86,13 +86,13 @@ func TestGPGKeys(t *testing.T) { assert.Len(t, keys, 1) primaryKey1 := keys[0] // Primary key 1 - assert.EqualValues(t, "38EA3BCED732982C", primaryKey1.KeyID) + assert.Equal(t, "38EA3BCED732982C", primaryKey1.KeyID) assert.Len(t, primaryKey1.Emails, 1) - assert.EqualValues(t, "user2@example.com", primaryKey1.Emails[0].Email) + assert.Equal(t, "user2@example.com", primaryKey1.Emails[0].Email) assert.True(t, primaryKey1.Emails[0].Verified) subKey := primaryKey1.SubsKey[0] // Subkey of 38EA3BCED732982C - assert.EqualValues(t, "70D7C694D17D03AD", subKey.KeyID) + assert.Equal(t, "70D7C694D17D03AD", subKey.KeyID) assert.Empty(t, subKey.Emails) var key api.GPGKey @@ -100,16 +100,16 @@ func TestGPGKeys(t *testing.T) { AddTokenAuth(tokenWithGPGKeyScope) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &key) - assert.EqualValues(t, "38EA3BCED732982C", key.KeyID) + assert.Equal(t, "38EA3BCED732982C", key.KeyID) assert.Len(t, key.Emails, 1) - assert.EqualValues(t, "user2@example.com", key.Emails[0].Email) + assert.Equal(t, "user2@example.com", key.Emails[0].Email) assert.True(t, key.Emails[0].Verified) req = NewRequest(t, "GET", "/api/v1/user/gpg_keys/"+strconv.FormatInt(subKey.ID, 10)). // Subkey of 38EA3BCED732982C AddTokenAuth(tokenWithGPGKeyScope) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &key) - assert.EqualValues(t, "70D7C694D17D03AD", key.KeyID) + assert.Equal(t, "70D7C694D17D03AD", key.KeyID) assert.Empty(t, key.Emails) }) diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index 7755b9861a..b30cdfd0fc 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -4,7 +4,6 @@ package integration import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -34,6 +33,10 @@ type APITestContext struct { func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.AccessTokenScope) APITestContext { session := loginUser(t, username) + if len(scope) == 0 { + // FIXME: legacy logic: no scope means all + scope = []auth.AccessTokenScope{auth.AccessTokenScopeAll} + } token := getTokenForLoggedInUser(t, session, scope...) return APITestContext{ Session: session, @@ -260,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), @@ -273,8 +276,8 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) } err := api.APIError{} DecodeJSON(t, resp, &err) - assert.EqualValues(t, "Please try again later", err.Message) - queue.GetManager().FlushAll(context.Background(), 5*time.Second) + assert.Equal(t, "Please try again later", err.Message) + queue.GetManager().FlushAll(t.Context(), 5*time.Second) <-time.After(1 * time.Second) } @@ -283,7 +286,7 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) expected = http.StatusOK } - if !assert.EqualValues(t, expected, resp.Code, + if !assert.Equal(t, expected, resp.Code, "Request: %s %s", req.Method, req.URL.String()) { logUnexpectedResponse(t, resp) } diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go index c9cdd46b9a..4324fd37d9 100644 --- a/tests/integration/api_issue_label_test.go +++ b/tests/integration/api_issue_label_test.go @@ -38,8 +38,8 @@ func TestAPIModifyLabels(t *testing.T) { apiLabel := new(api.Label) DecodeJSON(t, resp, &apiLabel) dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, RepoID: repo.ID}) - assert.EqualValues(t, dbLabel.Name, apiLabel.Name) - assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + assert.Equal(t, dbLabel.Name, apiLabel.Name) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ Name: "TestL 2", @@ -67,7 +67,7 @@ func TestAPIModifyLabels(t *testing.T) { AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiLabel) - assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) // EditLabel newName := "LabelNewName" @@ -79,7 +79,7 @@ func TestAPIModifyLabels(t *testing.T) { }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiLabel) - assert.EqualValues(t, newColor, apiLabel.Color) + assert.Equal(t, newColor, apiLabel.Color) req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ Color: &newColorWrong, }).AddTokenAuth(token) @@ -165,7 +165,7 @@ func TestAPIReplaceIssueLabels(t *testing.T) { var apiLabels []*api.Label DecodeJSON(t, resp, &apiLabels) if assert.Len(t, apiLabels, 1) { - assert.EqualValues(t, label.ID, apiLabels[0].ID) + assert.Equal(t, label.ID, apiLabels[0].ID) } unittest.AssertCount(t, &issues_model.IssueLabel{IssueID: issue.ID}, 1) @@ -191,7 +191,7 @@ func TestAPIReplaceIssueLabelsWithLabelNames(t *testing.T) { var apiLabels []*api.Label DecodeJSON(t, resp, &apiLabels) if assert.Len(t, apiLabels, 1) { - assert.EqualValues(t, label.Name, apiLabels[0].Name) + assert.Equal(t, label.Name, apiLabels[0].Name) } } @@ -215,8 +215,8 @@ func TestAPIModifyOrgLabels(t *testing.T) { apiLabel := new(api.Label) DecodeJSON(t, resp, &apiLabel) dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, OrgID: owner.ID}) - assert.EqualValues(t, dbLabel.Name, apiLabel.Name) - assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + assert.Equal(t, dbLabel.Name, apiLabel.Name) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ Name: "TestL 2", @@ -244,7 +244,7 @@ func TestAPIModifyOrgLabels(t *testing.T) { AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiLabel) - assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) // EditLabel newName := "LabelNewName" @@ -256,7 +256,7 @@ func TestAPIModifyOrgLabels(t *testing.T) { }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiLabel) - assert.EqualValues(t, newColor, apiLabel.Color) + assert.Equal(t, newColor, apiLabel.Color) req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ Color: &newColorWrong, }).AddTokenAuth(token) diff --git a/tests/integration/api_issue_lock_test.go b/tests/integration/api_issue_lock_test.go new file mode 100644 index 0000000000..47b1f2cf0d --- /dev/null +++ b/tests/integration/api_issue_lock_test.go @@ -0,0 +1,74 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + 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" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPILockIssue(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("Lock", func(t *testing.T) { + issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + assert.False(t, issueBefore.IsLocked) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/lock", owner.Name, repo.Name, issueBefore.Index) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + + // check lock issue + req := NewRequestWithJSON(t, "PUT", urlStr, api.LockIssueOption{Reason: "Spam"}).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + assert.True(t, issueAfter.IsLocked) + + // check with other user + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + session34 := loginUser(t, user34.Name) + token34 := getTokenForLoggedInUser(t, session34, auth_model.AccessTokenScopeAll) + req = NewRequestWithJSON(t, "PUT", urlStr, api.LockIssueOption{Reason: "Spam"}).AddTokenAuth(token34) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("Unlock", func(t *testing.T) { + issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/lock", owner.Name, repo.Name, issueBefore.Index) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + + lockReq := NewRequestWithJSON(t, "PUT", urlStr, api.LockIssueOption{Reason: "Spam"}).AddTokenAuth(token) + MakeRequest(t, lockReq, http.StatusNoContent) + + // check unlock issue + req := NewRequest(t, "DELETE", urlStr).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + assert.False(t, issueAfter.IsLocked) + + // check with other user + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + session34 := loginUser(t, user34.Name) + token34 := getTokenForLoggedInUser(t, session34, auth_model.AccessTokenScopeAll) + req = NewRequest(t, "DELETE", urlStr).AddTokenAuth(token34) + MakeRequest(t, req, http.StatusForbidden) + }) +} diff --git a/tests/integration/api_issue_milestone_test.go b/tests/integration/api_issue_milestone_test.go index 2d00752302..1196c8d358 100644 --- a/tests/integration/api_issue_milestone_test.go +++ b/tests/integration/api_issue_milestone_test.go @@ -73,7 +73,7 @@ func TestAPIIssuesMilestone(t *testing.T) { AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiMilestone) - assert.EqualValues(t, apiMilestones[2], apiMilestone) + assert.Equal(t, apiMilestones[2], apiMilestone) req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s&name=%s", owner.Name, repo.Name, "all", "milestone2")). AddTokenAuth(token) diff --git a/tests/integration/api_issue_stopwatch_test.go b/tests/integration/api_issue_stopwatch_test.go index 4765787e6f..3606d9a228 100644 --- a/tests/integration/api_issue_stopwatch_test.go +++ b/tests/integration/api_issue_stopwatch_test.go @@ -35,11 +35,11 @@ func TestAPIListStopWatches(t *testing.T) { stopwatch := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: owner.ID}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: stopwatch.IssueID}) if assert.Len(t, apiWatches, 1) { - assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) - assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex) - assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle) - assert.EqualValues(t, repo.Name, apiWatches[0].RepoName) - assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName) + assert.Equal(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) + assert.Equal(t, issue.Index, apiWatches[0].IssueIndex) + assert.Equal(t, issue.Title, apiWatches[0].IssueTitle) + assert.Equal(t, repo.Name, apiWatches[0].RepoName) + assert.Equal(t, repo.OwnerName, apiWatches[0].RepoOwnerName) assert.Positive(t, apiWatches[0].Seconds) } } diff --git a/tests/integration/api_issue_subscription_test.go b/tests/integration/api_issue_subscription_test.go index 7a716301c4..74ba171c01 100644 --- a/tests/integration/api_issue_subscription_test.go +++ b/tests/integration/api_issue_subscription_test.go @@ -43,11 +43,11 @@ func TestAPIIssueSubscriptions(t *testing.T) { wi := new(api.WatchInfo) DecodeJSON(t, resp, wi) - assert.EqualValues(t, isWatching, wi.Subscribed) - assert.EqualValues(t, !isWatching, wi.Ignored) - assert.EqualValues(t, issue.APIURL(db.DefaultContext)+"/subscriptions", wi.URL) + assert.Equal(t, isWatching, wi.Subscribed) + assert.Equal(t, !isWatching, wi.Ignored) + assert.Equal(t, issue.APIURL(db.DefaultContext)+"/subscriptions", wi.URL) assert.EqualValues(t, issue.CreatedUnix, wi.CreatedAt.Unix()) - assert.EqualValues(t, issueRepo.APIURL(), wi.RepositoryURL) + assert.Equal(t, issueRepo.APIURL(), wi.RepositoryURL) } testSubscription(issue1, true) diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index d8394a33d9..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) @@ -313,7 +310,7 @@ func TestAPISearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) assert.Len(t, apiIssues, 20) query.Add("limit", "10") @@ -321,7 +318,7 @@ func TestAPISearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) assert.Len(t, apiIssues, 10) query = url.Values{"assigned": {"true"}, "state": {"all"}} @@ -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_issue_tracked_time_test.go b/tests/integration/api_issue_tracked_time_test.go index fd2c452b20..bd562b602e 100644 --- a/tests/integration/api_issue_tracked_time_test.go +++ b/tests/integration/api_issue_tracked_time_test.go @@ -41,8 +41,8 @@ func TestAPIGetTrackedTimes(t *testing.T) { for i, time := range expect { assert.Equal(t, time.ID, apiTimes[i].ID) - assert.EqualValues(t, issue2.Title, apiTimes[i].Issue.Title) - assert.EqualValues(t, issue2.ID, apiTimes[i].IssueID) + assert.Equal(t, issue2.Title, apiTimes[i].Issue.Title) + assert.Equal(t, issue2.ID, apiTimes[i].IssueID) assert.Equal(t, time.Created.Unix(), apiTimes[i].Created.Unix()) assert.Equal(t, time.Time, apiTimes[i].Time) user, err := user_model.GetUserByID(db.DefaultContext, time.UserID) @@ -125,6 +125,6 @@ func TestAPIAddTrackedTimes(t *testing.T) { DecodeJSON(t, resp, &apiNewTime) assert.EqualValues(t, 33, apiNewTime.Time) - assert.EqualValues(t, user2.ID, apiNewTime.UserID) + assert.Equal(t, user2.ID, apiNewTime.UserID) assert.EqualValues(t, 947688818, apiNewTime.Created.Unix()) } diff --git a/tests/integration/api_keys_test.go b/tests/integration/api_keys_test.go index 2276b955cf..3162051acc 100644 --- a/tests/integration/api_keys_test.go +++ b/tests/integration/api_keys_test.go @@ -143,7 +143,7 @@ func TestCreateUserKey(t *testing.T) { }) // Search by fingerprint - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/keys?fingerprint=%s", newPublicKey.Fingerprint)). + req = NewRequest(t, "GET", "/api/v1/user/keys?fingerprint="+newPublicKey.Fingerprint). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) @@ -183,7 +183,7 @@ func TestCreateUserKey(t *testing.T) { token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteUser) // Should find key even though not ours, but we shouldn't know whose it is - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/keys?fingerprint=%s", newPublicKey.Fingerprint)). + req = NewRequest(t, "GET", "/api/v1/user/keys?fingerprint="+newPublicKey.Fingerprint). AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_label_templates_test.go b/tests/integration/api_label_templates_test.go index 007e979011..3e637daba6 100644 --- a/tests/integration/api_label_templates_test.go +++ b/tests/integration/api_label_templates_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "net/url" "strings" @@ -42,7 +41,7 @@ func TestAPIGetLabelTemplateInfo(t *testing.T) { // Use the first template for the test templateName := repo_module.LabelTemplateFiles[0].DisplayName - urlStr := fmt.Sprintf("/api/v1/label/templates/%s", url.PathEscape(templateName)) + urlStr := "/api/v1/label/templates/" + url.PathEscape(templateName) req := NewRequest(t, "GET", urlStr) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_license_templates_test.go b/tests/integration/api_license_templates_test.go index e12aab7c2c..52e240f9a7 100644 --- a/tests/integration/api_license_templates_test.go +++ b/tests/integration/api_license_templates_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "net/url" "testing" @@ -39,7 +38,7 @@ func TestAPIGetLicenseTemplateInfo(t *testing.T) { // Use the first template for the test licenseName := repo_module.Licenses[0] - urlStr := fmt.Sprintf("/api/v1/licenses/%s", url.PathEscape(licenseName)) + urlStr := "/api/v1/licenses/" + url.PathEscape(licenseName) req := NewRequest(t, "GET", urlStr) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_nodeinfo_test.go b/tests/integration/api_nodeinfo_test.go index 75f8dbb4ba..916c2f1723 100644 --- a/tests/integration/api_nodeinfo_test.go +++ b/tests/integration/api_nodeinfo_test.go @@ -5,35 +5,31 @@ package integration import ( "net/http" - "net/url" "testing" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" ) func TestNodeinfo(t *testing.T) { - setting.Federation.Enabled = true - testWebRoutes = routers.NormalRoutes() - defer func() { - setting.Federation.Enabled = false - testWebRoutes = routers.NormalRoutes() - }() + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.Federation.Enabled, true)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() - onGiteaRun(t, func(*testing.T, *url.URL) { - req := NewRequest(t, "GET", "/api/v1/nodeinfo") - resp := MakeRequest(t, req, http.StatusOK) - VerifyJSONSchema(t, resp, "nodeinfo_2.1.json") + req := NewRequest(t, "GET", "/api/v1/nodeinfo") + resp := MakeRequest(t, req, http.StatusOK) + VerifyJSONSchema(t, resp, "nodeinfo_2.1.json") - var nodeinfo api.NodeInfo - DecodeJSON(t, resp, &nodeinfo) - assert.True(t, nodeinfo.OpenRegistrations) - assert.Equal(t, "gitea", nodeinfo.Software.Name) - assert.Equal(t, 29, nodeinfo.Usage.Users.Total) - assert.Equal(t, 22, nodeinfo.Usage.LocalPosts) - assert.Equal(t, 3, nodeinfo.Usage.LocalComments) - }) + var nodeinfo api.NodeInfo + DecodeJSON(t, resp, &nodeinfo) + assert.True(t, nodeinfo.OpenRegistrations) + assert.Equal(t, "gitea", nodeinfo.Software.Name) + assert.Equal(t, 29, nodeinfo.Usage.Users.Total) + assert.Equal(t, 22, nodeinfo.Usage.LocalPosts) + assert.Equal(t, 3, nodeinfo.Usage.LocalComments) } diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go index dc4ba83ecc..e6bc142476 100644 --- a/tests/integration/api_notification_test.go +++ b/tests/integration/api_notification_test.go @@ -35,7 +35,7 @@ func TestAPINotification(t *testing.T) { // -- GET /notifications -- // test filter since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?since=%s", since)). + req := NewRequest(t, "GET", "/api/v1/notifications?since="+since). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var apiNL []api.NotificationThread @@ -104,10 +104,10 @@ func TestAPINotification(t *testing.T) { assert.EqualValues(t, 5, apiN.ID) assert.False(t, apiN.Pinned) assert.True(t, apiN.Unread) - assert.EqualValues(t, "issue4", apiN.Subject.Title) + assert.Equal(t, "issue4", apiN.Subject.Title) assert.EqualValues(t, "Issue", apiN.Subject.Type) - assert.EqualValues(t, thread5.Issue.APIURL(db.DefaultContext), apiN.Subject.URL) - assert.EqualValues(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL) + assert.Equal(t, thread5.Issue.APIURL(db.DefaultContext), apiN.Subject.URL) + assert.Equal(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL) MakeRequest(t, NewRequest(t, "GET", "/api/v1/notifications/new"), http.StatusUnauthorized) diff --git a/tests/integration/api_oauth2_apps_test.go b/tests/integration/api_oauth2_apps_test.go index 7a17b4ca88..13f64fd69e 100644 --- a/tests/integration/api_oauth2_apps_test.go +++ b/tests/integration/api_oauth2_apps_test.go @@ -43,12 +43,12 @@ func testAPICreateOAuth2Application(t *testing.T) { var createdApp *api.OAuth2Application DecodeJSON(t, resp, &createdApp) - assert.EqualValues(t, appBody.Name, createdApp.Name) + assert.Equal(t, appBody.Name, createdApp.Name) assert.Len(t, createdApp.ClientSecret, 56) assert.Len(t, createdApp.ClientID, 36) assert.True(t, createdApp.ConfidentialClient) assert.NotEmpty(t, createdApp.Created) - assert.EqualValues(t, appBody.RedirectURIs[0], createdApp.RedirectURIs[0]) + assert.Equal(t, appBody.RedirectURIs[0], createdApp.RedirectURIs[0]) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{UID: user.ID, Name: createdApp.Name}) } @@ -74,12 +74,12 @@ func testAPIListOAuth2Applications(t *testing.T) { DecodeJSON(t, resp, &appList) expectedApp := appList[0] - assert.EqualValues(t, expectedApp.Name, existApp.Name) - assert.EqualValues(t, expectedApp.ClientID, existApp.ClientID) + assert.Equal(t, expectedApp.Name, existApp.Name) + assert.Equal(t, expectedApp.ClientID, existApp.ClientID) assert.Equal(t, expectedApp.ConfidentialClient, existApp.ConfidentialClient) assert.Len(t, expectedApp.ClientID, 36) assert.Empty(t, expectedApp.ClientSecret) - assert.EqualValues(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) + assert.Equal(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } @@ -128,13 +128,13 @@ func testAPIGetOAuth2Application(t *testing.T) { DecodeJSON(t, resp, &app) expectedApp := app - assert.EqualValues(t, expectedApp.Name, existApp.Name) - assert.EqualValues(t, expectedApp.ClientID, existApp.ClientID) + assert.Equal(t, expectedApp.Name, existApp.Name) + assert.Equal(t, expectedApp.ClientID, existApp.ClientID) assert.Equal(t, expectedApp.ConfidentialClient, existApp.ConfidentialClient) assert.Len(t, expectedApp.ClientID, 36) assert.Empty(t, expectedApp.ClientSecret) assert.Len(t, expectedApp.RedirectURIs, 1) - assert.EqualValues(t, expectedApp.RedirectURIs[0], existApp.RedirectURIs[0]) + assert.Equal(t, expectedApp.RedirectURIs[0], existApp.RedirectURIs[0]) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } @@ -168,8 +168,8 @@ func testAPIUpdateOAuth2Application(t *testing.T) { expectedApp := app assert.Len(t, expectedApp.RedirectURIs, 2) - assert.EqualValues(t, expectedApp.RedirectURIs[0], appBody.RedirectURIs[0]) - assert.EqualValues(t, expectedApp.RedirectURIs[1], appBody.RedirectURIs[1]) + assert.Equal(t, expectedApp.RedirectURIs[0], appBody.RedirectURIs[0]) + assert.Equal(t, expectedApp.RedirectURIs[1], appBody.RedirectURIs[1]) assert.Equal(t, expectedApp.ConfidentialClient, appBody.ConfidentialClient) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index fff121490c..6577bd1684 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -6,7 +6,6 @@ package integration import ( "fmt" "net/http" - "net/url" "strings" "testing" @@ -19,46 +18,53 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestAPIOrgCreate(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) - - org := api.CreateOrgOption{ - UserName: "user1_org", - FullName: "User1's organization", - Description: "This organization created by user1", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "limited", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - - var apiOrg api.Organization - DecodeJSON(t, resp, &apiOrg) - - assert.Equal(t, org.UserName, apiOrg.Name) - assert.Equal(t, org.FullName, apiOrg.FullName) - assert.Equal(t, org.Description, apiOrg.Description) - assert.Equal(t, org.Website, apiOrg.Website) - assert.Equal(t, org.Location, apiOrg.Location) - assert.Equal(t, org.Visibility, apiOrg.Visibility) +func TestAPIOrgCreateRename(t *testing.T) { + defer tests.PrepareTestEnv(t)() + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) + + org := api.CreateOrgOption{ + UserName: "user1_org", + FullName: "User1's organization", + Description: "This organization created by user1", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: "limited", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, org.UserName, apiOrg.Name) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) + + unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: org.UserName, + LowerName: strings.ToLower(org.UserName), + FullName: org.FullName, + }) - unittest.AssertExistsAndLoadBean(t, &user_model.User{ - Name: org.UserName, - LowerName: strings.ToLower(org.UserName), - FullName: org.FullName, - }) + // check org name + req = NewRequestf(t, "GET", "/api/v1/orgs/%s", org.UserName).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiOrg) + assert.Equal(t, org.UserName, apiOrg.Name) + t.Run("CheckPermission", func(t *testing.T) { // Check owner team permission ownerTeam, _ := org_model.GetOwnerTeam(db.DefaultContext, apiOrg.ID) - for _, ut := range unit_model.AllRepoUnitTypes { up := perm.AccessModeOwner if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki { @@ -71,40 +77,67 @@ func TestAPIOrgCreate(t *testing.T) { AccessMode: up, }) } + }) - req = NewRequestf(t, "GET", "/api/v1/orgs/%s", org.UserName). - AddTokenAuth(token) + t.Run("CheckMembers", func(t *testing.T) { + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", org.UserName).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &apiOrg) - assert.EqualValues(t, org.UserName, apiOrg.Name) - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org.UserName). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) + // user1 on this org is public + var users []*api.User + DecodeJSON(t, resp, &users) + assert.Len(t, users, 1) + assert.Equal(t, "user1", users[0].UserName) + }) + t.Run("RenameOrg", func(t *testing.T) { + req = NewRequestWithJSON(t, "POST", "/api/v1/orgs/user1_org/rename", &api.RenameOrgOption{ + NewName: "renamed_org", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertExistsAndLoadBean(t, &org_model.Organization{Name: "renamed_org"}) + org.UserName = "renamed_org" // update the variable so the following tests could still use it + }) + + t.Run("ListRepos", func(t *testing.T) { + // FIXME: this test is wrong, there is no repository at all, so the for-loop is empty + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org.UserName).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) var repos []*api.Repository DecodeJSON(t, resp, &repos) for _, repo := range repos { assert.False(t, repo.Private) } + }) +} + +func TestAPIOrgGeneral(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user1Session := loginUser(t, "user1") + user1Token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteOrganization) - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", org.UserName). - AddTokenAuth(token) + t.Run("OrgGetAll", func(t *testing.T) { + // accessing with a token will return all orgs + req := NewRequest(t, "GET", "/api/v1/orgs").AddTokenAuth(user1Token) + resp := MakeRequest(t, req, http.StatusOK) + var apiOrgList []*api.Organization + + DecodeJSON(t, resp, &apiOrgList) + assert.Len(t, apiOrgList, 13) + assert.Equal(t, "Limited Org 36", apiOrgList[1].FullName) + assert.Equal(t, "limited", apiOrgList[1].Visibility) + + // accessing without a token will return only public orgs + req = NewRequest(t, "GET", "/api/v1/orgs") resp = MakeRequest(t, req, http.StatusOK) - // user1 on this org is public - var users []*api.User - DecodeJSON(t, resp, &users) - assert.Len(t, users, 1) - assert.EqualValues(t, "user1", users[0].UserName) + DecodeJSON(t, resp, &apiOrgList) + assert.Len(t, apiOrgList, 9) + assert.Equal(t, "org 17", apiOrgList[0].FullName) + assert.Equal(t, "public", apiOrgList[0].Visibility) }) -} -func TestAPIOrgEdit(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - session := loginUser(t, "user1") - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + t.Run("OrgEdit", func(t *testing.T) { org := api.EditOrgOption{ FullName: "Org3 organization new full name", Description: "A new description", @@ -112,8 +145,7 @@ func TestAPIOrgEdit(t *testing.T) { Location: "Beijing", Visibility: "private", } - req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org). - AddTokenAuth(token) + req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token) resp := MakeRequest(t, req, http.StatusOK) var apiOrg api.Organization @@ -126,13 +158,8 @@ func TestAPIOrgEdit(t *testing.T) { assert.Equal(t, org.Location, apiOrg.Location) assert.Equal(t, org.Visibility, apiOrg.Visibility) }) -} -func TestAPIOrgEditBadVisibility(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - session := loginUser(t, "user1") - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + t.Run("OrgEditBadVisibility", func(t *testing.T) { org := api.EditOrgOption{ FullName: "Org3 organization new full name", Description: "A new description", @@ -140,18 +167,12 @@ func TestAPIOrgEditBadVisibility(t *testing.T) { Location: "Beijing", Visibility: "badvisibility", } - req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org). - AddTokenAuth(token) + req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token) MakeRequest(t, req, http.StatusUnprocessableEntity) }) -} -func TestAPIOrgDeny(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - setting.Service.RequireSignInView = true - defer func() { - setting.Service.RequireSignInView = false - }() + t.Run("OrgDeny", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() orgName := "user1_org" req := NewRequestf(t, "GET", "/api/v1/orgs/%s", orgName) @@ -163,43 +184,13 @@ func TestAPIOrgDeny(t *testing.T) { req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", orgName) MakeRequest(t, req, http.StatusNotFound) }) -} - -func TestAPIGetAll(t *testing.T) { - defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization) - - // accessing with a token will return all orgs - req := NewRequest(t, "GET", "/api/v1/orgs"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var apiOrgList []*api.Organization - - DecodeJSON(t, resp, &apiOrgList) - assert.Len(t, apiOrgList, 13) - assert.Equal(t, "Limited Org 36", apiOrgList[1].FullName) - assert.Equal(t, "limited", apiOrgList[1].Visibility) - - // accessing without a token will return only public orgs - req = NewRequest(t, "GET", "/api/v1/orgs") - resp = MakeRequest(t, req, http.StatusOK) - - DecodeJSON(t, resp, &apiOrgList) - assert.Len(t, apiOrgList, 9) - assert.Equal(t, "org 17", apiOrgList[0].FullName) - assert.Equal(t, "public", apiOrgList[0].Visibility) -} - -func TestAPIOrgSearchEmptyTeam(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) + t.Run("OrgSearchEmptyTeam", func(t *testing.T) { orgName := "org_with_empty_team" - // create org req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{ UserName: orgName, - }).AddTokenAuth(token) + }).AddTokenAuth(user1Token) MakeRequest(t, req, http.StatusCreated) // create team with no member @@ -208,12 +199,12 @@ func TestAPIOrgSearchEmptyTeam(t *testing.T) { IncludesAllRepositories: true, Permission: "read", Units: []string{"repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", "repo.pulls"}, - }).AddTokenAuth(token) + }).AddTokenAuth(user1Token) MakeRequest(t, req, http.StatusCreated) // case-insensitive search for teams that have no members req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/teams/search?q=%s", orgName, "empty")). - AddTokenAuth(token) + AddTokenAuth(user1Token) resp := MakeRequest(t, req, http.StatusOK) data := struct { Ok bool @@ -222,7 +213,40 @@ func TestAPIOrgSearchEmptyTeam(t *testing.T) { DecodeJSON(t, resp, &data) assert.True(t, data.Ok) if assert.Len(t, data.Data, 1) { - assert.EqualValues(t, "Empty", data.Data[0].Name) + assert.Equal(t, "Empty", data.Data[0].Name) } }) + + t.Run("User2ChangeStatus", func(t *testing.T) { + user2Session := loginUser(t, "user2") + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteOrganization) + + req := NewRequest(t, "PUT", "/api/v1/orgs/org3/public_members/user2").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "DELETE", "/api/v1/orgs/org3/public_members/user2").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNoContent) + + // non admin but org owner could also change other member's status + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + require.False(t, user2.IsAdmin) + req = NewRequest(t, "PUT", "/api/v1/orgs/org3/public_members/user1").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "DELETE", "/api/v1/orgs/org3/public_members/user1").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNoContent) + }) + + t.Run("User4ChangeStatus", func(t *testing.T) { + user4Session := loginUser(t, "user4") + user4Token := getTokenForLoggedInUser(t, user4Session, auth_model.AccessTokenScopeWriteOrganization) + + // user4 is a normal team member, they could change their own status + req := NewRequest(t, "PUT", "/api/v1/orgs/org3/public_members/user4").AddTokenAuth(user4Token) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "DELETE", "/api/v1/orgs/org3/public_members/user4").AddTokenAuth(user4Token) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "PUT", "/api/v1/orgs/org3/public_members/user1").AddTokenAuth(user4Token) + MakeRequest(t, req, http.StatusForbidden) + req = NewRequest(t, "DELETE", "/api/v1/orgs/org3/public_members/user1").AddTokenAuth(user4Token) + MakeRequest(t, req, http.StatusForbidden) + }) } diff --git a/tests/integration/api_packages_arch_test.go b/tests/integration/api_packages_arch_test.go index 9c7a9dd19d..e5778b4203 100644 --- a/tests/integration/api_packages_arch_test.go +++ b/tests/integration/api_packages_arch_test.go @@ -79,6 +79,34 @@ license = MIT`) return buf.Bytes() } + readIndexContent := func(r io.Reader) (map[string]string, error) { + gzr, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + + content := make(map[string]string) + + tr := tar.NewReader(gzr) + for { + hd, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + buf, err := io.ReadAll(tr) + if err != nil { + return nil, err + } + + content[hd.Name] = string(buf) + } + + return content, nil + } compressions := []string{"gz", "xz", "zst"} repositories := []string{"main", "testing", "with/slash", ""} @@ -135,7 +163,7 @@ license = MIT`) assert.Condition(t, func() bool { seen := false expectedFilename := fmt.Sprintf("%s-%s-aarch64.pkg.tar.%s", packageName, packageVersion, compression) - expectedCompositeKey := fmt.Sprintf("%s|aarch64", repository) + expectedCompositeKey := repository + "|aarch64" for _, pf := range pfs { if pf.Name == expectedFilename && pf.CompositeKey == expectedCompositeKey { if seen { @@ -171,35 +199,6 @@ license = MIT`) MakeRequest(t, req, http.StatusConflict) }) - readIndexContent := func(r io.Reader) (map[string]string, error) { - gzr, err := gzip.NewReader(r) - if err != nil { - return nil, err - } - - content := make(map[string]string) - - tr := tar.NewReader(gzr) - for { - hd, err := tr.Next() - if err == io.EOF { - break - } - if err != nil { - return nil, err - } - - buf, err := io.ReadAll(tr) - if err != nil { - return nil, err - } - - content[hd.Name] = string(buf) - } - - return content, nil - } - t.Run("Index", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -299,4 +298,39 @@ license = MIT`) }) } } + t.Run("KeepLastVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + pkgVer1 := createPackage("gz", "gitea-test", "1.0.0", "aarch64") + pkgVer2 := createPackage("gz", "gitea-test", "1.0.1", "aarch64") + req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer1)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + req = NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer2)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) + resp := MakeRequest(t, req, http.StatusOK) + + content, err := readIndexContent(resp.Body) + assert.NoError(t, err) + assert.Len(t, content, 2) + + _, has := content["gitea-test-1.0.0/desc"] + assert.False(t, has) + _, has = content["gitea-test-1.0.1/desc"] + assert.True(t, has) + + req = NewRequest(t, "DELETE", rootURL+"/gitea-test/1.0.1/aarch64"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) + resp = MakeRequest(t, req, http.StatusOK) + content, err = readIndexContent(resp.Body) + assert.NoError(t, err) + assert.Len(t, content, 2) + _, has = content["gitea-test-1.0.0/desc"] + assert.True(t, has) + }) } diff --git a/tests/integration/api_packages_cargo_test.go b/tests/integration/api_packages_cargo_test.go index 3fb9687653..8b5caa7ea7 100644 --- a/tests/integration/api_packages_cargo_test.go +++ b/tests/integration/api_packages_cargo_test.go @@ -94,7 +94,7 @@ func testPackageCargo(t *testing.T, _ *neturl.URL) { } root := fmt.Sprintf("%sapi/packages/%s/cargo", setting.AppURL, user.Name) - url := fmt.Sprintf("%s/api/v1/crates", root) + url := root + "/api/v1/crates" t.Run("Index", func(t *testing.T) { t.Run("Git/Config", func(t *testing.T) { diff --git a/tests/integration/api_packages_chef_test.go b/tests/integration/api_packages_chef_test.go index 6efb2708af..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 { @@ -274,7 +274,7 @@ nwIDAQAB uploadPackage := func(t *testing.T, version string, expectedStatus int) { var body bytes.Buffer mpw := multipart.NewWriter(&body) - part, _ := mpw.CreateFormFile("tarball", fmt.Sprintf("%s.tar.gz", version)) + part, _ := mpw.CreateFormFile("tarball", version+".tar.gz") zw := gzip.NewWriter(part) tw := tar.NewWriter(zw) @@ -320,7 +320,7 @@ nwIDAQAB pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) assert.NoError(t, err) assert.Len(t, pfs, 1) - assert.Equal(t, fmt.Sprintf("%s.tar.gz", packageVersion), pfs[0].Name) + assert.Equal(t, packageVersion+".tar.gz", pfs[0].Name) assert.True(t, pfs[0].IsLead) uploadPackage(t, packageVersion, http.StatusConflict) diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index 51b047ab41..54f61d91d9 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" composer_module "code.gitea.io/gitea/modules/packages/composer" @@ -63,7 +64,7 @@ func TestPackageComposer(t *testing.T) { t.Run("ServiceIndex", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/packages.json", url)). + req := NewRequest(t, "GET", url+"/packages.json"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -217,5 +218,39 @@ func TestPackageComposer(t *testing.T) { assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) assert.Len(t, pkgs[0].Bin, 1) assert.Equal(t, packageBin, pkgs[0].Bin[0]) + + // Test package linked to repository + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + userPkgs, err := packages.GetPackagesByType(db.DefaultContext, user.ID, packages.TypeComposer) + assert.NoError(t, err) + assert.Len(t, userPkgs, 1) + assert.EqualValues(t, 0, userPkgs[0].RepoID) + + err = packages.SetRepositoryLink(db.DefaultContext, userPkgs[0].ID, repo1.ID) + assert.NoError(t, err) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + result = composer.PackageMetadataResponse{} + DecodeJSON(t, resp, &result) + + assert.Contains(t, result.Packages, packageName) + pkgs = result.Packages[packageName] + assert.Len(t, pkgs, 1) + assert.Equal(t, packageName, pkgs[0].Name) + assert.Equal(t, packageVersion, pkgs[0].Version) + assert.Equal(t, packageType, pkgs[0].Type) + assert.Equal(t, packageDescription, pkgs[0].Description) + assert.Len(t, pkgs[0].Authors, 1) + assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) + assert.Equal(t, "zip", pkgs[0].Dist.Type) + assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) + assert.Len(t, pkgs[0].Bin, 1) + assert.Equal(t, packageBin, pkgs[0].Bin[0]) + assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL) + assert.Equal(t, "git", pkgs[0].Source.Type) + assert.Equal(t, packageVersion, pkgs[0].Source.Reference) }) } diff --git a/tests/integration/api_packages_conan_test.go b/tests/integration/api_packages_conan_test.go index 3055e57a2e..4e83c998b8 100644 --- a/tests/integration/api_packages_conan_test.go +++ b/tests/integration/api_packages_conan_test.go @@ -91,18 +91,18 @@ func uploadConanPackageV1(t *testing.T, baseURL, token, name, version, user, cha AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", recipeURL)). + req = NewRequest(t, "GET", recipeURL+"/digest"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", recipeURL)). + req = NewRequest(t, "GET", recipeURL+"/download_urls"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL)) + req = NewRequest(t, "POST", recipeURL+"/upload_urls") MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{ + req = NewRequestWithJSON(t, "POST", recipeURL+"/upload_urls", map[string]int64{ conanfileName: int64(len(contentConanfile)), "removed.txt": 0, }).AddTokenAuth(token) @@ -127,18 +127,18 @@ func uploadConanPackageV1(t *testing.T, baseURL, token, name, version, user, cha AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", packageURL)). + req = NewRequest(t, "GET", packageURL+"/digest"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", packageURL)). + req = NewRequest(t, "GET", packageURL+"/download_urls"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "POST", fmt.Sprintf("%s/upload_urls", packageURL)) + req = NewRequest(t, "POST", packageURL+"/upload_urls") MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", packageURL), map[string]int64{ + req = NewRequestWithJSON(t, "POST", packageURL+"/upload_urls", map[string]int64{ conaninfoName: int64(len(contentConaninfo)), "removed.txt": 0, }).AddTokenAuth(token) @@ -167,7 +167,7 @@ func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, cha AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - req = NewRequest(t, "GET", fmt.Sprintf("%s/files", recipeURL)). + req = NewRequest(t, "GET", recipeURL+"/files"). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) @@ -180,7 +180,7 @@ func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, cha packageURL := fmt.Sprintf("%s/packages/%s/revisions/%s", recipeURL, conanPackageReference, packageRevision) - req = NewRequest(t, "GET", fmt.Sprintf("%s/files", packageURL)). + req = NewRequest(t, "GET", packageURL+"/files"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) @@ -188,7 +188,7 @@ func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, cha AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - req = NewRequest(t, "GET", fmt.Sprintf("%s/files", packageURL)). + req = NewRequest(t, "GET", packageURL+"/files"). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) @@ -219,7 +219,7 @@ func TestPackageConan(t *testing.T) { t.Run("Ping", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/ping", url)) + req := NewRequest(t, "GET", url+"/v1/ping") resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities")) @@ -230,7 +230,7 @@ func TestPackageConan(t *testing.T) { t.Run("UserName/Password Authenticate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)). + req := NewRequest(t, "GET", url+"/v1/users/authenticate"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -256,7 +256,7 @@ func TestPackageConan(t *testing.T) { token := getTokenForLoggedInUser(t, session, scope) - req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)). + req := NewRequest(t, "GET", url+"/v1/users/authenticate"). AddTokenAuth(token) resp := MakeRequest(t, req, expectedAuthStatusCode) if expectedAuthStatusCode != http.StatusOK { @@ -273,7 +273,7 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, "TestScope", version1, "testing", channel1) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{ + req = NewRequestWithJSON(t, "POST", recipeURL+"/upload_urls", map[string]int64{ conanfileName: 64, "removed.txt": 0, }).AddTokenAuth(token) @@ -308,7 +308,7 @@ func TestPackageConan(t *testing.T) { t.Run("CheckCredentials", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/check_credentials", url)). + req := NewRequest(t, "GET", url+"/v1/users/check_credentials"). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) }) @@ -356,7 +356,7 @@ func TestPackageConan(t *testing.T) { assert.Equal(t, int64(len(contentConaninfo)), pb.Size) } else { - assert.FailNow(t, "unknown file: %s", pf.Name) + assert.FailNow(t, "unknown file", "unknown file: %s", pf.Name) } } }) @@ -376,14 +376,14 @@ func TestPackageConan(t *testing.T) { assert.Contains(t, fileHashes, conanfileName) assert.Equal(t, "7abc52241c22090782c54731371847a8", fileHashes[conanfileName]) - req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", recipeURL)) + req = NewRequest(t, "GET", recipeURL+"/digest") resp = MakeRequest(t, req, http.StatusOK) downloadURLs := make(map[string]string) DecodeJSON(t, resp, &downloadURLs) assert.Contains(t, downloadURLs, conanfileName) - req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", recipeURL)) + req = NewRequest(t, "GET", recipeURL+"/download_urls") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &downloadURLs) @@ -404,14 +404,14 @@ func TestPackageConan(t *testing.T) { assert.Contains(t, fileHashes, conaninfoName) assert.Equal(t, "7628bfcc5b17f1470c468621a78df394", fileHashes[conaninfoName]) - req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", packageURL)) + req = NewRequest(t, "GET", packageURL+"/digest") resp = MakeRequest(t, req, http.StatusOK) downloadURLs = make(map[string]string) DecodeJSON(t, resp, &downloadURLs) assert.Contains(t, downloadURLs, conaninfoName) - req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", packageURL)) + req = NewRequest(t, "GET", packageURL+"/download_urls") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &downloadURLs) @@ -550,7 +550,7 @@ func TestPackageConan(t *testing.T) { t.Run("Ping", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/ping", url)) + req := NewRequest(t, "GET", url+"/v2/ping") resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities")) @@ -561,7 +561,7 @@ func TestPackageConan(t *testing.T) { t.Run("UserName/Password Authenticate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)). + req := NewRequest(t, "GET", url+"/v2/users/authenticate"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -573,7 +573,7 @@ func TestPackageConan(t *testing.T) { assert.Equal(t, user.ID, pkgMeta.UserID) assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope) - token = fmt.Sprintf("Bearer %s", body) + token = "Bearer " + body }) badToken := "" @@ -590,7 +590,7 @@ func TestPackageConan(t *testing.T) { token := getTokenForLoggedInUser(t, session, scope) - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)). + req := NewRequest(t, "GET", url+"/v2/users/authenticate"). AddTokenAuth(token) resp := MakeRequest(t, req, expectedAuthStatusCode) if expectedAuthStatusCode != http.StatusOK { @@ -640,7 +640,7 @@ func TestPackageConan(t *testing.T) { t.Run("CheckCredentials", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/check_credentials", url)). + req := NewRequest(t, "GET", url+"/v2/users/check_credentials"). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) }) @@ -664,7 +664,7 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1) - req := NewRequest(t, "GET", fmt.Sprintf("%s/latest", recipeURL)) + req := NewRequest(t, "GET", recipeURL+"/latest") resp := MakeRequest(t, req, http.StatusOK) obj := make(map[string]string) diff --git a/tests/integration/api_packages_conda_test.go b/tests/integration/api_packages_conda_test.go index 272a660d45..32f55e5435 100644 --- a/tests/integration/api_packages_conda_test.go +++ b/tests/integration/api_packages_conda_test.go @@ -193,19 +193,19 @@ func TestPackageConda(t *testing.T) { Removed map[string]*PackageInfo `json:"removed"` } - req := NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json", root)) + req := NewRequest(t, "GET", root+"/noarch/repodata.json") resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) - req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json.bz2", root)) + req = NewRequest(t, "GET", root+"/noarch/repodata.json.bz2") resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) - req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/current_repodata.json", root)) + req = NewRequest(t, "GET", root+"/noarch/current_repodata.json") resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) - req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/current_repodata.json.bz2", root)) + req = NewRequest(t, "GET", root+"/noarch/current_repodata.json.bz2") resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) @@ -218,7 +218,7 @@ func TestPackageConda(t *testing.T) { pd, err := packages.GetPackageDescriptor(db.DefaultContext, pv) assert.NoError(t, err) - req := NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json", root)) + req := NewRequest(t, "GET", root+"/noarch/repodata.json") resp := MakeRequest(t, req, http.StatusOK) var result RepoData diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index 3905ad1b70..204f099bbe 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -7,8 +7,10 @@ import ( "bytes" "crypto/sha256" "encoding/base64" + "encoding/hex" "fmt" "net/http" + "strconv" "strings" "sync" "testing" @@ -16,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" @@ -56,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" @@ -69,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}]}` @@ -92,12 +94,12 @@ func TestPackageContainer(t *testing.T) { t.Run("Anonymous", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + req := NewRequest(t, "GET", setting.AppURL+"v2") resp := MakeRequest(t, req, http.StatusUnauthorized) assert.ElementsMatch(t, defaultAuthenticateValues, resp.Header().Values("WWW-Authenticate")) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req = NewRequest(t, "GET", setting.AppURL+"v2/token") resp = MakeRequest(t, req, http.StatusOK) tokenResponse := &TokenResponse{} @@ -105,18 +107,18 @@ func TestPackageContainer(t *testing.T) { assert.NotEmpty(t, tokenResponse.Token) - anonymousToken = fmt.Sprintf("Bearer %s", tokenResponse.Token) + anonymousToken = "Bearer " + tokenResponse.Token - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(anonymousToken) MakeRequest(t, req, http.StatusOK) - defer test.MockVariableValue(&setting.Service.RequireSignInView, true)() + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + req = NewRequest(t, "GET", setting.AppURL+"v2") MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req = NewRequest(t, "GET", setting.AppURL+"v2/token") MakeRequest(t, req, http.StatusUnauthorized) defer test.MockVariableValue(&setting.AppURL, "https://domain:8443/sub-path/")() @@ -129,12 +131,12 @@ func TestPackageContainer(t *testing.T) { t.Run("UserName/Password", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + req := NewRequest(t, "GET", setting.AppURL+"v2") resp := MakeRequest(t, req, http.StatusUnauthorized) assert.ElementsMatch(t, defaultAuthenticateValues, resp.Header().Values("WWW-Authenticate")) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2/token"). AddBasicAuth(user.Name) resp = MakeRequest(t, req, http.StatusOK) @@ -147,9 +149,9 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, user.ID, pkgMeta.UserID) assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope) - userToken = fmt.Sprintf("Bearer %s", tokenResponse.Token) + userToken = "Bearer " + tokenResponse.Token - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(userToken) MakeRequest(t, req, http.StatusOK) }) @@ -161,23 +163,23 @@ func TestPackageContainer(t *testing.T) { session := loginUser(t, user.Name) readToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage) - req := NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req := NewRequest(t, "GET", setting.AppURL+"v2/token") req.Request.SetBasicAuth(user.Name, readToken) resp := MakeRequest(t, req, http.StatusOK) tokenResponse := &TokenResponse{} DecodeJSON(t, resp, &tokenResponse) - readToken = fmt.Sprintf("Bearer %s", tokenResponse.Token) + readToken = "Bearer " + tokenResponse.Token badToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req = NewRequest(t, "GET", setting.AppURL+"v2/token") req.Request.SetBasicAuth(user.Name, badToken) MakeRequest(t, req, http.StatusUnauthorized) testCase := func(scope auth_model.AccessTokenScope, expectedAuthStatus, expectedStatus int) { token := getTokenForLoggedInUser(t, session, scope) - req := NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req := NewRequest(t, "GET", setting.AppURL+"v2/token") req.SetBasicAuth(user.Name, token) resp := MakeRequest(t, req, expectedAuthStatus) @@ -190,8 +192,8 @@ func TestPackageContainer(t *testing.T) { assert.NotEmpty(t, tokenResponse.Token) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). - AddTokenAuth(fmt.Sprintf("Bearer %s", tokenResponse.Token)) + req = NewRequest(t, "GET", setting.AppURL+"v2"). + AddTokenAuth("Bearer " + tokenResponse.Token) MakeRequest(t, req, expectedStatus) } testCase(auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusOK) @@ -204,17 +206,17 @@ func TestPackageContainer(t *testing.T) { t.Run("DetermineSupport", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req := NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(readToken) resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(badToken) MakeRequest(t, req, http.StatusUnauthorized) }) @@ -226,15 +228,15 @@ func TestPackageContainer(t *testing.T) { t.Run("UploadBlob/Monolithic", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req := NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(anonymousToken) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(readToken) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(badToken) MakeRequest(t, req, http.StatusUnauthorized) @@ -249,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) @@ -264,15 +266,15 @@ func TestPackageContainer(t *testing.T) { t.Run("UploadBlob/Chunked", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req := NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(readToken) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(badToken) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusAccepted) @@ -295,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") @@ -309,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) @@ -325,7 +339,7 @@ func TestPackageContainer(t *testing.T) { t.Run("Cancel", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req := NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusAccepted) @@ -340,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) @@ -428,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)) @@ -441,7 +456,7 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, "application/vnd.docker.image.rootfs.diff.tar.gzip", pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, blobDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) default: - assert.FailNow(t, "unknown file: %s", pfd.File.Name) + assert.FailNow(t, "unknown file", "unknown file: %s", pfd.File.Name) } } @@ -467,7 +482,7 @@ func TestPackageContainer(t *testing.T) { t.Run("HeadManifest", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/unknown-tag", url)). + req := NewRequest(t, "HEAD", url+"/manifests/unknown-tag"). AddTokenAuth(userToken) MakeRequest(t, req, http.StatusNotFound) @@ -475,14 +490,14 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(manifestContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length")) assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) }) t.Run("GetManifest", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/manifests/unknown-tag", url)). + req := NewRequest(t, "GET", url+"/manifests/unknown-tag"). AddTokenAuth(userToken) MakeRequest(t, req, http.StatusNotFound) @@ -490,8 +505,8 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(manifestContent)), resp.Header().Get("Content-Length")) - assert.Equal(t, oci.MediaTypeImageManifest, resp.Header().Get("Content-Type")) + assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length")) + 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()) }) @@ -512,7 +527,7 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(untaggedManifestContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(untaggedManifestContent)), resp.Header().Get("Content-Length")) assert.Equal(t, untaggedManifestDigest, resp.Header().Get("Docker-Content-Digest")) pv, err := packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, untaggedManifestDigest) @@ -530,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)) @@ -598,7 +613,7 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(blobContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(blobContent)), resp.Header().Get("Content-Length")) assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest)). @@ -617,11 +632,27 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(blobContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(blobContent)), resp.Header().Get("Content-Length")) assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) 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)() @@ -631,27 +662,27 @@ func TestPackageContainer(t *testing.T) { ExpectedLink string }{ { - URL: fmt.Sprintf("%s/tags/list", url), + URL: url + "/tags/list", ExpectedTags: []string{"latest", "main", "multi"}, ExpectedLink: fmt.Sprintf(`</v2/%s/%s/tags/list?last=multi>; rel="next"`, user.Name, image), }, { - URL: fmt.Sprintf("%s/tags/list?n=0", url), + URL: url + "/tags/list?n=0", ExpectedTags: []string{}, ExpectedLink: "", }, { - URL: fmt.Sprintf("%s/tags/list?n=2", url), + URL: url + "/tags/list?n=2", ExpectedTags: []string{"latest", "main"}, ExpectedLink: fmt.Sprintf(`</v2/%s/%s/tags/list?last=main&n=2>; rel="next"`, user.Name, image), }, { - URL: fmt.Sprintf("%s/tags/list?last=main", url), + URL: url + "/tags/list?last=main", ExpectedTags: []string{"multi"}, ExpectedLink: fmt.Sprintf(`</v2/%s/%s/tags/list?last=multi>; rel="next"`, user.Name, image), }, { - URL: fmt.Sprintf("%s/tags/list?n=1&last=latest", url), + URL: url + "/tags/list?n=1&last=latest", ExpectedTags: []string{"main"}, ExpectedLink: fmt.Sprintf(`</v2/%s/%s/tags/list?last=main&n=1>; rel="next"`, user.Name, image), }, @@ -731,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)} @@ -757,7 +788,7 @@ func TestPackageContainer(t *testing.T) { return func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL)). + req := NewRequest(t, "GET", setting.AppURL+"v2/_catalog"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_packages_cran_test.go b/tests/integration/api_packages_cran_test.go index 667ba0908c..bd4a99f331 100644 --- a/tests/integration/api_packages_cran_test.go +++ b/tests/integration/api_packages_cran_test.go @@ -133,8 +133,8 @@ func TestPackageCran(t *testing.T) { assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") body := resp.Body.String() - assert.Contains(t, body, fmt.Sprintf("Package: %s", packageName)) - assert.Contains(t, body, fmt.Sprintf("Version: %s", packageVersion)) + assert.Contains(t, body, "Package: "+packageName) + assert.Contains(t, body, "Version: "+packageVersion) req = NewRequest(t, "GET", url+"/src/contrib/PACKAGES.gz"). AddBasicAuth(user.Name) @@ -230,8 +230,8 @@ func TestPackageCran(t *testing.T) { assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") body := resp.Body.String() - assert.Contains(t, body, fmt.Sprintf("Package: %s", packageName)) - assert.Contains(t, body, fmt.Sprintf("Version: %s", packageVersion)) + assert.Contains(t, body, "Package: "+packageName) + assert.Contains(t, body, "Version: "+packageVersion) req = NewRequest(t, "GET", url+"/bin/windows/contrib/4.2/PACKAGES.gz"). AddBasicAuth(user.Name) 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_generic_test.go b/tests/integration/api_packages_generic_test.go index baa8dd66c8..5f410fc470 100644 --- a/tests/integration/api_packages_generic_test.go +++ b/tests/integration/api_packages_generic_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/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -131,11 +132,7 @@ func TestPackageGeneric(t *testing.T) { t.Run("RequireSignInView", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - - setting.Service.RequireSignInView = true - defer func() { - setting.Service.RequireSignInView = false - }() + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() req = NewRequest(t, "GET", url+"/dummy.bin") MakeRequest(t, req, http.StatusUnauthorized) 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_helm_test.go b/tests/integration/api_packages_helm_test.go index 76285add11..8f5c6ac571 100644 --- a/tests/integration/api_packages_helm_test.go +++ b/tests/integration/api_packages_helm_test.go @@ -52,7 +52,7 @@ dependencies: zw := gzip.NewWriter(&buf) archive := tar.NewWriter(zw) archive.WriteHeader(&tar.Header{ - Name: fmt.Sprintf("%s/Chart.yaml", packageName), + Name: packageName + "/Chart.yaml", Mode: 0o600, Size: int64(len(chartContent)), }) @@ -122,7 +122,7 @@ dependencies: t.Run("Index", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/index.yaml", url)). + req := NewRequest(t, "GET", url+"/index.yaml"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go index 486a5af93e..30ef1884cd 100644 --- a/tests/integration/api_packages_maven_test.go +++ b/tests/integration/api_packages_maven_test.go @@ -80,6 +80,7 @@ func TestPackageMaven(t *testing.T) { t.Run("UploadLegacy", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + // try to upload a package with legacy package name (will be saved as "GroupID-ArtifactID") legacyRootLink := "/api/packages/user2/maven/com/gitea/legacy-project" req := NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.2/any-file-name?use_legacy_package_name=1", strings.NewReader("test-content")).AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusCreated) @@ -97,6 +98,13 @@ func TestPackageMaven(t *testing.T) { req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2") MakeRequest(t, req, http.StatusNotFound) + // legacy package names should also be able to be listed + req = NewRequest(t, "GET", legacyRootLink+"/maven-metadata.xml").AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + respBody := resp.Body.String() + assert.Contains(t, respBody, "<version>1.0.2</version>") + + // then upload a package with correct package name (will be saved as "GroupID:ArtifactID") req = NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.3/any-file-name", strings.NewReader("test-content")).AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusCreated) _, err = packages.GetPackageByName(db.DefaultContext, user.ID, packages.TypeMaven, "com.gitea-legacy-project") @@ -114,6 +122,12 @@ func TestPackageMaven(t *testing.T) { req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2") MakeRequest(t, req, http.StatusOK) + // now 2 packages should be listed + req = NewRequest(t, "GET", legacyRootLink+"/maven-metadata.xml").AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + respBody = resp.Body.String() + assert.Contains(t, respBody, "<version>1.0.2</version>") + assert.Contains(t, respBody, "<version>1.0.3</version>") require.NoError(t, packages.DeletePackageByID(db.DefaultContext, p.ID)) }) @@ -307,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_npm_test.go b/tests/integration/api_packages_npm_test.go index ae1dd876f7..a190ed679d 100644 --- a/tests/integration/api_packages_npm_test.go +++ b/tests/integration/api_packages_npm_test.go @@ -28,7 +28,7 @@ func TestPackageNpm(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := fmt.Sprintf("Bearer %s", getTokenForLoggedInUser(t, loginUser(t, user.Name), auth_model.AccessTokenScopeWritePackage)) + token := "Bearer " + getTokenForLoggedInUser(t, loginUser(t, user.Name), auth_model.AccessTokenScopeWritePackage) packageName := "@scope/test-package" packageVersion := "1.0.1-pre" diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index 622c2c4394..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 { @@ -198,7 +233,7 @@ func TestPackageNuGet(t *testing.T) { t.Run(c.Owner, func(t *testing.T) { url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) - req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) + req := NewRequest(t, "GET", url+"/index.json") if c.UseBasicAuth { req.AddBasicAuth(user.Name) } else if c.token != "" { @@ -273,10 +308,10 @@ func TestPackageNuGet(t *testing.T) { pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) assert.Equal(t, int64(len(content)), pb.Size) - case fmt.Sprintf("%s.nuspec", packageName): + case packageName + ".nuspec": assert.False(t, pf.IsLead) default: - assert.Fail(t, "unexpected filename: %v", pf.Name) + assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) } } @@ -319,10 +354,10 @@ func TestPackageNuGet(t *testing.T) { pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) assert.Equal(t, int64(len(content)), pb.Size) - case fmt.Sprintf("%s.nuspec", packageName): + case packageName + ".nuspec": assert.False(t, pf.IsLead) default: - assert.Fail(t, "unexpected filename: %v", pf.Name) + assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) } } @@ -360,15 +395,15 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) return &buf } - req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage("unknown-package", "SymbolsPackage")). + req := NewRequestWithBody(t, "PUT", url+"/symbolpackage", createSymbolPackage("unknown-package", "SymbolsPackage")). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) - req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "DummyPackage")). + req = NewRequestWithBody(t, "PUT", url+"/symbolpackage", createSymbolPackage(packageName, "DummyPackage")). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusBadRequest) - req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage")). + req = NewRequestWithBody(t, "PUT", url+"/symbolpackage", createSymbolPackage(packageName, "SymbolsPackage")). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusCreated) @@ -393,19 +428,19 @@ 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) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) assert.Equal(t, int64(616), pb.Size) - case fmt.Sprintf("%s.nuspec", packageName): + case packageName + ".nuspec": assert.False(t, pf.IsLead) 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) @@ -419,11 +454,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) assert.Equal(t, nuget_module.PropertySymbolID, pps[0].Name) assert.Equal(t, symbolID, pps[0].Value) default: - assert.FailNow(t, "unexpected file: %v", pf.Name) + assert.FailNow(t, "unexpected file", "unexpected file: %v", pf.Name) } } - req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage")). + req = NewRequestWithBody(t, "PUT", url+"/symbolpackage", createSymbolPackage(packageName, "SymbolsPackage")). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusConflict) }) @@ -631,7 +666,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) }) t.Run("Next", func(t *testing.T) { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)). + req := NewRequest(t, "GET", url+"/Search()?searchTerm='test'&$skip=0&$top=1"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -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_pub_test.go b/tests/integration/api_packages_pub_test.go index 11da894ddf..3c1bca908e 100644 --- a/tests/integration/api_packages_pub_test.go +++ b/tests/integration/api_packages_pub_test.go @@ -37,7 +37,7 @@ func TestPackagePub(t *testing.T) { packageVersion := "1.0.1" packageDescription := "Test Description" - filename := fmt.Sprintf("%s.tar.gz", packageVersion) + filename := packageVersion + ".tar.gz" pubspecContent := `name: ` + packageName + ` version: ` + packageVersion + ` diff --git a/tests/integration/api_packages_pypi_test.go b/tests/integration/api_packages_pypi_test.go index 2dabb5005b..54db45f1ac 100644 --- a/tests/integration/api_packages_pypi_test.go +++ b/tests/integration/api_packages_pypi_test.go @@ -67,7 +67,7 @@ func TestPackagePyPI(t *testing.T) { body, writer, closeFunc := createBasicMultipartFile(filename, packageName, content) writer.WriteField("project_urls", "DOCUMENTATION , https://readthedocs.org") - writer.WriteField("project_urls", fmt.Sprintf("Home-page, %s", projectURL)) + writer.WriteField("project_urls", "Home-page, "+projectURL) _ = closeFunc() diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 6feceaeb78..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()) }) @@ -317,7 +322,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, var result Metadata decodeGzipXML(t, resp, &result) - assert.EqualValues(t, 1, result.PackageCount) + assert.Equal(t, 1, result.PackageCount) assert.Len(t, result.Packages, 1) p := result.Packages[0] assert.Equal(t, "rpm", p.Type) @@ -366,7 +371,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, var result Filelists decodeGzipXML(t, resp, &result) - assert.EqualValues(t, 1, result.PackageCount) + assert.Equal(t, 1, result.PackageCount) assert.Len(t, result.Packages, 1) p := result.Packages[0] assert.NotEmpty(t, p.Pkgid) @@ -403,7 +408,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, var result Other decodeGzipXML(t, resp, &result) - assert.EqualValues(t, 1, result.PackageCount) + assert.Equal(t, 1, result.PackageCount) assert.Len(t, result.Packages, 1) p := result.Packages[0] assert.NotEmpty(t, p.Pkgid) @@ -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_rubygems_test.go b/tests/integration/api_packages_rubygems_test.go index fe9283df4d..ab76c52440 100644 --- a/tests/integration/api_packages_rubygems_test.go +++ b/tests/integration/api_packages_rubygems_test.go @@ -185,7 +185,7 @@ func TestPackageRubyGems(t *testing.T) { root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name) uploadFile := func(t *testing.T, content []byte, expectedStatus int) { - req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(content)). + req := NewRequestWithBody(t, "POST", root+"/api/v1/gems", bytes.NewReader(content)). AddBasicAuth(user.Name) MakeRequest(t, req, expectedStatus) } @@ -293,7 +293,7 @@ gAAAAP//MS06Gw==`) t.Run("Versions", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name) + req := NewRequest(t, "GET", root+"/versions").AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, `--- gitea 1.0.5 08843c2dd0ea19910e6b056b98e38f1c @@ -307,7 +307,7 @@ gitea-another 0.99 8b639e4048d282941485368ec42609be _ = writer.WriteField("gem_name", packageName) _ = writer.WriteField("version", packageVersion) _ = writer.Close() - req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body). + req := NewRequestWithBody(t, "DELETE", root+"/api/v1/gems/yank", &body). SetHeader("Content-Type", writer.FormDataContentType()). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusOK) @@ -330,7 +330,7 @@ gitea-another 0.99 8b639e4048d282941485368ec42609be t.Run("VersionsAfterDelete", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name) + req := NewRequest(t, "GET", root+"/versions").AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "---\n", resp.Body.String()) }) diff --git a/tests/integration/api_packages_swift_test.go b/tests/integration/api_packages_swift_test.go index c0e0dccfab..b29e8459ff 100644 --- a/tests/integration/api_packages_swift_test.go +++ b/tests/integration/api_packages_swift_test.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPackageSwift(t *testing.T) { @@ -34,6 +35,7 @@ func TestPackageSwift(t *testing.T) { packageName := "test_package" packageID := packageScope + "." + packageName packageVersion := "1.0.3" + packageVersion2 := "1.0.4" packageAuthor := "KN4CK3R" packageDescription := "Gitea Test Package" packageRepositoryURL := "https://gitea.io/gitea/gitea" @@ -183,6 +185,94 @@ func TestPackageSwift(t *testing.T) { ) }) + t.Run("UploadMultipart", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) { + var body bytes.Buffer + mpw := multipart.NewWriter(&body) + + // Read the source archive content + sourceContent, err := io.ReadAll(sr) + assert.NoError(t, err) + mpw.WriteField("source-archive", string(sourceContent)) + + if metadata != "" { + mpw.WriteField("metadata", metadata) + } + + mpw.Close() + + req := NewRequestWithBody(t, "PUT", url, &body). + SetHeader("Content-Type", mpw.FormDataContentType()). + SetHeader("Accept", swift_router.AcceptJSON). + AddBasicAuth(user.Name) + MakeRequest(t, req, expectedStatus) + } + + createArchive := func(files map[string]string) *bytes.Buffer { + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + for filename, content := range files { + w, _ := zw.Create(filename) + w.Write([]byte(content)) + } + zw.Close() + return &buf + } + + uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2) + + req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) + MakeRequest(t, req, http.StatusUnauthorized) + + // Test with metadata as form field + uploadPackage( + t, + uploadURL, + http.StatusCreated, + createArchive(map[string]string{ + "Package.swift": contentManifest1, + "Package@swift-5.6.swift": contentManifest2, + }), + `{"name":"`+packageName+`","version":"`+packageVersion2+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`, + ) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift) + assert.NoError(t, err) + require.Len(t, pvs, 2) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests + thisPackageVersion := pvs[0] + pd, err := packages.GetPackageDescriptor(db.DefaultContext, thisPackageVersion) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.Equal(t, packageID, pd.Package.Name) + assert.Equal(t, packageVersion2, pd.Version.Version) + assert.IsType(t, &swift_module.Metadata{}, pd.Metadata) + metadata := pd.Metadata.(*swift_module.Metadata) + assert.Equal(t, packageDescription, metadata.Description) + assert.Len(t, metadata.Manifests, 2) + assert.Equal(t, contentManifest1, metadata.Manifests[""].Content) + assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content) + assert.Len(t, pd.VersionProperties, 1) + assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL)) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, thisPackageVersion.ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion2), pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + uploadPackage( + t, + uploadURL, + http.StatusConflict, + createArchive(map[string]string{ + "Package.swift": contentManifest1, + }), + "", + ) + }) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -211,7 +301,7 @@ func TestPackageSwift(t *testing.T) { SetHeader("Accept", swift_router.AcceptJSON) resp := MakeRequest(t, req, http.StatusOK) - versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion) + versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion2) assert.Equal(t, "1", resp.Header().Get("Content-Version")) assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link")) @@ -221,9 +311,9 @@ func TestPackageSwift(t *testing.T) { var result *swift_router.EnumeratePackageVersionsResponse DecodeJSON(t, resp, &result) - assert.Len(t, result.Releases, 1) - assert.Contains(t, result.Releases, packageVersion) - assert.Equal(t, versionURL, result.Releases[packageVersion].URL) + assert.Len(t, result.Releases, 2) + assert.Contains(t, result.Releases, packageVersion2) + assert.Equal(t, versionURL, result.Releases[packageVersion2].URL) req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName)). AddBasicAuth(user.Name) diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index daf32e82f9..f10b098885 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -15,14 +15,15 @@ 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" packages_service "code.gitea.io/gitea/services/packages" packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup" + repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -34,7 +35,7 @@ func TestPackageAPI(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) session := loginUser(t, user.Name) tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage) - tokenDeletePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWritePackage) + tokenWritePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWritePackage) packageName := "test-package" packageVersion := "1.0.3" @@ -48,7 +49,7 @@ func TestPackageAPI(t *testing.T) { t.Run("ListPackages", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s", user.Name)). + req := NewRequest(t, "GET", "/api/v1/packages/"+user.Name). AddTokenAuth(tokenReadPackage) resp := MakeRequest(t, req, http.StatusOK) @@ -82,47 +83,101 @@ func TestPackageAPI(t *testing.T) { assert.Equal(t, packageVersion, p.Version) assert.NotNil(t, p.Creator) assert.Equal(t, user.Name, p.Creator.UserName) + }) - t.Run("RepositoryLink", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("ListPackageVersions", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - p, err := packages_model.GetPackageByName(db.DefaultContext, user.ID, packages_model.TypeGeneric, packageName) - assert.NoError(t, err) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)). + AddTokenAuth(tokenReadPackage) + resp := MakeRequest(t, req, http.StatusOK) - // no repository link - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenReadPackage) - resp := MakeRequest(t, req, http.StatusOK) + var apiPackages []*api.Package + DecodeJSON(t, resp, &apiPackages) + + assert.Len(t, apiPackages, 1) + assert.Equal(t, string(packages_model.TypeGeneric), apiPackages[0].Type) + assert.Equal(t, packageName, apiPackages[0].Name) + assert.Equal(t, packageVersion, apiPackages[0].Version) + }) + + t.Run("LatestPackageVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - var ap1 *api.Package - DecodeJSON(t, resp, &ap1) - assert.Nil(t, ap1.Repository) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/latest", user.Name, packageName)). + AddTokenAuth(tokenReadPackage) + resp := MakeRequest(t, req, http.StatusOK) - // link to public repository - assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, 1)) + var apiPackage *api.Package + DecodeJSON(t, resp, &apiPackage) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenReadPackage) - resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, string(packages_model.TypeGeneric), apiPackage.Type) + assert.Equal(t, packageName, apiPackage.Name) + assert.Equal(t, packageVersion, apiPackage.Version) + }) - var ap2 *api.Package - DecodeJSON(t, resp, &ap2) - assert.NotNil(t, ap2.Repository) - assert.EqualValues(t, 1, ap2.Repository.ID) + t.Run("RepositoryLink", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - // link to private repository - assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, 2)) + _, err := packages_model.GetPackageByName(db.DefaultContext, user.ID, packages_model.TypeGeneric, packageName) + assert.NoError(t, err) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenReadPackage) - resp = MakeRequest(t, req, http.StatusOK) + // no repository link + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). + AddTokenAuth(tokenReadPackage) + resp := MakeRequest(t, req, http.StatusOK) - var ap3 *api.Package - DecodeJSON(t, resp, &ap3) - assert.Nil(t, ap3.Repository) + var ap1 *api.Package + DecodeJSON(t, resp, &ap1) + assert.Nil(t, ap1.Repository) - assert.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, 2)) + // create a repository + newRepo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ + Name: "repo4", }) + assert.NoError(t, err) + + // link to public repository + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, newRepo.Name)).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). + AddTokenAuth(tokenReadPackage) + resp = MakeRequest(t, req, http.StatusOK) + + var ap2 *api.Package + DecodeJSON(t, resp, &ap2) + assert.NotNil(t, ap2.Repository) + assert.Equal(t, newRepo.ID, ap2.Repository.ID) + + // link to repository without write access, should fail + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, "repo3")).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNotFound) + + // remove link + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/unlink", user.Name, packageName)).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). + AddTokenAuth(tokenReadPackage) + resp = MakeRequest(t, req, http.StatusOK) + + var ap3 *api.Package + DecodeJSON(t, resp, &ap3) + assert.Nil(t, ap3.Repository) + + // force link to a repository the currently logged-in user doesn't have access to + privateRepoID := int64(6) + assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, ap1.ID, privateRepoID)) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).AddTokenAuth(tokenReadPackage) + resp = MakeRequest(t, req, http.StatusOK) + + var ap4 *api.Package + DecodeJSON(t, resp, &ap4) + assert.Nil(t, ap4.Repository) + + assert.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, privateRepoID)) }) t.Run("ListPackageFiles", func(t *testing.T) { @@ -152,11 +207,11 @@ func TestPackageAPI(t *testing.T) { defer tests.PrintCurrentTest(t)() req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenDeletePackage) + AddTokenAuth(tokenWritePackage) MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenDeletePackage) + AddTokenAuth(tokenWritePackage) MakeRequest(t, req, http.StatusNoContent) }) } @@ -384,7 +439,7 @@ func TestPackageAccess(t *testing.T) { {limitedOrgNoMember, http.StatusOK}, {publicOrgNoMember, http.StatusOK}, } { - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s", target.Owner.Name)). + req := NewRequest(t, "GET", "/api/v1/packages/"+target.Owner.Name). AddTokenAuth(tokenReadPackage) MakeRequest(t, req, target.ExpectedStatus) } @@ -483,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) @@ -493,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) }) @@ -581,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, @@ -631,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_packages_vagrant_test.go b/tests/integration/api_packages_vagrant_test.go index a5e954f3a2..1743e37222 100644 --- a/tests/integration/api_packages_vagrant_test.go +++ b/tests/integration/api_packages_vagrant_test.go @@ -35,7 +35,7 @@ func TestPackageVagrant(t *testing.T) { packageDescription := "Test Description" packageProvider := "virtualbox" - filename := fmt.Sprintf("%s.box", packageProvider) + filename := packageProvider + ".box" infoContent, _ := json.Marshal(map[string]string{ "description": packageDescription, @@ -59,7 +59,7 @@ func TestPackageVagrant(t *testing.T) { t.Run("Authenticate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - authenticateURL := fmt.Sprintf("%s/authenticate", root) + authenticateURL := root + "/authenticate" req := NewRequest(t, "GET", authenticateURL) MakeRequest(t, req, http.StatusUnauthorized) diff --git a/tests/integration/api_private_serv_test.go b/tests/integration/api_private_serv_test.go index 2001bb1fad..b0dd0cf049 100644 --- a/tests/integration/api_private_serv_test.go +++ b/tests/integration/api_private_serv_test.go @@ -17,7 +17,7 @@ import ( func TestAPIPrivateNoServ(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() key, user, err := private.ServNoCommand(ctx, 1) assert.NoError(t, err) @@ -39,7 +39,7 @@ func TestAPIPrivateNoServ(t *testing.T) { func TestAPIPrivateServ(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() // Can push to a repo we own diff --git a/tests/integration/api_pull_review_test.go b/tests/integration/api_pull_review_test.go index b85882a510..1fc65ddea8 100644 --- a/tests/integration/api_pull_review_test.go +++ b/tests/integration/api_pull_review_test.go @@ -43,17 +43,17 @@ func TestAPIPullReview(t *testing.T) { require.Len(t, reviews, 8) for _, r := range reviews { - assert.EqualValues(t, pullIssue.HTMLURL(), r.HTMLPullURL) + assert.Equal(t, pullIssue.HTMLURL(), r.HTMLPullURL) } assert.EqualValues(t, 8, reviews[3].ID) assert.EqualValues(t, "APPROVED", reviews[3].State) - assert.EqualValues(t, 0, reviews[3].CodeCommentsCount) + assert.Equal(t, 0, reviews[3].CodeCommentsCount) assert.True(t, reviews[3].Stale) assert.False(t, reviews[3].Official) assert.EqualValues(t, 10, reviews[5].ID) assert.EqualValues(t, "REQUEST_CHANGES", reviews[5].State) - assert.EqualValues(t, 1, reviews[5].CodeCommentsCount) + assert.Equal(t, 1, reviews[5].CodeCommentsCount) assert.EqualValues(t, -1, reviews[5].Reviewer.ID) // ghost user assert.False(t, reviews[5].Stale) assert.True(t, reviews[5].Official) @@ -64,13 +64,13 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) var review api.PullReview DecodeJSON(t, resp, &review) - assert.EqualValues(t, *reviews[3], review) + assert.Equal(t, *reviews[3], review) req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, reviews[5].ID). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &review) - assert.EqualValues(t, *reviews[5], review) + assert.Equal(t, *reviews[5], review) // test GetPullReviewComments comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 7}) @@ -80,11 +80,11 @@ func TestAPIPullReview(t *testing.T) { var reviewComments []*api.PullReviewComment DecodeJSON(t, resp, &reviewComments) assert.Len(t, reviewComments, 1) - assert.EqualValues(t, "Ghost", reviewComments[0].Poster.UserName) - assert.EqualValues(t, "a review from a deleted user", reviewComments[0].Body) - assert.EqualValues(t, comment.ID, reviewComments[0].ID) + assert.Equal(t, "Ghost", reviewComments[0].Poster.UserName) + assert.Equal(t, "a review from a deleted user", reviewComments[0].Body) + assert.Equal(t, comment.ID, reviewComments[0].ID) assert.EqualValues(t, comment.UpdatedUnix, reviewComments[0].Updated.Unix()) - assert.EqualValues(t, comment.HTMLURL(db.DefaultContext), reviewComments[0].HTMLURL) + assert.Equal(t, comment.HTMLURL(db.DefaultContext), reviewComments[0].HTMLURL) // test CreatePullReview req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ @@ -113,7 +113,7 @@ func TestAPIPullReview(t *testing.T) { DecodeJSON(t, resp, &review) assert.EqualValues(t, 6, review.ID) assert.EqualValues(t, "PENDING", review.State) - assert.EqualValues(t, 3, review.CodeCommentsCount) + assert.Equal(t, 3, review.CodeCommentsCount) // test SubmitPullReview req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID), &api.SubmitPullReviewOptions{ @@ -124,7 +124,7 @@ func TestAPIPullReview(t *testing.T) { DecodeJSON(t, resp, &review) assert.EqualValues(t, 6, review.ID) assert.EqualValues(t, "APPROVED", review.State) - assert.EqualValues(t, 3, review.CodeCommentsCount) + assert.Equal(t, 3, review.CodeCommentsCount) // test dismiss review req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/dismissals", repo.OwnerName, repo.Name, pullIssue.Index, review.ID), &api.DismissPullReviewOptions{ @@ -151,7 +151,7 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &review) assert.EqualValues(t, "COMMENT", review.State) - assert.EqualValues(t, 0, review.CodeCommentsCount) + assert.Equal(t, 0, review.CodeCommentsCount) req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) @@ -179,7 +179,7 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &commentReview) assert.EqualValues(t, "COMMENT", commentReview.State) - assert.EqualValues(t, 2, commentReview.CodeCommentsCount) + assert.Equal(t, 2, commentReview.CodeCommentsCount) assert.Empty(t, commentReview.Body) assert.False(t, commentReview.Dismissed) @@ -194,8 +194,8 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &commentReview) assert.EqualValues(t, "COMMENT", commentReview.State) - assert.EqualValues(t, 0, commentReview.CodeCommentsCount) - assert.EqualValues(t, commentBody, commentReview.Body) + assert.Equal(t, 0, commentReview.CodeCommentsCount) + assert.Equal(t, commentBody, commentReview.Body) assert.False(t, commentReview.Dismissed) // test CreatePullReview Comment without body and no comments @@ -207,7 +207,7 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusUnprocessableEntity) errMap := make(map[string]any) json.Unmarshal(resp.Body.Bytes(), &errMap) - assert.EqualValues(t, "review event COMMENT requires a body or a comment", errMap["message"].(string)) + assert.Equal(t, "review event COMMENT requires a body or a comment", errMap["message"].(string)) // test get review requests // to make it simple, use same api with get review @@ -221,14 +221,14 @@ func TestAPIPullReview(t *testing.T) { DecodeJSON(t, resp, &reviews) assert.EqualValues(t, 11, reviews[0].ID) assert.EqualValues(t, "REQUEST_REVIEW", reviews[0].State) - assert.EqualValues(t, 0, reviews[0].CodeCommentsCount) + assert.Equal(t, 0, reviews[0].CodeCommentsCount) assert.False(t, reviews[0].Stale) assert.True(t, reviews[0].Official) - assert.EqualValues(t, "test_team", reviews[0].ReviewerTeam.Name) + assert.Equal(t, "test_team", reviews[0].ReviewerTeam.Name) assert.EqualValues(t, 12, reviews[1].ID) assert.EqualValues(t, "REQUEST_REVIEW", reviews[1].State) - assert.EqualValues(t, 0, reviews[0].CodeCommentsCount) + assert.Equal(t, 0, reviews[0].CodeCommentsCount) assert.False(t, reviews[1].Stale) assert.True(t, reviews[1].Official) assert.EqualValues(t, 1, reviews[1].Reviewer.ID) diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index 969e110895..f2df6021e1 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -5,11 +5,13 @@ package integration import ( "bytes" - "context" "fmt" "io" "net/http" + "net/url" + "strings" "testing" + "time" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" @@ -18,11 +20,15 @@ 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/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/gitdiff" issue_service "code.gitea.io/gitea/services/issue" + pull_service "code.gitea.io/gitea/services/pull" + files_service "code.gitea.io/gitea/services/repository/files" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -51,30 +57,30 @@ func TestAPIViewPulls(t *testing.T) { assert.Empty(t, pull.RequestedReviewersTeams) assert.EqualValues(t, 5, pull.RequestedReviewers[0].ID) assert.EqualValues(t, 6, pull.RequestedReviewers[1].ID) - assert.EqualValues(t, 1, pull.ChangedFiles) if assert.EqualValues(t, 5, pull.ID) { resp = ctx.Session.MakeRequest(t, NewRequest(t, "GET", pull.DiffURL), http.StatusOK) bs, err := io.ReadAll(resp.Body) assert.NoError(t, err) - patch, err := gitdiff.ParsePatch(context.Background(), 1000, 5000, 10, bytes.NewReader(bs), "") + patch, err := gitdiff.ParsePatch(t.Context(), 1000, 5000, 10, bytes.NewReader(bs), "") assert.NoError(t, err) - if assert.Len(t, patch.Files, pull.ChangedFiles) { + if assert.Len(t, patch.Files, 1) { assert.Equal(t, "File-WoW", patch.Files[0].Name) // FIXME: The old name should be empty if it's a file add type assert.Equal(t, "File-WoW", patch.Files[0].OldName) - assert.EqualValues(t, pull.Additions, patch.Files[0].Addition) - assert.EqualValues(t, pull.Deletions, patch.Files[0].Deletion) + assert.Equal(t, 1, patch.Files[0].Addition) + assert.Equal(t, 0, patch.Files[0].Deletion) assert.Equal(t, gitdiff.DiffFileAdd, patch.Files[0].Type) } t.Run(fmt.Sprintf("APIGetPullFiles_%d", pull.ID), doAPIGetPullFiles(ctx, pull, func(t *testing.T, files []*api.ChangedFile) { - if assert.Len(t, files, pull.ChangedFiles) { + if assert.Len(t, files, 1) { assert.Equal(t, "File-WoW", files[0].Filename) assert.Empty(t, files[0].PreviousFilename) - assert.EqualValues(t, pull.Additions, files[0].Additions) - assert.EqualValues(t, pull.Deletions, files[0].Deletions) + assert.Equal(t, 1, files[0].Additions) + assert.Equal(t, 1, files[0].Changes) + assert.Equal(t, 0, files[0].Deletions) assert.Equal(t, "added", files[0].Status) } })) @@ -88,53 +94,51 @@ func TestAPIViewPulls(t *testing.T) { assert.EqualValues(t, 4, pull.RequestedReviewers[1].ID) assert.EqualValues(t, 2, pull.RequestedReviewers[2].ID) assert.EqualValues(t, 5, pull.RequestedReviewers[3].ID) - assert.EqualValues(t, 1, pull.ChangedFiles) if assert.EqualValues(t, 2, pull.ID) { resp = ctx.Session.MakeRequest(t, NewRequest(t, "GET", pull.DiffURL), http.StatusOK) bs, err := io.ReadAll(resp.Body) assert.NoError(t, err) - patch, err := gitdiff.ParsePatch(context.Background(), 1000, 5000, 10, bytes.NewReader(bs), "") + patch, err := gitdiff.ParsePatch(t.Context(), 1000, 5000, 10, bytes.NewReader(bs), "") assert.NoError(t, err) - if assert.Len(t, patch.Files, pull.ChangedFiles) { + if assert.Len(t, patch.Files, 1) { assert.Equal(t, "README.md", patch.Files[0].Name) assert.Equal(t, "README.md", patch.Files[0].OldName) - assert.EqualValues(t, pull.Additions, patch.Files[0].Addition) - assert.EqualValues(t, pull.Deletions, patch.Files[0].Deletion) + assert.Equal(t, 4, patch.Files[0].Addition) + assert.Equal(t, 1, patch.Files[0].Deletion) assert.Equal(t, gitdiff.DiffFileChange, patch.Files[0].Type) } t.Run(fmt.Sprintf("APIGetPullFiles_%d", pull.ID), doAPIGetPullFiles(ctx, pull, func(t *testing.T, files []*api.ChangedFile) { - if assert.Len(t, files, pull.ChangedFiles) { + if assert.Len(t, files, 1) { assert.Equal(t, "README.md", files[0].Filename) // FIXME: The PreviousFilename name should be the same as Filename if it's a file change - assert.Equal(t, "", files[0].PreviousFilename) - assert.EqualValues(t, pull.Additions, files[0].Additions) - assert.EqualValues(t, pull.Deletions, files[0].Deletions) + assert.Empty(t, files[0].PreviousFilename) + assert.Equal(t, 4, files[0].Additions) + assert.Equal(t, 1, files[0].Deletions) assert.Equal(t, "changed", files[0].Status) } })) } - pull = pulls[2] + pull = pulls[0] assert.EqualValues(t, 1, pull.Poster.ID) - assert.Len(t, pull.RequestedReviewers, 1) + assert.Len(t, pull.RequestedReviewers, 2) assert.Empty(t, pull.RequestedReviewersTeams) - assert.EqualValues(t, 1, pull.RequestedReviewers[0].ID) - assert.EqualValues(t, 0, pull.ChangedFiles) + assert.EqualValues(t, 5, pull.RequestedReviewers[0].ID) - if assert.EqualValues(t, 1, pull.ID) { + if assert.EqualValues(t, 5, pull.ID) { resp = ctx.Session.MakeRequest(t, NewRequest(t, "GET", pull.DiffURL), http.StatusOK) bs, err := io.ReadAll(resp.Body) assert.NoError(t, err) - patch, err := gitdiff.ParsePatch(context.Background(), 1000, 5000, 10, bytes.NewReader(bs), "") + patch, err := gitdiff.ParsePatch(t.Context(), 1000, 5000, 10, bytes.NewReader(bs), "") assert.NoError(t, err) - assert.EqualValues(t, pull.ChangedFiles, patch.NumFiles) + assert.Len(t, patch.Files, 1) t.Run(fmt.Sprintf("APIGetPullFiles_%d", pull.ID), doAPIGetPullFiles(ctx, pull, func(t *testing.T, files []*api.ChangedFile) { - assert.Len(t, files, pull.ChangedFiles) + assert.Len(t, files, 1) })) } } @@ -196,7 +200,7 @@ func TestAPICreatePullSuccess(t *testing.T) { session := loginUser(t, owner11.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", owner11.Name), + Head: owner11.Name + ":master", Base: "master", Title: "create a failure pr", }).AddTokenAuth(token) @@ -216,7 +220,7 @@ func TestAPICreatePullBasePermission(t *testing.T) { session := loginUser(t, user4.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", repo11.OwnerName), + Head: repo11.OwnerName + ":master", Base: "master", Title: "create a failure pr", } @@ -244,7 +248,7 @@ func TestAPICreatePullHeadPermission(t *testing.T) { session := loginUser(t, user4.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", repo11.OwnerName), + Head: repo11.OwnerName + ":master", Base: "master", Title: "create a failure pr", } @@ -272,7 +276,7 @@ func TestAPICreatePullSameRepoSuccess(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner.Name, repo.Name), &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:pr-to-update", owner.Name), + Head: owner.Name + ":pr-to-update", Base: "master", Title: "successfully create a PR between branches of the same repository", }).AddTokenAuth(token) @@ -293,7 +297,7 @@ func TestAPICreatePullWithFieldsSuccess(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", owner11.Name), + Head: owner11.Name + ":master", Base: "master", Title: "create a failure pr", Body: "foobaaar", @@ -310,12 +314,12 @@ func TestAPICreatePullWithFieldsSuccess(t *testing.T) { DecodeJSON(t, res, pull) assert.NotNil(t, pull.Milestone) - assert.EqualValues(t, opts.Milestone, pull.Milestone.ID) + assert.Equal(t, opts.Milestone, pull.Milestone.ID) if assert.Len(t, pull.Assignees, 1) { - assert.EqualValues(t, opts.Assignees[0], owner10.Name) + assert.Equal(t, opts.Assignees[0], owner10.Name) } assert.NotNil(t, pull.Labels) - assert.EqualValues(t, opts.Labels[0], pull.Labels[0].ID) + assert.Equal(t, opts.Labels[0], pull.Labels[0].ID) } func TestAPICreatePullWithFieldsFailure(t *testing.T) { @@ -331,7 +335,7 @@ func TestAPICreatePullWithFieldsFailure(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", owner11.Name), + Head: owner11.Name + ":master", Base: "master", } @@ -369,7 +373,7 @@ func TestAPIEditPull(t *testing.T) { apiPull := new(api.PullRequest) resp := MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, apiPull) - assert.EqualValues(t, "master", apiPull.Base.Name) + assert.Equal(t, "master", apiPull.Base.Name) newTitle := "edit a this pr" newBody := "edited body" @@ -380,7 +384,7 @@ func TestAPIEditPull(t *testing.T) { }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, apiPull) - assert.EqualValues(t, "feature/1", apiPull.Base.Name) + assert.Equal(t, "feature/1", apiPull.Base.Name) // check comment history pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID}) err := pull.LoadIssue(db.DefaultContext) @@ -427,3 +431,94 @@ func TestAPICommitPullRequest(t *testing.T) { req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token) ctx.Session.MakeRequest(t, req, http.StatusNotFound) } + +func TestAPIViewPullFilesWithHeadRepoDeleted(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + ctx := NewAPITestContext(t, "user1", baseRepo.Name, auth_model.AccessTokenScopeAll) + + doAPIForkRepository(ctx, "user2")(t) + + forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ForkID: baseRepo.ID, OwnerName: "user1"}) + + // add a new file to the forked repo + addFileToForkedResp, err := files_service.ChangeRepoFiles(git.DefaultContext, forkedRepo, user1, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: "file_1.txt", + ContentReader: strings.NewReader("file1"), + }, + }, + Message: "add file1", + OldBranch: "master", + NewBranch: "fork-branch-1", + Author: &files_service.IdentityOptions{ + GitUserName: user1.Name, + GitUserEmail: user1.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user1.Name, + GitUserEmail: user1.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addFileToForkedResp) + + // create Pull + pullIssue := &issues_model.Issue{ + RepoID: baseRepo.ID, + Title: "Test pull-request-target-event", + PosterID: user1.ID, + Poster: user1, + IsPull: true, + } + pullRequest := &issues_model.PullRequest{ + HeadRepoID: forkedRepo.ID, + BaseRepoID: baseRepo.ID, + HeadBranch: "fork-branch-1", + BaseBranch: "master", + HeadRepo: forkedRepo, + BaseRepo: baseRepo, + Type: issues_model.PullRequestGitea, + } + + prOpts := &pull_service.NewPullRequestOptions{Repo: baseRepo, Issue: pullIssue, PullRequest: pullRequest} + err = pull_service.NewPullRequest(git.DefaultContext, prOpts) + assert.NoError(t, err) + pr := convert.ToAPIPullRequest(t.Context(), pullRequest, user1) + + ctx = NewAPITestContext(t, "user2", baseRepo.Name, auth_model.AccessTokenScopeAll) + doAPIGetPullFiles(ctx, pr, func(t *testing.T, files []*api.ChangedFile) { + if assert.Len(t, files, 1) { + assert.Equal(t, "file_1.txt", files[0].Filename) + assert.Empty(t, files[0].PreviousFilename) + assert.Equal(t, 1, files[0].Additions) + assert.Equal(t, 1, files[0].Changes) + assert.Equal(t, 0, files[0].Deletions) + assert.Equal(t, "added", files[0].Status) + } + })(t) + + // delete the head repository of the pull request + forkCtx := NewAPITestContext(t, "user1", forkedRepo.Name, auth_model.AccessTokenScopeAll) + doAPIDeleteRepository(forkCtx)(t) + + doAPIGetPullFiles(ctx, pr, func(t *testing.T, files []*api.ChangedFile) { + if assert.Len(t, files, 1) { + assert.Equal(t, "file_1.txt", files[0].Filename) + assert.Empty(t, files[0].PreviousFilename) + assert.Equal(t, 1, files[0].Additions) + assert.Equal(t, 1, files[0].Changes) + assert.Equal(t, 0, files[0].Deletions) + assert.Equal(t, "added", files[0].Status) + } + })(t) + }) +} diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index b3d4928b7b..a3dbc0363b 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -97,7 +97,7 @@ func createNewReleaseUsingAPI(t *testing.T, token string, owner *user_model.User Title: newRelease.Title, } unittest.AssertExistsAndLoadBean(t, rel) - assert.EqualValues(t, newRelease.Note, rel.Note) + assert.Equal(t, newRelease.Note, rel.Note) return &newRelease } @@ -151,7 +151,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { Title: newRelease.Title, } unittest.AssertExistsAndLoadBean(t, rel) - assert.EqualValues(t, rel.Note, newRelease.Note) + assert.Equal(t, rel.Note, newRelease.Note) } func TestAPICreateProtectedTagRelease(t *testing.T) { @@ -329,7 +329,7 @@ func TestAPIUploadAssetRelease(t *testing.T) { var attachment *api.Attachment DecodeJSON(t, resp, &attachment) - assert.EqualValues(t, filename, attachment.Name) + assert.Equal(t, filename, attachment.Name) assert.EqualValues(t, 104, attachment.Size) req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=test-asset", bytes.NewReader(body.Bytes())). @@ -340,7 +340,7 @@ func TestAPIUploadAssetRelease(t *testing.T) { var attachment2 *api.Attachment DecodeJSON(t, resp, &attachment2) - assert.EqualValues(t, "test-asset", attachment2.Name) + assert.Equal(t, "test-asset", attachment2.Name) assert.EqualValues(t, 104, attachment2.Size) }) @@ -358,7 +358,7 @@ func TestAPIUploadAssetRelease(t *testing.T) { var attachment *api.Attachment DecodeJSON(t, resp, &attachment) - assert.EqualValues(t, "stream.bin", attachment.Name) + assert.Equal(t, "stream.bin", attachment.Name) assert.EqualValues(t, 104, attachment.Size) }) } diff --git a/tests/integration/api_repo_archive_test.go b/tests/integration/api_repo_archive_test.go index 8589199da3..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" @@ -48,7 +50,7 @@ func TestAPIDownloadArchive(t *testing.T) { bs2, err := io.ReadAll(resp.Body) assert.NoError(t, err) // The locked URL should give the same bytes as the non-locked one - assert.EqualValues(t, bs, bs2) + assert.Equal(t, bs, bs2) link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.bundle", user2.Name, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) @@ -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}) @@ -88,14 +93,20 @@ func TestAPIDownloadArchive2(t *testing.T) { bs2, err := io.ReadAll(resp.Body) assert.NoError(t, err) // The locked URL should give the same bytes as the non-locked one - assert.EqualValues(t, bs, bs2) + assert.Equal(t, bs, bs2) link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/bundle/master", user2.Name, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) 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_branch_test.go b/tests/integration/api_repo_branch_test.go index 63080b308c..066eb366b1 100644 --- a/tests/integration/api_repo_branch_test.go +++ b/tests/integration/api_repo_branch_test.go @@ -4,11 +4,11 @@ package integration import ( - "bytes" "fmt" "io" "net/http" "net/url" + "strings" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -42,8 +42,8 @@ func TestAPIRepoBranchesPlain(t *testing.T) { var branches []*api.Branch assert.NoError(t, json.Unmarshal(bs, &branches)) assert.Len(t, branches, 2) - assert.EqualValues(t, "test_branch", branches[0].Name) - assert.EqualValues(t, "master", branches[1].Name) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "master", branches[1].Name) link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name)) MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden) @@ -53,20 +53,20 @@ func TestAPIRepoBranchesPlain(t *testing.T) { assert.NoError(t, err) var branch api.Branch assert.NoError(t, json.Unmarshal(bs, &branch)) - assert.EqualValues(t, "test_branch", branch.Name) + assert.Equal(t, "test_branch", branch.Name) MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden) req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) req.Header.Add("Content-Type", "application/json") - req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) resp = MakeRequest(t, req, http.StatusCreated) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) var branch2 api.Branch assert.NoError(t, json.Unmarshal(bs, &branch2)) - assert.EqualValues(t, "test_branch2", branch2.Name) - assert.EqualValues(t, branch.Commit.ID, branch2.Commit.ID) + assert.Equal(t, "test_branch2", branch2.Name) + assert.Equal(t, branch.Commit.ID, branch2.Commit.ID) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err = io.ReadAll(resp.Body) @@ -75,9 +75,9 @@ func TestAPIRepoBranchesPlain(t *testing.T) { branches = []*api.Branch{} assert.NoError(t, json.Unmarshal(bs, &branches)) assert.Len(t, branches, 3) - assert.EqualValues(t, "test_branch", branches[0].Name) - assert.EqualValues(t, "test_branch2", branches[1].Name) - assert.EqualValues(t, "master", branches[2].Name) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "test_branch2", branches[1].Name) + assert.Equal(t, "master", branches[2].Name) link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name)) MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound) @@ -104,8 +104,8 @@ func TestAPIRepoBranchesMirror(t *testing.T) { var branches []*api.Branch assert.NoError(t, json.Unmarshal(bs, &branches)) assert.Len(t, branches, 2) - assert.EqualValues(t, "test_branch", branches[0].Name) - assert.EqualValues(t, "master", branches[1].Name) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "master", branches[1].Name) link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo5.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) @@ -113,18 +113,18 @@ func TestAPIRepoBranchesMirror(t *testing.T) { assert.NoError(t, err) var branch api.Branch assert.NoError(t, json.Unmarshal(bs, &branch)) - assert.EqualValues(t, "test_branch", branch.Name) + assert.Equal(t, "test_branch", branch.Name) req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) req.Header.Add("Content-Type", "application/json") - req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) resp = MakeRequest(t, req, http.StatusForbidden) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) + assert.Equal(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()).AddTokenAuth(token), http.StatusForbidden) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) + assert.Equal(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) } diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go index 463db1dfb1..11e2924e84 100644 --- a/tests/integration/api_repo_collaborator_test.go +++ b/tests/integration/api_repo_collaborator_test.go @@ -5,7 +5,6 @@ package integration import ( "net/http" - "net/url" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -14,132 +13,145 @@ import ( "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" ) func TestAPIRepoCollaboratorPermission(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}) + defer tests.PrepareTestEnv(t)() + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}) - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) - user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}) - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) + user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}) + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) + testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) - t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "owner", repoPermission.Permission) - }) + assert.Equal(t, "owner", repoPermission.Permission) + }) - t.Run("CollaboratorWithReadAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) + t.Run("CollaboratorWithReadAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "read", repoPermission.Permission) - }) + assert.Equal(t, "read", repoPermission.Permission) + }) - t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) + t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "write", repoPermission.Permission) - }) + assert.Equal(t, "write", repoPermission.Permission) + }) - t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) + t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "admin", repoPermission.Permission) - }) + assert.Equal(t, "admin", repoPermission.Permission) + }) - t.Run("CollaboratorNotFound", func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user"). - AddTokenAuth(testCtx.Token) - MakeRequest(t, req, http.StatusNotFound) - }) + t.Run("CollaboratorNotFound", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user"). + AddTokenAuth(testCtx.Token) + MakeRequest(t, req, http.StatusNotFound) + }) - t.Run("CollaboratorBlocked", func(t *testing.T) { - ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) - ctx.ExpectedCode = http.StatusForbidden - doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t) - }) + t.Run("CollaboratorBlocked", func(t *testing.T) { + ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) + ctx.ExpectedCode = http.StatusForbidden + doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t) + }) + + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). + AddTokenAuth(_testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + assert.Equal(t, "read", repoPermission.Permission) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) + t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) { + session := loginUser(t, user5.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "read", repoPermission.Permission) + repoCollPerm := api.RepoCollaboratorPermission{} + DecodeJSON(t, resp, &repoCollPerm) + + assert.Equal(t, "read", repoCollPerm.Permission) }) + }) - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) - _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). + AddTokenAuth(_testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "read", repoPermission.Permission) - }) + assert.Equal(t, "read", repoPermission.Permission) + }) - t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin)) - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead)) + t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin)) + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead)) - _session := loginUser(t, user10.Name) - _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + _session := loginUser(t, user10.Name) + _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name). + AddTokenAuth(_testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) - var repoPermission api.RepoCollaboratorPermission - DecodeJSON(t, resp, &repoPermission) + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) - assert.Equal(t, "read", repoPermission.Permission) - }) + assert.Equal(t, "read", repoPermission.Permission) }) } diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index 41ad7211ff..af3bc54680 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -4,12 +4,11 @@ package integration import ( - stdCtx "context" "encoding/base64" "fmt" "net/http" "net/url" - "path/filepath" + "path" "testing" "time" @@ -20,6 +19,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/context" "github.com/stretchr/testify/assert" @@ -50,28 +50,42 @@ func getCreateFileOptions() api.CreateFileOptions { } } -func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCommitSHA string) *api.FileResponse { +func normalizeFileContentResponseCommitTime(c *api.ContentsResponse) { + // decoded JSON response may contain different timezone from the one parsed by git commit + // so we need to normalize the time to UTC to make "assert.Equal" pass + c.LastCommitterDate = util.ToPointer(c.LastCommitterDate.UTC()) + c.LastAuthorDate = util.ToPointer(c.LastAuthorDate.UTC()) +} + +type apiFileResponseInfo struct { + repoFullName, commitID, treePath, lastCommitSHA string + lastCommitterWhen, lastAuthorWhen time.Time +} + +func getExpectedFileResponseForCreate(info apiFileResponseInfo) *api.FileResponse { sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" encoding := "base64" content := "VGhpcyBpcyBuZXcgdGV4dA==" - selfURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master" - htmlURL := setting.AppURL + repoFullName + "/src/branch/master/" + treePath - gitURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha - downloadURL := setting.AppURL + repoFullName + "/raw/branch/master/" + treePath - return &api.FileResponse{ + selfURL := setting.AppURL + "api/v1/repos/" + info.repoFullName + "/contents/" + info.treePath + "?ref=master" + htmlURL := setting.AppURL + info.repoFullName + "/src/branch/master/" + info.treePath + gitURL := setting.AppURL + "api/v1/repos/" + info.repoFullName + "/git/blobs/" + sha + downloadURL := setting.AppURL + info.repoFullName + "/raw/branch/master/" + info.treePath + ret := &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: latestCommitSHA, - Size: 16, - Type: "file", - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: path.Base(info.treePath), + Path: info.treePath, + SHA: sha, + LastCommitSHA: util.ToPointer(info.lastCommitSHA), + LastCommitterDate: util.ToPointer(info.lastCommitterWhen), + LastAuthorDate: util.ToPointer(info.lastAuthorWhen), + Size: 16, + Type: "file", + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -80,10 +94,10 @@ func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCo }, Commit: &api.FileCommitResponse{ CommitMeta: api.CommitMeta{ - URL: setting.AppURL + "api/v1/repos/" + repoFullName + "/git/commits/" + commitID, - SHA: commitID, + URL: setting.AppURL + "api/v1/repos/" + info.repoFullName + "/git/commits/" + info.commitID, + SHA: info.commitID, }, - HTMLURL: setting.AppURL + repoFullName + "/commit/" + commitID, + HTMLURL: setting.AppURL + info.repoFullName + "/commit/" + info.commitID, Author: &api.CommitUser{ Identity: api.Identity{ Name: "Anne Doe", @@ -107,6 +121,8 @@ func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCo Payload: "", }, } + normalizeFileContentResponseCommitTime(ret.Content) + return ret } func BenchmarkAPICreateFileSmall(b *testing.B) { @@ -115,7 +131,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) } @@ -130,7 +146,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) @@ -167,22 +183,30 @@ func TestAPICreateFile(t *testing.T) { req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusCreated) - gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), repo1) + gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) + defer gitRepo.Close() commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) - latestCommit, _ := gitRepo.GetCommitByPath(treePath) - expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath, latestCommit.ID.String()) + lastCommit, _ := gitRepo.GetCommitByPath(treePath) + expectedFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{ + repoFullName: "user2/repo1", + commitID: commitID, + treePath: treePath, + lastCommitSHA: lastCommit.ID.String(), + lastCommitterWhen: lastCommit.Committer.When, + lastAuthorWhen: lastCommit.Author.When, + }) var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) - gitRepo.Close() + normalizeFileContentResponseCommitTime(fileResponse.Content) + assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) + assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + assert.Equal(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) + assert.Equal(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) + assert.Equal(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) + assert.Equal(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) } // Test creating a file in a new branch @@ -199,10 +223,10 @@ func TestAPICreateFile(t *testing.T) { expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) - assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) + assert.Equal(t, expectedSHA, fileResponse.Content.SHA) + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.Equal(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.Equal(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) // Test creating a file without a message createFileOptions = getCreateFileOptions() @@ -214,7 +238,7 @@ func TestAPICreateFile(t *testing.T) { resp = MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &fileResponse) expectedMessage := "Add " + treePath + "\n" - assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + assert.Equal(t, expectedMessage, fileResponse.Commit.Message) // Test trying to create a file that already exists, should fail createFileOptions = getCreateFileOptions() @@ -285,20 +309,28 @@ func TestAPICreateFile(t *testing.T) { AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusCreated) emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}) // public repo - gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), emptyRepo) + gitRepo, _ := gitrepo.OpenRepository(t.Context(), emptyRepo) + defer gitRepo.Close() commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) latestCommit, _ := gitRepo.GetCommitByPath(treePath) - expectedFileResponse := getExpectedFileResponseForCreate("user2/empty-repo", commitID, treePath, latestCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{ + repoFullName: "user2/empty-repo", + commitID: commitID, + treePath: treePath, + lastCommitSHA: latestCommit.ID.String(), + lastCommitterWhen: latestCommit.Committer.When, + lastAuthorWhen: latestCommit.Author.When, + }) DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) - gitRepo.Close() + normalizeFileContentResponseCommitTime(fileResponse.Content) + assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) + assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + assert.Equal(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) + assert.Equal(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) + assert.Equal(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) + assert.Equal(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) }) } diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go index 7c93307e19..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", } } @@ -87,7 +89,7 @@ func TestAPIDeleteFile(t *testing.T) { DecodeJSON(t, resp, &fileResponse) assert.NotNil(t, fileResponse) assert.Nil(t, fileResponse.Content) - assert.EqualValues(t, deleteFileOptions.Message+"\n", fileResponse.Commit.Message) + assert.Equal(t, deleteFileOptions.Message+"\n", fileResponse.Commit.Message) // Test deleting file without a message fileID++ @@ -100,7 +102,7 @@ func TestAPIDeleteFile(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) expectedMessage := "Delete " + treePath + "\n" - assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + assert.Equal(t, expectedMessage, fileResponse.Commit.Message) // Test deleting a file with the wrong SHA fileID++ @@ -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_get_test.go b/tests/integration/api_repo_file_get_test.go index 2f897093ee..379851b689 100644 --- a/tests/integration/api_repo_file_get_test.go +++ b/tests/integration/api_repo_file_get_test.go @@ -44,8 +44,6 @@ func TestAPIGetRawFileOrLFS(t *testing.T) { reqLFS := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/"+lfs) respLFS := MakeRequestNilResponseRecorder(t, reqLFS, http.StatusOK) assert.Equal(t, testFileSizeSmall, respLFS.Length) - - doAPIDeleteRepository(httpContext) }) }) } diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index ac28e0c0a2..9a56711da6 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -4,12 +4,11 @@ package integration import ( - stdCtx "context" "encoding/base64" "fmt" "net/http" "net/url" - "path/filepath" + "path" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -19,6 +18,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/context" "github.com/stretchr/testify/assert" @@ -28,7 +28,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", @@ -48,28 +48,30 @@ func getUpdateFileOptions() *api.UpdateFileOptions { } } -func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) *api.FileResponse { +func getExpectedFileResponseForUpdate(info apiFileResponseInfo) *api.FileResponse { sha := "08bd14b2e2852529157324de9c226b3364e76136" encoding := "base64" content := "VGhpcyBpcyB1cGRhdGVkIHRleHQ=" - selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" - htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath + selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + info.treePath + "?ref=master" + htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + info.treePath gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha - downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath - return &api.FileResponse{ + downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + info.treePath + ret := &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 20, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: path.Base(info.treePath), + Path: info.treePath, + SHA: sha, + LastCommitSHA: util.ToPointer(info.lastCommitSHA), + LastCommitterDate: util.ToPointer(info.lastCommitterWhen), + LastAuthorDate: util.ToPointer(info.lastAuthorWhen), + Type: "file", + Size: 20, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -78,10 +80,10 @@ func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) }, Commit: &api.FileCommitResponse{ CommitMeta: api.CommitMeta{ - URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID, - SHA: commitID, + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + info.commitID, + SHA: info.commitID, }, - HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, + HTMLURL: setting.AppURL + "user2/repo1/commit/" + info.commitID, Author: &api.CommitUser{ Identity: api.Identity{ Name: "John Doe", @@ -103,6 +105,8 @@ func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) Payload: "", }, } + normalizeFileContentResponseCommitTime(ret.Content) + return ret } func TestAPIUpdateFile(t *testing.T) { @@ -135,18 +139,25 @@ func TestAPIUpdateFile(t *testing.T) { req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusOK) - gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), repo1) + gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) + defer gitRepo.Close() commitID, _ := gitRepo.GetBranchCommitID(updateFileOptions.NewBranchName) lasCommit, _ := gitRepo.GetCommitByPath(treePath) - expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath, lasCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForUpdate(apiFileResponseInfo{ + commitID: commitID, + treePath: treePath, + lastCommitSHA: lasCommit.ID.String(), + lastCommitterWhen: lasCommit.Committer.When, + lastAuthorWhen: lasCommit.Author.When, + }) var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - gitRepo.Close() + normalizeFileContentResponseCommitTime(fileResponse.Content) + assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) + assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) } // Test updating a file in a new branch @@ -164,10 +175,10 @@ func TestAPIUpdateFile(t *testing.T) { expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) - assert.EqualValues(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) + assert.Equal(t, expectedSHA, fileResponse.Content.SHA) + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.Equal(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.Equal(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) // Test updating a file and renaming it updateFileOptions = getUpdateFileOptions() @@ -184,9 +195,9 @@ func TestAPIUpdateFile(t *testing.T) { expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.Equal(t, expectedSHA, fileResponse.Content.SHA) + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.Equal(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) // Test updating a file without a message updateFileOptions = getUpdateFileOptions() @@ -200,7 +211,7 @@ func TestAPIUpdateFile(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) expectedMessage := "Update " + treePath + "\n" - assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + assert.Equal(t, expectedMessage, fileResponse.Commit.Message) // Test updating a file with the wrong SHA fileID++ diff --git a/tests/integration/api_repo_files_change_test.go b/tests/integration/api_repo_files_change_test.go index fb3ae5e4dd..999bcdc680 100644 --- a/tests/integration/api_repo_files_change_test.go +++ b/tests/integration/api_repo_files_change_test.go @@ -4,7 +4,6 @@ package integration import ( - stdCtx "context" "encoding/base64" "fmt" "net/http" @@ -78,51 +77,56 @@ func TestAPIChangeFiles(t *testing.T) { token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Test changing files in repo1 which user2 owns, try both with branch and empty branch - for _, branch := range [...]string{ - "master", // Branch - "", // Empty branch - } { - fileID++ - createTreePath := fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - changeFilesOptions := getChangeFilesOptions() - changeFilesOptions.BranchName = branch - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). - AddTokenAuth(token2) - resp := MakeRequest(t, req, http.StatusCreated) - gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), repo1) - commitID, _ := gitRepo.GetBranchCommitID(changeFilesOptions.NewBranchName) - createLasCommit, _ := gitRepo.GetCommitByPath(createTreePath) - updateLastCommit, _ := gitRepo.GetCommitByPath(updateTreePath) - expectedCreateFileResponse := getExpectedFileResponseForCreate(fmt.Sprintf("%v/%v", user2.Name, repo1.Name), commitID, createTreePath, createLasCommit.ID.String()) - expectedUpdateFileResponse := getExpectedFileResponseForUpdate(commitID, updateTreePath, updateLastCommit.ID.String()) - var filesResponse api.FilesResponse - DecodeJSON(t, resp, &filesResponse) - - // check create file - assert.EqualValues(t, expectedCreateFileResponse.Content, filesResponse.Files[0]) - - // check update file - assert.EqualValues(t, expectedUpdateFileResponse.Content, filesResponse.Files[1]) - - // test commit info - assert.EqualValues(t, expectedCreateFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.EqualValues(t, expectedCreateFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) - assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) - assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Email, filesResponse.Commit.Committer.Email) - assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Name, filesResponse.Commit.Committer.Name) - - // test delete file - assert.Nil(t, filesResponse.Files[2]) - - gitRepo.Close() + for _, branch := range []string{"master", ""} { + t.Run("Branch-"+branch, func(t *testing.T) { + fileID++ + createTreePath := fmt.Sprintf("new/file%d.txt", fileID) + updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) + deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) + _, _ = createFile(user2, repo1, updateTreePath) + _, _ = createFile(user2, repo1, deleteTreePath) + changeFilesOptions := getChangeFilesOptions() + changeFilesOptions.BranchName = branch + changeFilesOptions.Files[0].Path = createTreePath + changeFilesOptions.Files[1].Path = updateTreePath + changeFilesOptions.Files[2].Path = deleteTreePath + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). + AddTokenAuth(token2) + resp := MakeRequest(t, req, http.StatusCreated) + gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) + defer gitRepo.Close() + commitID, _ := gitRepo.GetBranchCommitID(changeFilesOptions.NewBranchName) + createLasCommit, _ := gitRepo.GetCommitByPath(createTreePath) + updateLastCommit, _ := gitRepo.GetCommitByPath(updateTreePath) + expectedCreateFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{ + repoFullName: fmt.Sprintf("%s/%s", user2.Name, repo1.Name), + commitID: commitID, + treePath: createTreePath, + lastCommitSHA: createLasCommit.ID.String(), + lastCommitterWhen: createLasCommit.Committer.When, + lastAuthorWhen: createLasCommit.Author.When, + }) + expectedUpdateFileResponse := getExpectedFileResponseForUpdate(apiFileResponseInfo{ + commitID: commitID, + treePath: updateTreePath, + lastCommitSHA: updateLastCommit.ID.String(), + lastCommitterWhen: updateLastCommit.Committer.When, + lastAuthorWhen: updateLastCommit.Author.When, + }) + var filesResponse api.FilesResponse + DecodeJSON(t, resp, &filesResponse) + normalizeFileContentResponseCommitTime(filesResponse.Files[0]) + normalizeFileContentResponseCommitTime(filesResponse.Files[1]) + assert.Equal(t, expectedCreateFileResponse.Content, filesResponse.Files[0]) // check create file + assert.Equal(t, expectedUpdateFileResponse.Content, filesResponse.Files[1]) // check update file + assert.Equal(t, expectedCreateFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedCreateFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + assert.Equal(t, expectedCreateFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) + assert.Equal(t, expectedCreateFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) + assert.Equal(t, expectedCreateFileResponse.Commit.Committer.Email, filesResponse.Commit.Committer.Email) + assert.Equal(t, expectedCreateFileResponse.Commit.Committer.Name, filesResponse.Commit.Committer.Name) + assert.Nil(t, filesResponse.Files[2]) // test delete file + }) } // Test changing files in a new branch @@ -150,15 +154,15 @@ func TestAPIChangeFiles(t *testing.T) { expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136" expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) - assert.EqualValues(t, expectedCreateSHA, filesResponse.Files[0].SHA) - assert.EqualValues(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) - assert.EqualValues(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) - assert.EqualValues(t, expectedUpdateSHA, filesResponse.Files[1].SHA) - assert.EqualValues(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) - assert.EqualValues(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) + assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) + assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) + assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA) + assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) + assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) assert.Nil(t, filesResponse.Files[2]) - assert.EqualValues(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) + assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) // Test updating a file and renaming it changeFilesOptions = getChangeFilesOptions() @@ -176,9 +180,9 @@ func TestAPIChangeFiles(t *testing.T) { expectedUpdateSHA = "08bd14b2e2852529157324de9c226b3364e76136" expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) - assert.EqualValues(t, expectedUpdateSHA, filesResponse.Files[0].SHA) - assert.EqualValues(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL) - assert.EqualValues(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL) + assert.Equal(t, expectedUpdateSHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL) + assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL) // Test updating a file without a message changeFilesOptions = getChangeFilesOptions() @@ -198,7 +202,7 @@ func TestAPIChangeFiles(t *testing.T) { resp = MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &filesResponse) expectedMessage := fmt.Sprintf("Add %v\nUpdate %v\nDelete %v\n", createTreePath, updateTreePath, deleteTreePath) - assert.EqualValues(t, expectedMessage, filesResponse.Commit.Message) + assert.Equal(t, expectedMessage, filesResponse.Commit.Message) // Test updating a file with the wrong SHA fileID++ diff --git a/tests/integration/api_repo_files_get_test.go b/tests/integration/api_repo_files_get_test.go new file mode 100644 index 0000000000..a4ded7da3f --- /dev/null +++ b/tests/integration/api_repo_files_get_test.go @@ -0,0 +1,157 @@ +// 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" + 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/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAPIGetRequestedFiles(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo1) + assert.NoError(t, err) + defer gitRepo.Close() + lastCommit, _ := gitRepo.GetCommitByPath("README.md") + + requestFiles := func(t *testing.T, url string, files []string, expectedStatusCode ...int) (ret []*api.ContentsResponse) { + req := NewRequestWithJSON(t, "POST", url, &api.GetFilesOptions{Files: files}) + resp := MakeRequest(t, req, util.OptionalArg(expectedStatusCode, http.StatusOK)) + if resp.Code != http.StatusOK { + return nil + } + DecodeJSON(t, resp, &ret) + return ret + } + + t.Run("User2Get", func(t *testing.T) { + reqBodyOpt := &api.GetFilesOptions{Files: []string{"README.md"}} + reqBodyParam, _ := json.Marshal(reqBodyOpt) + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/file-contents?body="+url.QueryEscape(string(reqBodyParam))) + resp := MakeRequest(t, req, http.StatusOK) + var ret []*api.ContentsResponse + DecodeJSON(t, resp, &ret) + expected := []*api.ContentsResponse{getExpectedContentsResponseForContents(repo1.DefaultBranch, "branch", lastCommit.ID.String())} + assert.Equal(t, expected, ret) + }) + t.Run("User2NoRef", func(t *testing.T) { + ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents", []string{"README.md"}) + expected := []*api.ContentsResponse{getExpectedContentsResponseForContents(repo1.DefaultBranch, "branch", lastCommit.ID.String())} + assert.Equal(t, expected, ret) + }) + t.Run("User2RefBranch", func(t *testing.T) { + ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=master", []string{"README.md"}) + expected := []*api.ContentsResponse{getExpectedContentsResponseForContents(repo1.DefaultBranch, "branch", lastCommit.ID.String())} + assert.Equal(t, expected, ret) + }) + t.Run("User2RefTag", func(t *testing.T) { + ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=v1.1", []string{"README.md"}) + expected := []*api.ContentsResponse{getExpectedContentsResponseForContents("v1.1", "tag", lastCommit.ID.String())} + assert.Equal(t, expected, ret) + }) + t.Run("User2RefCommit", func(t *testing.T) { + ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=65f1bf27bc3bf70f64657658635e66094edbcb4d", []string{"README.md"}) + expected := []*api.ContentsResponse{getExpectedContentsResponseForContents("65f1bf27bc3bf70f64657658635e66094edbcb4d", "commit", lastCommit.ID.String())} + assert.Equal(t, expected, ret) + }) + t.Run("User2RefNotExist", func(t *testing.T) { + ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=not-exist", []string{"README.md"}, http.StatusNotFound) + assert.Empty(t, ret) + }) + + t.Run("PermissionCheck", func(t *testing.T) { + filesOptions := &api.GetFilesOptions{Files: []string{"README.md"}} + // Test accessing private ref with user token that does not have access - should fail + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", user2.Name, repo16.Name), &filesOptions).AddTokenAuth(token4) + MakeRequest(t, req, http.StatusNotFound) + // Test access private ref of owner of token + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", user2.Name, repo16.Name), &filesOptions).AddTokenAuth(token2) + MakeRequest(t, req, http.StatusOK) + // Test access of org org3 private repo file by owner user2 + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", org3.Name, repo3.Name), &filesOptions).AddTokenAuth(token2) + MakeRequest(t, req, http.StatusOK) + }) + + t.Run("ResponseList", func(t *testing.T) { + defer test.MockVariableValue(&setting.API.DefaultPagingNum)() + defer test.MockVariableValue(&setting.API.DefaultMaxBlobSize)() + defer test.MockVariableValue(&setting.API.DefaultMaxResponseSize)() + + type expected struct { + Name string + HasContent bool + } + assertResponse := func(t *testing.T, expected []*expected, ret []*api.ContentsResponse) { + require.Len(t, ret, len(expected)) + for i, e := range expected { + if e == nil { + assert.Nil(t, ret[i], "item %d", i) + continue + } + assert.Equal(t, e.Name, ret[i].Name, "item %d name", i) + if e.HasContent { + require.NotNil(t, ret[i].Content, "item %d content", i) + assert.NotEmpty(t, *ret[i].Content, "item %d content", i) + } else { + assert.Nil(t, ret[i].Content, "item %d content", i) + } + } + } + + // repo1 "DefaultBranch" has 2 files: LICENSE (1064 bytes), README.md (30 bytes) + ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"}) + assertResponse(t, []*expected{nil, {"LICENSE", true}, {"README.md", true}}, ret) + + // the returned file list is limited by the DefaultPagingNum + setting.API.DefaultPagingNum = 2 + ret = requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"}) + assertResponse(t, []*expected{nil, {"LICENSE", true}}, ret) + setting.API.DefaultPagingNum = 100 + + // if a file exceeds the DefaultMaxBlobSize, the content is not returned + setting.API.DefaultMaxBlobSize = 200 + ret = requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"}) + assertResponse(t, []*expected{nil, {"LICENSE", false}, {"README.md", true}}, ret) + setting.API.DefaultMaxBlobSize = 20000 + + // if the total response size would exceed the DefaultMaxResponseSize, then the list stops + setting.API.DefaultMaxResponseSize = ret[1].Size*4/3 + 10 + ret = requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"}) + assertResponse(t, []*expected{nil, {"LICENSE", true}}, ret) + setting.API.DefaultMaxBlobSize = 20000 + }) +} diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go index 1ba74490a3..563d6fcc10 100644 --- a/tests/integration/api_repo_get_contents_list_test.go +++ b/tests/integration/api_repo_get_contents_list_test.go @@ -6,8 +6,9 @@ package integration import ( "net/http" "net/url" - "path/filepath" + "path" "testing" + "time" auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" @@ -17,6 +18,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" repo_service "code.gitea.io/gitea/services/repository" "github.com/stretchr/testify/assert" @@ -31,16 +33,18 @@ func getExpectedContentsListResponseForContents(ref, refType, lastCommitSHA stri downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath return []*api.ContentsResponse{ { - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 30, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: path.Base(treePath), + Path: treePath, + SHA: sha, + LastCommitSHA: util.ToPointer(lastCommitSHA), + LastCommitterDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))), + LastAuthorDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))), + Type: "file", + Size: 30, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -62,7 +66,6 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo - treePath := "" // root dir // Get user2's token session := loginUser(t, user2.Name) @@ -91,7 +94,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // ref is default ref ref := repo1.DefaultBranch refType := "branch" - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents?ref=%s", user2.Name, repo1.Name, ref) resp := MakeRequest(t, req, http.StatusOK) var contentsListResponse []*api.ContentsResponse DecodeJSON(t, resp, &contentsListResponse) @@ -99,22 +102,22 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { lastCommit, err := gitRepo.GetCommitByPath("README.md") assert.NoError(t, err) expectedContentsListResponse := getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // No ref refType = "branch" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo1.Name) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) expectedContentsListResponse = getExpectedContentsListResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String()) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // ref is the branch we created above in setup ref = newBranch refType = "branch" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents?ref=%s", user2.Name, repo1.Name, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) @@ -123,12 +126,12 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { lastCommit, err = branchCommit.GetCommitByPath("README.md") assert.NoError(t, err) expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // ref is the new tag we created above in setup ref = newTag refType = "tag" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) @@ -137,35 +140,35 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { lastCommit, err = tagCommit.GetCommitByPath("README.md") assert.NoError(t, err) expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // ref is a commit ref = commitID refType = "commit" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, commitID) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // Test file contents a file with a bad ref ref = "badref" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref) MakeRequest(t, req, http.StatusNotFound) // Test accessing private ref with user token that does not have access - should fail - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo16.Name). AddTokenAuth(token4) MakeRequest(t, req, http.StatusNotFound) // Test access private ref of owner of token - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md", user2.Name, repo16.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo16.Name). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) // Test access of org org3 private repo file by owner user2 - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", org3.Name, repo3.Name). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) } diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go index d0f61da0c0..33df74f6ee 100644 --- a/tests/integration/api_repo_get_contents_test.go +++ b/tests/integration/api_repo_get_contents_test.go @@ -7,7 +7,9 @@ import ( "io" "net/http" "net/url" + "slices" "testing" + "time" auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" @@ -17,34 +19,33 @@ 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" 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 { treePath := "README.md" - sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f" - encoding := "base64" - content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x" selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=" + ref htmlURL := setting.AppURL + "user2/repo1/src/" + refType + "/" + ref + "/" + treePath - gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha - downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath + gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f" return &api.ContentsResponse{ - Name: treePath, - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 30, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: treePath, + Path: treePath, + SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + LastCommitSHA: util.ToPointer(lastCommitSHA), + LastCommitterDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))), + LastAuthorDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))), + Type: "file", + Size: 30, + Encoding: util.ToPointer("base64"), + Content: util.ToPointer("IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"), + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: util.ToPointer(setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath), Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -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,54 +81,56 @@ 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 ***/ + // not found + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/no-such/file.md", user2.Name, repo1.Name) + resp := MakeRequest(t, req, http.StatusNotFound) + assert.Contains(t, resp.Body.String(), "object does not exist [id: , rel_path: no-such]") + // ref is default ref ref := repo1.DefaultBranch refType := "branch" - 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) + 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) 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.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // No ref refType = "branch" 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.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) - // ref is the branch we created above in setup + // ref is the branch we created above in setup ref = newBranch refType = "branch" 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()) - assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // ref is the new tag we created above in setup ref = newTag @@ -131,11 +138,10 @@ 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()) - assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // ref is a commit ref = commitID @@ -143,9 +149,8 @@ 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.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // Test file contents a file with a bad ref ref = "badref" @@ -168,9 +173,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" @@ -178,22 +181,22 @@ func TestAPIGetContentsRefFormats(t *testing.T) { resp := MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/"+file), http.StatusOK) raw, err := io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, content, string(raw)) + assert.Equal(t, content, string(raw)) resp = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/"+sha+"/"+file), http.StatusOK) raw, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, content, string(raw)) + assert.Equal(t, content, string(raw)) resp = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/"+file+"?ref="+sha), http.StatusOK) raw, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, content, string(raw)) + assert.Equal(t, content, string(raw)) resp = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/"+file+"?ref=master"), http.StatusOK) raw, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, content, string(raw)) + assert.Equal(t, content, string(raw)) _ = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/docs/README.md?ref=main"), http.StatusNotFound) _ = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/README.md?ref=main"), http.StatusOK) @@ -203,3 +206,98 @@ 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?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.NotNil(t, contentsResponse.DirContents) + + req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/.?ref=sub-home-md-img-check") + resp = MakeRequest(t, req, http.StatusOK) + contentsResponse = api.ContentsExtResponse{} + DecodeJSON(t, resp, &contentsResponse) + assert.Nil(t, contentsResponse.FileContents) + assert.NotNil(t, contentsResponse.DirContents) + + req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check") + 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) + assert.Nil(t, contentsResponse.DirContents[0].LastCommitSHA) + assert.Nil(t, contentsResponse.DirContents[0].LastCommitMessage) + + // "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 or commit info 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) + assert.Nil(t, contentsResponse.FileContents.LastCommitSHA) + assert.Nil(t, contentsResponse.FileContents.LastCommitMessage) + + // 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,commit_metadata,commit_message") + 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) + assert.Equal(t, "4649299398e4d39a5c09eb4f534df6f1e1eb87cc", *contentsResponse.FileContents.LastCommitSHA) + assert.Equal(t, "Test how READMEs render images when found in a subfolder\n", *contentsResponse.FileContents.LastCommitMessage) + + 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.Nil(t, contentsResponse.FileContents.LastCommitSHA) + assert.Nil(t, contentsResponse.FileContents.LastCommitMessage) + assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize) + assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid) + }) +} diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go index 184362e7e3..d4274bdb40 100644 --- a/tests/integration/api_repo_git_blobs_test.go +++ b/tests/integration/api_repo_git_blobs_test.go @@ -41,7 +41,7 @@ func TestAPIReposGitBlobs(t *testing.T) { DecodeJSON(t, resp, &gitBlobResponse) assert.NotNil(t, gitBlobResponse) expectedContent := "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK" - assert.Equal(t, expectedContent, gitBlobResponse.Content) + assert.Equal(t, expectedContent, *gitBlobResponse.Content) // Tests a private repo with no token so will fail req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo16.Name, repo16ReadmeSHA) @@ -72,7 +72,7 @@ func TestAPIReposGitBlobs(t *testing.T) { // Login as User4. session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) diff --git a/tests/integration/api_repo_git_commits_test.go b/tests/integration/api_repo_git_commits_test.go index c4c626eb49..a1584d4629 100644 --- a/tests/integration/api_repo_git_commits_test.go +++ b/tests/integration/api_repo_git_commits_test.go @@ -72,12 +72,12 @@ func TestAPIReposGitCommitList(t *testing.T) { DecodeJSON(t, resp, &apiData) assert.Len(t, apiData, 2) - assert.EqualValues(t, "cfe3b3c1fd36fba04f9183287b106497e1afe986", apiData[0].CommitMeta.SHA) + assert.Equal(t, "cfe3b3c1fd36fba04f9183287b106497e1afe986", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"link_hi", "test.csv"}, apiData[0].Files) - assert.EqualValues(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[1].CommitMeta.SHA) + assert.Equal(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[1].CommitMeta.SHA) compareCommitFiles(t, []string{"test.csv"}, apiData[1].Files) - assert.EqualValues(t, "2", resp.Header().Get("X-Total")) + assert.Equal(t, "2", resp.Header().Get("X-Total")) } func TestAPIReposGitCommitListNotMaster(t *testing.T) { @@ -96,14 +96,14 @@ func TestAPIReposGitCommitListNotMaster(t *testing.T) { DecodeJSON(t, resp, &apiData) assert.Len(t, apiData, 3) - assert.EqualValues(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", apiData[0].CommitMeta.SHA) + assert.Equal(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) - assert.EqualValues(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", apiData[1].CommitMeta.SHA) + assert.Equal(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", apiData[1].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[1].Files) - assert.EqualValues(t, "5099b81332712fe655e34e8dd63574f503f61811", apiData[2].CommitMeta.SHA) + assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", apiData[2].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[2].Files) - assert.EqualValues(t, "3", resp.Header().Get("X-Total")) + assert.Equal(t, "3", resp.Header().Get("X-Total")) } func TestAPIReposGitCommitListPage2Empty(t *testing.T) { @@ -177,7 +177,7 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) { reqDiff := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.diff", user.Name). AddTokenAuth(token) resp := MakeRequest(t, reqDiff, http.StatusOK) - assert.EqualValues(t, + assert.Equal(t, "commit f27c2b2b03dcab38beaf89b0ab4ff61f6de63441\nAuthor: User2 <user2@example.com>\nDate: Sun Aug 6 19:55:01 2017 +0200\n\n good signed commit\n\ndiff --git a/readme.md b/readme.md\nnew file mode 100644\nindex 0000000..458121c\n--- /dev/null\n+++ b/readme.md\n@@ -0,0 +1 @@\n+good sign\n", resp.Body.String()) @@ -185,7 +185,7 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) { reqPatch := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.patch", user.Name). AddTokenAuth(token) resp = MakeRequest(t, reqPatch, http.StatusOK) - assert.EqualValues(t, + assert.Equal(t, "From f27c2b2b03dcab38beaf89b0ab4ff61f6de63441 Mon Sep 17 00:00:00 2001\nFrom: User2 <user2@example.com>\nDate: Sun, 6 Aug 2017 19:55:01 +0200\nSubject: [PATCH] good signed commit\n\n---\n readme.md | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 readme.md\n\ndiff --git a/readme.md b/readme.md\nnew file mode 100644\nindex 0000000..458121c\n--- /dev/null\n+++ b/readme.md\n@@ -0,0 +1 @@\n+good sign\n", resp.Body.String()) } @@ -208,7 +208,7 @@ func TestGetFileHistory(t *testing.T) { assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) - assert.EqualValues(t, "1", resp.Header().Get("X-Total")) + assert.Equal(t, "1", resp.Header().Get("X-Total")) } func TestGetFileHistoryNotOnMaster(t *testing.T) { @@ -229,5 +229,5 @@ func TestGetFileHistoryNotOnMaster(t *testing.T) { assert.Equal(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"test.csv"}, apiData[0].Files) - assert.EqualValues(t, "1", resp.Header().Get("X-Total")) + assert.Equal(t, "1", resp.Header().Get("X-Total")) } diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go index c5883a8058..5a66337589 100644 --- a/tests/integration/api_repo_git_tags_test.go +++ b/tests/integration/api_repo_git_tags_test.go @@ -30,8 +30,8 @@ func TestAPIGitTags(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Set up git config for the tagger - _ = git.NewCommand(git.DefaultContext, "config", "user.name").AddDynamicArguments(user.Name).Run(&git.RunOpts{Dir: repo.RepoPath()}) - _ = git.NewCommand(git.DefaultContext, "config", "user.email").AddDynamicArguments(user.Email).Run(&git.RunOpts{Dir: repo.RepoPath()}) + _ = git.NewCommand("config", "user.name").AddDynamicArguments(user.Name).Run(git.DefaultContext, &git.RunOpts{Dir: repo.RepoPath()}) + _ = git.NewCommand("config", "user.email").AddDynamicArguments(user.Email).Run(git.DefaultContext, &git.RunOpts{Dir: repo.RepoPath()}) gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo) defer gitRepo.Close() diff --git a/tests/integration/api_repo_git_trees_test.go b/tests/integration/api_repo_git_trees_test.go index 8eec6d8d22..47063d9091 100644 --- a/tests/integration/api_repo_git_trees_test.go +++ b/tests/integration/api_repo_git_trees_test.go @@ -69,7 +69,7 @@ func TestAPIReposGitTrees(t *testing.T) { // Login as User4. session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) 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_locks_test.go b/tests/integration/api_repo_lfs_locks_test.go index 4ba01e6d9b..161fa45dc6 100644 --- a/tests/integration/api_repo_lfs_locks_test.go +++ b/tests/integration/api_repo_lfs_locks_test.go @@ -112,7 +112,7 @@ func TestAPILFSLocksLogged(t *testing.T) { var lfsLock api.LFSLockResponse DecodeJSON(t, resp, &lfsLock) assert.Equal(t, test.user.Name, lfsLock.Lock.Owner.Name) - assert.EqualValues(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + assert.Equal(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second for _, id := range test.addTime { resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) } @@ -129,9 +129,9 @@ func TestAPILFSLocksLogged(t *testing.T) { DecodeJSON(t, resp, &lfsLocks) assert.Len(t, lfsLocks.Locks, test.totalCount) for i, lock := range lfsLocks.Locks { - assert.EqualValues(t, test.locksOwners[i].Name, lock.Owner.Name) + assert.Equal(t, test.locksOwners[i].Name, lock.Owner.Name) assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second) - assert.EqualValues(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + assert.Equal(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second } req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) @@ -143,7 +143,7 @@ func TestAPILFSLocksLogged(t *testing.T) { assert.Len(t, lfsLocksVerify.Ours, test.oursCount) assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount) for _, lock := range lfsLocksVerify.Ours { - assert.EqualValues(t, test.user.Name, lock.Owner.Name) + assert.Equal(t, test.user.Name, lock.Owner.Name) deleteTests = append(deleteTests, struct { user *user_model.User repo *repo_model.Repository diff --git a/tests/integration/api_repo_lfs_migrate_test.go b/tests/integration/api_repo_lfs_migrate_test.go index 8b4d79db02..6ca6f9afab 100644 --- a/tests/integration/api_repo_lfs_migrate_test.go +++ b/tests/integration/api_repo_lfs_migrate_test.go @@ -40,7 +40,7 @@ func TestAPIRepoLFSMigrateLocal(t *testing.T) { LFS: true, }).AddTokenAuth(token) resp := MakeRequest(t, req, NoExpectedStatus) - assert.EqualValues(t, http.StatusCreated, resp.Code) + assert.Equal(t, http.StatusCreated, resp.Code) store := lfs.NewContentStore() ok, _ := store.Verify(lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}) 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_raw_test.go b/tests/integration/api_repo_raw_test.go index e5f83d1c80..e9d741925f 100644 --- a/tests/integration/api_repo_raw_test.go +++ b/tests/integration/api_repo_raw_test.go @@ -30,11 +30,11 @@ func TestAPIReposRaw(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/raw/%s/README.md", user.Name, ref). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "file", resp.Header().Get("x-gitea-object-type")) + assert.Equal(t, "file", resp.Header().Get("x-gitea-object-type")) } // Test default branch req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/raw/README.md", user.Name). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "file", resp.Header().Get("x-gitea-object-type")) + assert.Equal(t, "file", resp.Header().Get("x-gitea-object-type")) } diff --git a/tests/integration/api_repo_secrets_test.go b/tests/integration/api_repo_secrets_test.go index c3074d9ece..2059aff484 100644 --- a/tests/integration/api_repo_secrets_test.go +++ b/tests/integration/api_repo_secrets_test.go @@ -73,6 +73,33 @@ func TestAPIRepoSecrets(t *testing.T) { } }) + t.Run("CreateWithDescription", func(t *testing.T) { + cases := []struct { + Name string + Description string + ExpectedStatus int + }{ + { + Name: "no_description", + Description: "", + ExpectedStatus: http.StatusCreated, + }, + { + Name: "description", + Description: "some description", + ExpectedStatus: http.StatusCreated, + }, + } + + for _, c := range cases { + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), c.Name), api.CreateOrUpdateSecretOption{ + Data: "data", + Description: c.Description, + }).AddTokenAuth(token) + MakeRequest(t, req, c.ExpectedStatus) + } + }) + t.Run("Update", func(t *testing.T) { name := "update_secret" url := fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), name) diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go index a7f021ca4f..3932a8ba2b 100644 --- a/tests/integration/api_repo_tags_test.go +++ b/tests/integration/api_repo_tags_test.go @@ -48,10 +48,10 @@ func TestAPIRepoTags(t *testing.T) { assert.Len(t, tags, 2) for _, tag := range tags { if tag.Name != "v1.1" { - assert.EqualValues(t, newTag.Name, tag.Name) - assert.EqualValues(t, newTag.Message, tag.Message) - assert.EqualValues(t, "nice!\nand some text", tag.Message) - assert.EqualValues(t, newTag.Commit.SHA, tag.Commit.SHA) + assert.Equal(t, newTag.Name, tag.Name) + assert.Equal(t, newTag.Message, tag.Message) + assert.Equal(t, "nice!\nand some text", tag.Message) + assert.Equal(t, newTag.Commit.SHA, tag.Commit.SHA) } } @@ -61,7 +61,7 @@ func TestAPIRepoTags(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) var tag *api.Tag DecodeJSON(t, resp, &tag) - assert.EqualValues(t, newTag, tag) + assert.Equal(t, newTag, tag) // delete tag delReq := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/tags/%s", user.Name, repoName, newTag.Name). diff --git a/tests/integration/api_repo_teams_test.go b/tests/integration/api_repo_teams_test.go index 07d065b02b..143e3dd29f 100644 --- a/tests/integration/api_repo_teams_test.go +++ b/tests/integration/api_repo_teams_test.go @@ -37,15 +37,15 @@ func TestAPIRepoTeams(t *testing.T) { var teams []*api.Team DecodeJSON(t, res, &teams) if assert.Len(t, teams, 2) { - assert.EqualValues(t, "Owners", teams[0].Name) + assert.Equal(t, "Owners", teams[0].Name) assert.True(t, teams[0].CanCreateOrgRepo) assert.True(t, util.SliceSortedEqual(unit.AllUnitKeyNames(), teams[0].Units), "%v == %v", unit.AllUnitKeyNames(), teams[0].Units) - assert.EqualValues(t, "owner", teams[0].Permission) + assert.Equal(t, "owner", teams[0].Permission) - assert.EqualValues(t, "test_team", teams[1].Name) + assert.Equal(t, "test_team", teams[1].Name) assert.False(t, teams[1].CanCreateOrgRepo) - assert.EqualValues(t, []string{"repo.issues"}, teams[1].Units) - assert.EqualValues(t, "write", teams[1].Permission) + assert.Equal(t, []string{"repo.issues"}, teams[1].Units) + assert.Equal(t, "write", teams[1].Permission) } // IsTeam @@ -54,7 +54,7 @@ func TestAPIRepoTeams(t *testing.T) { res = MakeRequest(t, req, http.StatusOK) var team *api.Team DecodeJSON(t, res, &team) - assert.EqualValues(t, teams[1], team) + assert.Equal(t, teams[1], team) req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/teams/%s", publicOrgRepo.FullName(), "NonExistingTeam")). AddTokenAuth(token) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 13dc90f8a7..a2c3a467c6 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -37,7 +37,7 @@ func TestAPIUserReposNotLogin(t *testing.T) { unittest.Cond("is_private = ?", false)) assert.Len(t, apiRepos, expectedLen) for _, repo := range apiRepos { - assert.EqualValues(t, user.ID, repo.Owner.ID) + assert.Equal(t, user.ID, repo.Owner.ID) assert.False(t, repo.Private) } } @@ -45,7 +45,7 @@ func TestAPIUserReposNotLogin(t *testing.T) { func TestAPIUserReposWithWrongToken(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - wrongToken := fmt.Sprintf("Bearer %s", "wrong_token") + wrongToken := "Bearer " + "wrong_token" req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name). AddTokenAuth(wrongToken) resp := MakeRequest(t, req, http.StatusUnauthorized) @@ -266,25 +266,25 @@ func TestAPIViewRepo(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.EqualValues(t, 1, repo.ID) - assert.EqualValues(t, "repo1", repo.Name) - assert.EqualValues(t, 2, repo.Releases) - assert.EqualValues(t, 1, repo.OpenIssues) - assert.EqualValues(t, 3, repo.OpenPulls) + assert.Equal(t, "repo1", repo.Name) + assert.Equal(t, 2, repo.Releases) + assert.Equal(t, 1, repo.OpenIssues) + assert.Equal(t, 3, repo.OpenPulls) req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.EqualValues(t, 10, repo.ID) - assert.EqualValues(t, "repo10", repo.Name) - assert.EqualValues(t, 1, repo.OpenPulls) - assert.EqualValues(t, 1, repo.Forks) + assert.Equal(t, "repo10", repo.Name) + assert.Equal(t, 1, repo.OpenPulls) + assert.Equal(t, 1, repo.Forks) req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.EqualValues(t, 4, repo.ID) - assert.EqualValues(t, "repo4", repo.Name) - assert.EqualValues(t, 1, repo.Stars) + assert.Equal(t, "repo4", repo.Name) + assert.Equal(t, 1, repo.Stars) } func TestAPIOrgRepos(t *testing.T) { @@ -337,9 +337,9 @@ func TestAPIOrgReposWithCodeUnitDisabled(t *testing.T) { var units []unit_model.Type units = append(units, unit_model.TypeCode) - if err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units); err != nil { - assert.Fail(t, "should have been able to delete code repository unit; failed to %v", err) - } + err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units) + assert.NoError(t, err, "should have been able to delete code repository unit") + assert.False(t, repo21.UnitEnabled(db.DefaultContext, unit_model.TypeCode)) session := loginUser(t, "user2") @@ -403,12 +403,12 @@ func TestAPIRepoMigrate(t *testing.T) { case "Remote visit addressed rate limitation.": t.Log("test hit github rate limitation") case "You can not import from disallowed hosts.": - assert.EqualValues(t, "private-ip", testCase.repoName) + assert.Equal(t, "private-ip", testCase.repoName) default: - assert.FailNow(t, "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL) + assert.FailNow(t, "unexpected error", "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL) } } else { - assert.EqualValues(t, testCase.expectedStatus, resp.Code) + assert.Equal(t, testCase.expectedStatus, resp.Code) } } } @@ -471,6 +471,15 @@ func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) { assert.Equal(t, "Repository is not a mirror", errRespJSON["message"]) } +func testAPIOrgCreateRepo(t *testing.T, session *TestSession, orgName, repoName string, status int) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", orgName), &api.CreateRepoOption{ + Name: repoName, + }).AddTokenAuth(token) + MakeRequest(t, req, status) +} + func TestAPIOrgRepoCreate(t *testing.T) { testCases := []struct { ctxUserID int64 @@ -488,11 +497,7 @@ func TestAPIOrgRepoCreate(t *testing.T) { for _, testCase := range testCases { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", testCase.orgName), &api.CreateRepoOption{ - Name: testCase.repoName, - }).AddTokenAuth(token) - MakeRequest(t, req, testCase.expectedStatus) + testAPIOrgCreateRepo(t, session, testCase.orgName, testCase.repoName, testCase.expectedStatus) } } @@ -581,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_topic_test.go b/tests/integration/api_repo_topic_test.go index a10e159b78..82d0c54ca8 100644 --- a/tests/integration/api_repo_topic_test.go +++ b/tests/integration/api_repo_topic_test.go @@ -30,7 +30,7 @@ func TestAPITopicSearch(t *testing.T) { res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 6) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // pagination search topics first page topics.TopicNames = nil @@ -40,7 +40,7 @@ func TestAPITopicSearch(t *testing.T) { res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 4) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // pagination search topics second page topics.TopicNames = nil @@ -50,7 +50,7 @@ func TestAPITopicSearch(t *testing.T) { res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 2) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // add keyword search query = url.Values{"page": []string{"1"}, "limit": []string{"4"}} @@ -66,8 +66,8 @@ func TestAPITopicSearch(t *testing.T) { DecodeJSON(t, res, &topics) if assert.Len(t, topics.TopicNames, 1) { assert.EqualValues(t, 2, topics.TopicNames[0].ID) - assert.EqualValues(t, "database", topics.TopicNames[0].Name) - assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount) + assert.Equal(t, "database", topics.TopicNames[0].Name) + assert.Equal(t, 1, topics.TopicNames[0].RepoCount) } } 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_settings_test.go b/tests/integration/api_settings_test.go index 9881578fba..743dbb0481 100644 --- a/tests/integration/api_settings_test.go +++ b/tests/integration/api_settings_test.go @@ -30,11 +30,12 @@ func TestAPIExposedSettings(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiSettings) - assert.EqualValues(t, &api.GeneralAPISettings{ + assert.Equal(t, &api.GeneralAPISettings{ MaxResponseItems: setting.API.MaxResponseItems, DefaultPagingNum: setting.API.DefaultPagingNum, DefaultGitTreesPerPage: setting.API.DefaultGitTreesPerPage, DefaultMaxBlobSize: setting.API.DefaultMaxBlobSize, + DefaultMaxResponseSize: setting.API.DefaultMaxResponseSize, }, apiSettings) repo := new(api.GeneralRepoSettings) @@ -42,7 +43,7 @@ func TestAPIExposedSettings(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) - assert.EqualValues(t, &api.GeneralRepoSettings{ + assert.Equal(t, &api.GeneralRepoSettings{ MirrorsDisabled: !setting.Mirror.Enabled, HTTPGitDisabled: setting.Repository.DisableHTTPGit, MigrationsDisabled: setting.Repository.DisableMigrations, @@ -55,7 +56,7 @@ func TestAPIExposedSettings(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &attachment) - assert.EqualValues(t, &api.GeneralAttachmentSettings{ + assert.Equal(t, &api.GeneralAttachmentSettings{ Enabled: setting.Attachment.Enabled, AllowedTypes: setting.Attachment.AllowedTypes, MaxFiles: setting.Attachment.MaxFiles, diff --git a/tests/integration/api_team_test.go b/tests/integration/api_team_test.go index d14c66ff2c..e54203ed50 100644 --- a/tests/integration/api_team_test.go +++ b/tests/integration/api_team_test.go @@ -40,9 +40,9 @@ func TestAPITeam(t *testing.T) { var apiTeam api.Team DecodeJSON(t, resp, &apiTeam) - assert.EqualValues(t, team.ID, apiTeam.ID) + assert.Equal(t, team.ID, apiTeam.ID) assert.Equal(t, team.Name, apiTeam.Name) - assert.EqualValues(t, convert.ToOrganization(db.DefaultContext, org), apiTeam.Organization) + assert.Equal(t, convert.ToOrganization(db.DefaultContext, org), apiTeam.Organization) // non team member user will not access the teams details teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}) @@ -78,9 +78,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam1", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) teamID := apiTeam.ID // Edit team. @@ -149,9 +149,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam2", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - "read", nil, teamToCreate.UnitsMap) + "none", nil, teamToCreate.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - "read", nil, teamToCreate.UnitsMap) + "none", nil, teamToCreate.UnitsMap) teamID = apiTeam.ID // Edit team. @@ -171,9 +171,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "EditTeam2", &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) // Edit team Description only editDescription = "second team" @@ -184,9 +184,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "EditTeam2_DescOnly", &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) // Read team. teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) @@ -247,10 +247,10 @@ func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, d if units != nil { sort.StringSlice(units).Sort() sort.StringSlice(apiTeam.Units).Sort() - assert.EqualValues(t, units, apiTeam.Units, "units") + assert.Equal(t, units, apiTeam.Units, "units") } if unitsMap != nil { - assert.EqualValues(t, unitsMap, apiTeam.UnitsMap, "unitsMap") + assert.Equal(t, unitsMap, apiTeam.UnitsMap, "unitsMap") } }) } diff --git a/tests/integration/api_team_user_test.go b/tests/integration/api_team_user_test.go index 6c80bc9f80..cbbbe00a9e 100644 --- a/tests/integration/api_team_user_test.go +++ b/tests/integration/api_team_user_test.go @@ -21,29 +21,31 @@ import ( func TestAPITeamUser(t *testing.T) { defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) - req := NewRequest(t, "GET", "/api/v1/teams/1/members/user1"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequest(t, "GET", "/api/v1/teams/1/members/user2"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var user2 *api.User - DecodeJSON(t, resp, &user2) - user2.Created = user2.Created.In(time.Local) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - - expectedUser := convert.ToUser(db.DefaultContext, user, user) - - // test time via unix timestamp - assert.EqualValues(t, expectedUser.LastLogin.Unix(), user2.LastLogin.Unix()) - assert.EqualValues(t, expectedUser.Created.Unix(), user2.Created.Unix()) - expectedUser.LastLogin = user2.LastLogin - expectedUser.Created = user2.Created - - assert.Equal(t, expectedUser, user2) + user2Session := loginUser(t, "user2") + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteOrganization) + + t.Run("User2ReadUser1", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/teams/1/members/user1").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("User2ReadSelf", func(t *testing.T) { + // read self user + req := NewRequest(t, "GET", "/api/v1/teams/1/members/user2").AddTokenAuth(user2Token) + resp := MakeRequest(t, req, http.StatusOK) + var user2 *api.User + DecodeJSON(t, resp, &user2) + user2.Created = user2.Created.In(time.Local) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + + expectedUser := convert.ToUser(db.DefaultContext, user, user) + + // test time via unix timestamp + assert.Equal(t, expectedUser.LastLogin.Unix(), user2.LastLogin.Unix()) + assert.Equal(t, expectedUser.Created.Unix(), user2.Created.Unix()) + expectedUser.LastLogin = user2.LastLogin + expectedUser.Created = user2.Created + + assert.Equal(t, expectedUser, user2) + }) } diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go index 01d18ef6f1..1770358d21 100644 --- a/tests/integration/api_token_test.go +++ b/tests/integration/api_token_test.go @@ -507,7 +507,7 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model } else if minRequiredLevel == auth_model.Write { unauthorizedLevel = auth_model.Read } else { - assert.FailNow(t, "Invalid test case: Unknown access token scope level: %v", minRequiredLevel) + assert.FailNow(t, "Invalid test case", "Unknown access token scope level: %v", minRequiredLevel) } } diff --git a/tests/integration/api_twofa_test.go b/tests/integration/api_twofa_test.go index 18e6fa91b7..938feba61a 100644 --- a/tests/integration/api_twofa_test.go +++ b/tests/integration/api_twofa_test.go @@ -94,7 +94,7 @@ func TestBasicAuthWithWebAuthn(t *testing.T) { } var userParsed userResponse DecodeJSON(t, resp, &userParsed) - assert.EqualValues(t, "Basic authorization is not allowed while webAuthn enrolled", userParsed.Message) + assert.Equal(t, "basic authorization is not allowed while WebAuthn enrolled", userParsed.Message) // user32 has webauthn enrolled, he can't request git protocol with basic auth req = NewRequest(t, "GET", "/user2/repo1/info/refs") diff --git a/tests/integration/api_user_block_test.go b/tests/integration/api_user_block_test.go index ae6b9eb849..6f73b089df 100644 --- a/tests/integration/api_user_block_test.go +++ b/tests/integration/api_user_block_test.go @@ -76,7 +76,7 @@ func TestBlockUser(t *testing.T) { blockeeName := "user10" t.Run("Block", func(t *testing.T) { - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)) + req := NewRequest(t, "PUT", "/api/v1/user/blocks/"+blockeeName) MakeRequest(t, req, http.StatusUnauthorized) assert.EqualValues(t, 1, countStars(t, blockerID, blockeeID)) @@ -84,7 +84,7 @@ func TestBlockUser(t *testing.T) { assert.EqualValues(t, 1, countRepositoryTransfers(t, blockerID, blockeeID)) assert.EqualValues(t, 1, countCollaborations(t, blockerID, blockeeID)) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "GET", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusNotFound) @@ -97,15 +97,15 @@ func TestBlockUser(t *testing.T) { assert.EqualValues(t, 0, countRepositoryTransfers(t, blockerID, blockeeID)) assert.EqualValues(t, 0, countCollaborations(t, blockerID, blockeeID)) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "GET", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusBadRequest) // can't block blocked user - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s", "org3")). + req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+"org3"). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusBadRequest) // can't block organization @@ -124,18 +124,18 @@ func TestBlockUser(t *testing.T) { }) t.Run("Unblock", func(t *testing.T) { - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)) + req := NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusBadRequest) - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", "org3")). + req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+"org3"). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusBadRequest) diff --git a/tests/integration/api_user_email_test.go b/tests/integration/api_user_email_test.go index 6441e2ed8e..5b6f0708ea 100644 --- a/tests/integration/api_user_email_test.go +++ b/tests/integration/api_user_email_test.go @@ -28,7 +28,7 @@ func TestAPIListEmails(t *testing.T) { var emails []*api.Email DecodeJSON(t, resp, &emails) - assert.EqualValues(t, []*api.Email{ + assert.Equal(t, []*api.Email{ { Email: "user2@example.com", Verified: true, @@ -66,7 +66,7 @@ func TestAPIAddEmail(t *testing.T) { var emails []*api.Email DecodeJSON(t, resp, &emails) - assert.EqualValues(t, []*api.Email{ + assert.Equal(t, []*api.Email{ { Email: "user2@example.com", Verified: true, @@ -119,7 +119,7 @@ func TestAPIDeleteEmail(t *testing.T) { var emails []*api.Email DecodeJSON(t, resp, &emails) - assert.EqualValues(t, []*api.Email{ + assert.Equal(t, []*api.Email{ { Email: "user2@example.com", Verified: true, diff --git a/tests/integration/api_user_follow_test.go b/tests/integration/api_user_follow_test.go index fe20af6769..6cb31a6802 100644 --- a/tests/integration/api_user_follow_test.go +++ b/tests/integration/api_user_follow_test.go @@ -32,7 +32,7 @@ func TestAPIFollow(t *testing.T) { t.Run("Follow", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/following/%s", user1)). + req := NewRequest(t, "PUT", "/api/v1/user/following/"+user1). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) @@ -110,11 +110,11 @@ func TestAPIFollow(t *testing.T) { t.Run("CheckMyFollowing", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/following/%s", user1)). + req := NewRequest(t, "GET", "/api/v1/user/following/"+user1). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/following/%s", user2)). + req = NewRequest(t, "GET", "/api/v1/user/following/"+user2). AddTokenAuth(token1) MakeRequest(t, req, http.StatusNotFound) }) @@ -122,7 +122,7 @@ func TestAPIFollow(t *testing.T) { t.Run("Unfollow", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/following/%s", user1)). + req := NewRequest(t, "DELETE", "/api/v1/user/following/"+user1). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) }) diff --git a/tests/integration/api_user_info_test.go b/tests/integration/api_user_info_test.go index 89f7266859..06353eabe0 100644 --- a/tests/integration/api_user_info_test.go +++ b/tests/integration/api_user_info_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -31,7 +30,7 @@ func TestAPIUserInfo(t *testing.T) { t.Run("GetInfo", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s", user2)). + req := NewRequest(t, "GET", "/api/v1/users/"+user2). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) @@ -39,17 +38,17 @@ func TestAPIUserInfo(t *testing.T) { DecodeJSON(t, resp, &u) assert.Equal(t, user2, u.UserName) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s", user2)) + req = NewRequest(t, "GET", "/api/v1/users/"+user2) MakeRequest(t, req, http.StatusNotFound) // test if the placaholder Mail is returned if a User is not logged in - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s", org3.Name)) + req = NewRequest(t, "GET", "/api/v1/users/"+org3.Name) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &u) assert.Equal(t, org3.GetPlaceholderEmail(), u.Email) // Test if the correct Mail is returned if a User is logged in - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s", org3.Name)). + req = NewRequest(t, "GET", "/api/v1/users/"+org3.Name). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &u) diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index 5604a14259..97264969eb 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -66,7 +66,7 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) { for _, user := range results.Data { assert.Contains(t, user.UserName, query) modelUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID}) - assert.EqualValues(t, modelUser.GetPlaceholderEmail(), user.Email) + assert.Equal(t, modelUser.GetPlaceholderEmail(), user.Email) } } @@ -85,8 +85,8 @@ func TestAPIUserSearchSystemUsers(t *testing.T) { assert.NotEmpty(t, results.Data) if assert.Len(t, results.Data, 1) { user := results.Data[0] - assert.EqualValues(t, user.UserName, systemUser.Name) - assert.EqualValues(t, user.ID, systemUser.ID) + assert.Equal(t, user.UserName, systemUser.Name) + assert.Equal(t, user.ID, systemUser.ID) } }) } @@ -108,7 +108,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { for _, user := range results.Data { assert.Contains(t, user.UserName, query) assert.NotEmpty(t, user.Email) - assert.EqualValues(t, "private", user.Visibility) + assert.Equal(t, "private", user.Visibility) } } diff --git a/tests/integration/api_user_secrets_test.go b/tests/integration/api_user_secrets_test.go index 56bf30e804..10024ac090 100644 --- a/tests/integration/api_user_secrets_test.go +++ b/tests/integration/api_user_secrets_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -55,7 +54,7 @@ func TestAPIUserSecrets(t *testing.T) { } for _, c := range cases { - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/secrets/%s", c.Name), api.CreateOrUpdateSecretOption{ + req := NewRequestWithJSON(t, "PUT", "/api/v1/user/actions/secrets/"+c.Name, api.CreateOrUpdateSecretOption{ Data: "data", }).AddTokenAuth(token) MakeRequest(t, req, c.ExpectedStatus) @@ -64,7 +63,7 @@ func TestAPIUserSecrets(t *testing.T) { t.Run("Update", func(t *testing.T) { name := "update_secret" - url := fmt.Sprintf("/api/v1/user/actions/secrets/%s", name) + url := "/api/v1/user/actions/secrets/" + name req := NewRequestWithJSON(t, "PUT", url, api.CreateOrUpdateSecretOption{ Data: "initial", @@ -79,7 +78,7 @@ func TestAPIUserSecrets(t *testing.T) { t.Run("Delete", func(t *testing.T) { name := "delete_secret" - url := fmt.Sprintf("/api/v1/user/actions/secrets/%s", name) + url := "/api/v1/user/actions/secrets/" + name req := NewRequestWithJSON(t, "PUT", url, api.CreateOrUpdateSecretOption{ Data: "initial", diff --git a/tests/integration/api_user_star_test.go b/tests/integration/api_user_star_test.go index 0062889a92..989e7ab1d1 100644 --- a/tests/integration/api_user_star_test.go +++ b/tests/integration/api_user_star_test.go @@ -11,7 +11,9 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -30,13 +32,13 @@ func TestAPIStar(t *testing.T) { t.Run("Star", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusNoContent) // blocked user can't star a repo user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) MakeRequest(t, req, http.StatusForbidden) }) @@ -74,11 +76,11 @@ func TestAPIStar(t *testing.T) { t.Run("IsStarring", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s", repo+"notexisting")). + req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusNotFound) }) @@ -86,8 +88,70 @@ func TestAPIStar(t *testing.T) { t.Run("Unstar", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusNoContent) }) } + +func TestAPIStarDisabled(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := "user1" + repo := "user2/repo1" + + session := loginUser(t, user) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + + defer test.MockVariableValue(&setting.Repository.DisableStars, true)() + + t.Run("Star", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). + AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("GetStarredRepos", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starred", user)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("GetMyStarredRepos", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/api/v1/user/starred"). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("IsStarring", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("Unstar", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, http.StatusForbidden) + }) +} diff --git a/tests/integration/api_user_variables_test.go b/tests/integration/api_user_variables_test.go index 9fd84ddf81..d430c9e21d 100644 --- a/tests/integration/api_user_variables_test.go +++ b/tests/integration/api_user_variables_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -30,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", @@ -63,7 +62,7 @@ func TestAPIUserVariables(t *testing.T) { } for _, c := range cases { - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.CreateVariableOption{ + req := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/variables/"+c.Name, api.CreateVariableOption{ Value: "value", }).AddTokenAuth(token) MakeRequest(t, req, c.ExpectedStatus) @@ -72,11 +71,11 @@ func TestAPIUserVariables(t *testing.T) { t.Run("UpdateUserVariable", func(t *testing.T) { variableName := "test_update_var" - url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) + url := "/api/v1/user/actions/variables/" + variableName 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 @@ -118,7 +117,7 @@ func TestAPIUserVariables(t *testing.T) { } for _, c := range cases { - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.UpdateVariableOption{ + req := NewRequestWithJSON(t, "PUT", "/api/v1/user/actions/variables/"+c.Name, api.UpdateVariableOption{ Name: c.UpdateName, Value: "updated_val", }).AddTokenAuth(token) @@ -128,12 +127,12 @@ func TestAPIUserVariables(t *testing.T) { t.Run("DeleteRepoVariable", func(t *testing.T) { variableName := "test_delete_var" - url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) + url := "/api/v1/user/actions/variables/" + variableName 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_wiki_test.go b/tests/integration/api_wiki_test.go index 05d90fc4e3..8e5f67e282 100644 --- a/tests/integration/api_wiki_test.go +++ b/tests/integration/api_wiki_test.go @@ -172,6 +172,19 @@ func TestAPIListWikiPages(t *testing.T) { assert.Equal(t, dummymeta, meta) } +func testAPICreateWikiPage(t *testing.T, session *TestSession, userName, repoName, title string, status int) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", userName, repoName) + + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{ + Title: title, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")), + Message: "", + }).AddTokenAuth(token) + MakeRequest(t, req, status) +} + func TestAPINewWikiPage(t *testing.T) { for _, title := range []string{ "New page", @@ -180,16 +193,7 @@ func TestAPINewWikiPage(t *testing.T) { defer tests.PrepareTestEnv(t)() username := "user2" session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", username, "repo1") - - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{ - Title: title, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")), - Message: "", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + testAPICreateWikiPage(t, session, username, "repo1", title, http.StatusCreated) } } diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go index 5c50fd0288..24f0c03bed 100644 --- a/tests/integration/auth_ldap_test.go +++ b/tests/integration/auth_ldap_test.go @@ -4,7 +4,6 @@ package integration import ( - "context" "net/http" "os" "strings" @@ -16,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" @@ -238,7 +238,7 @@ func TestLDAPUserSync(t *testing.T) { defer tests.PrepareTestEnv(t)() te.addAuthSource(t) - err := auth.SyncExternalUsers(context.Background(), true) + err := auth.SyncExternalUsers(t.Context(), true) assert.NoError(t, err) // Check if users exists @@ -279,7 +279,7 @@ func TestLDAPUserSyncWithEmptyUsernameAttribute(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) - tr := htmlDoc.doc.Find("table.table tbody tr") + tr := htmlDoc.doc.Find("table.table tbody tr:not(.no-results-row)") assert.Equal(t, 0, tr.Length()) } @@ -292,7 +292,7 @@ func TestLDAPUserSyncWithEmptyUsernameAttribute(t *testing.T) { MakeRequest(t, req, http.StatusSeeOther) } - require.NoError(t, auth.SyncExternalUsers(context.Background(), true)) + require.NoError(t, auth.SyncExternalUsers(t.Context(), true)) authSource := unittest.AssertExistsAndLoadBean(t, &auth_model.Source{ Name: payload["name"], @@ -328,7 +328,7 @@ func TestLDAPUserSyncWithGroupFilter(t *testing.T) { u := te.otherLDAPUsers[0] testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").TrString("form.username_password_incorrect")) - require.NoError(t, auth.SyncExternalUsers(context.Background(), true)) + require.NoError(t, auth.SyncExternalUsers(t.Context(), true)) // Assert members of LDAP group "cn=git" are added for _, gitLDAPUser := range te.gitLDAPUsers { @@ -351,7 +351,7 @@ func TestLDAPUserSyncWithGroupFilter(t *testing.T) { ldapConfig.GroupFilter = "(cn=ship_crew)" require.NoError(t, auth_model.UpdateSource(db.DefaultContext, ldapSource)) - require.NoError(t, auth.SyncExternalUsers(context.Background(), true)) + require.NoError(t, auth.SyncExternalUsers(t.Context(), true)) for _, gitLDAPUser := range te.gitLDAPUsers { if gitLDAPUser.UserName == "fry" || gitLDAPUser.UserName == "leela" || gitLDAPUser.UserName == "bender" { @@ -392,7 +392,7 @@ func TestLDAPUserSSHKeySync(t *testing.T) { defer tests.PrepareTestEnv(t)() te.addAuthSource(t, ldapAuthOptions{attributeSSHPublicKey: "sshPublicKey"}) - require.NoError(t, auth.SyncExternalUsers(context.Background(), true)) + require.NoError(t, auth.SyncExternalUsers(t.Context(), true)) // Check if users has SSH keys synced for _, u := range te.gitLDAPUsers { @@ -432,14 +432,14 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) { assert.NoError(t, err) team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") assert.NoError(t, err) - require.NoError(t, auth.SyncExternalUsers(context.Background(), true)) + require.NoError(t, auth.SyncExternalUsers(t.Context(), true)) for _, gitLDAPUser := range te.gitLDAPUsers { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ 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/benchmarks_test.go b/tests/integration/benchmarks_test.go deleted file mode 100644 index 62da761d2d..0000000000 --- a/tests/integration/benchmarks_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "math/rand/v2" - "net/http" - "net/url" - "testing" - - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" - api "code.gitea.io/gitea/modules/structs" -) - -// StringWithCharset random string (from https://www.calhoun.io/creating-random-strings-in-go/) -func StringWithCharset(length int, charset string) string { - b := make([]byte, length) - for i := range b { - b[i] = charset[rand.IntN(len(charset))] - } - return string(b) -} - -func BenchmarkRepoBranchCommit(b *testing.B) { - onGiteaRun(b, func(b *testing.B, u *url.URL) { - samples := []int64{1, 2, 3} - b.ResetTimer() - - for _, repoID := range samples { - b.StopTimer() - repo := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: repoID}) - b.StartTimer() - b.Run(repo.Name, func(b *testing.B) { - session := loginUser(b, "user2") - b.ResetTimer() - b.Run("CreateBranch", func(b *testing.B) { - b.StopTimer() - branchName := StringWithCharset(5+rand.IntN(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - b.StartTimer() - for i := 0; i < b.N; i++ { - b.Run("new_"+branchName, func(b *testing.B) { - b.Skip("benchmark broken") // TODO fix - testAPICreateBranch(b, session, repo.OwnerName, repo.Name, repo.DefaultBranch, "new_"+branchName, http.StatusCreated) - }) - } - }) - b.Run("GetBranches", func(b *testing.B) { - req := NewRequestf(b, "GET", "/api/v1/repos/%s/branches", repo.FullName()) - session.MakeRequest(b, req, http.StatusOK) - }) - b.Run("AccessCommits", func(b *testing.B) { - var branches []*api.Branch - req := NewRequestf(b, "GET", "/api/v1/repos/%s/branches", repo.FullName()) - resp := session.MakeRequest(b, req, http.StatusOK) - DecodeJSON(b, resp, &branches) - b.ResetTimer() // We measure from here - if len(branches) != 0 { - for i := 0; i < b.N; i++ { - req := NewRequestf(b, "GET", "/api/v1/repos/%s/commits?sha=%s", repo.FullName(), branches[i%len(branches)].Name) - session.MakeRequest(b, req, http.StatusOK) - } - } - }) - }) - } - }) -} diff --git a/tests/integration/change_default_branch_test.go b/tests/integration/change_default_branch_test.go index 729eb1e4ce..9b61cff9fd 100644 --- a/tests/integration/change_default_branch_test.go +++ b/tests/integration/change_default_branch_test.go @@ -6,12 +6,16 @@ package integration import ( "fmt" "net/http" + "strconv" "testing" 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/git" "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" ) func TestChangeDefaultBranch(t *testing.T) { @@ -38,3 +42,96 @@ func TestChangeDefaultBranch(t *testing.T) { }) session.MakeRequest(t, req, http.StatusNotFound) } + +func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]git.DivergeObject) { + req := NewRequest(t, "GET", branchesURL) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + branchNodes := htmlDoc.doc.Find(".branch-name").Nodes + branchNames := []string{} + for _, node := range branchNodes { + branchNames = append(branchNames, node.FirstChild.Data) + } + + expectBranchCount := len(expectedBranchToDivergence) + + assert.Len(t, branchNames, expectBranchCount+1) + assert.Equal(t, expectedDefaultBranch, branchNames[0]) + + allCountBehindNodes := htmlDoc.doc.Find(".count-behind").Nodes + allCountAheadNodes := htmlDoc.doc.Find(".count-ahead").Nodes + + assert.Len(t, allCountAheadNodes, expectBranchCount) + assert.Len(t, allCountBehindNodes, expectBranchCount) + + for i := range expectBranchCount { + branchName := branchNames[i+1] + assert.Contains(t, expectedBranchToDivergence, branchName) + + expectedCountAhead := expectedBranchToDivergence[branchName].Ahead + expectedCountBehind := expectedBranchToDivergence[branchName].Behind + countAhead, err := strconv.Atoi(allCountAheadNodes[i].FirstChild.Data) + assert.NoError(t, err) + countBehind, err := strconv.Atoi(allCountBehindNodes[i].FirstChild.Data) + assert.NoError(t, err) + + assert.Equal(t, expectedCountAhead, countAhead) + assert.Equal(t, expectedCountBehind, countBehind) + } +} + +func TestChangeDefaultBranchDivergence(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + branchesURL := fmt.Sprintf("/%s/%s/branches", owner.Name, repo.Name) + settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) + + // check branch divergence before switching default branch + expectedBranchToDivergenceBefore := map[string]git.DivergeObject{ + "not-signed": { + Ahead: 0, + Behind: 0, + }, + "good-sign-not-yet-validated": { + Ahead: 0, + Behind: 1, + }, + "good-sign": { + Ahead: 1, + Behind: 3, + }, + } + checkDivergence(t, session, branchesURL, "master", expectedBranchToDivergenceBefore) + + // switch default branch + newDefaultBranch := "good-sign-not-yet-validated" + csrf := GetUserCSRFToken(t, session) + req := NewRequestWithValues(t, "POST", settingsBranchesURL, map[string]string{ + "_csrf": csrf, + "action": "default_branch", + "branch": newDefaultBranch, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // check branch divergence after switching default branch + expectedBranchToDivergenceAfter := map[string]git.DivergeObject{ + "master": { + Ahead: 1, + Behind: 0, + }, + "not-signed": { + Ahead: 1, + Behind: 0, + }, + "good-sign": { + Ahead: 1, + Behind: 2, + }, + } + checkDivergence(t, session, branchesURL, newDefaultBranch, expectedBranchToDivergenceAfter) +} 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/compare_test.go b/tests/integration/compare_test.go index cbf927813e..0648777fed 100644 --- a/tests/integration/compare_test.go +++ b/tests/integration/compare_test.go @@ -133,7 +133,7 @@ func TestCompareCodeExpand(t *testing.T) { Readme: "Default", AutoInit: true, DefaultBranch: "main", - }) + }, true) assert.NoError(t, err) session := loginUser(t, user1.Name) diff --git a/tests/integration/db_collation_test.go b/tests/integration/db_collation_test.go index acec4aa5d1..339bfce71c 100644 --- a/tests/integration/db_collation_test.go +++ b/tests/integration/db_collation_test.go @@ -75,7 +75,7 @@ func TestDatabaseCollation(t *testing.T) { defer test.MockVariableValue(&setting.Database.CharsetCollation, "utf8mb4_bin")() r, err := db.CheckCollations(x) assert.NoError(t, err) - assert.EqualValues(t, "utf8mb4_bin", r.ExpectedCollation) + assert.Equal(t, "utf8mb4_bin", r.ExpectedCollation) assert.NoError(t, db.ConvertDatabaseTable()) r, err = db.CheckCollations(x) assert.NoError(t, err) diff --git a/tests/integration/delete_user_test.go b/tests/integration/delete_user_test.go index ad3c882882..4b02c4725a 100644 --- a/tests/integration/delete_user_test.go +++ b/tests/integration/delete_user_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -34,7 +33,7 @@ func TestUserDeleteAccount(t *testing.T) { session := loginUser(t, "user8") csrf := GetUserCSRFToken(t, session) - urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword) + urlStr := "/user/settings/account/delete?password=" + userPassword req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ "_csrf": csrf, }) @@ -49,7 +48,7 @@ func TestUserDeleteAccountStillOwnRepos(t *testing.T) { session := loginUser(t, "user2") csrf := GetUserCSRFToken(t, session) - urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword) + urlStr := "/user/settings/account/delete?password=" + userPassword req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ "_csrf": csrf, }) diff --git a/tests/integration/dump_restore_test.go b/tests/integration/dump_restore_test.go index abec8f300c..d2d43075c3 100644 --- a/tests/integration/dump_restore_test.go +++ b/tests/integration/dump_restore_test.go @@ -4,7 +4,6 @@ package integration import ( - "context" "errors" "fmt" "net/url" @@ -21,7 +20,6 @@ import ( base "code.gitea.io/gitea/modules/migration" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/migrations" "github.com/stretchr/testify/assert" @@ -44,10 +42,7 @@ func TestDumpRestore(t *testing.T) { reponame := "repo1" - basePath, err := os.MkdirTemp("", reponame) - assert.NoError(t, err) - defer util.RemoveAll(basePath) - + basePath := t.TempDir() repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) @@ -57,7 +52,7 @@ func TestDumpRestore(t *testing.T) { // Phase 1: dump repo1 from the Gitea instance to the filesystem // - ctx := context.Background() + ctx := t.Context() opts := migrations.MigrateOptions{ GitServiceType: structs.GiteaService, Issues: true, @@ -66,10 +61,10 @@ func TestDumpRestore(t *testing.T) { Milestones: true, Comments: true, AuthToken: token, - CloneAddr: repo.CloneLinkGeneral(context.Background()).HTTPS, + CloneAddr: repo.CloneLinkGeneral(t.Context()).HTTPS, RepoName: reponame, } - err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) + err := migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) assert.NoError(t, err) // @@ -96,7 +91,7 @@ func TestDumpRestore(t *testing.T) { // Phase 3: dump restored from the Gitea instance to the filesystem // opts.RepoName = newreponame - opts.CloneAddr = newrepo.CloneLinkGeneral(context.Background()).HTTPS + opts.CloneAddr = newrepo.CloneLinkGeneral(t.Context()).HTTPS err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) assert.NoError(t, err) @@ -183,7 +178,7 @@ func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) }).([]*base.Comment) assert.True(c.t, ok) for _, comment := range comments { - assert.EqualValues(c.t, issue.Number, comment.IssueIndex) + assert.Equal(c.t, issue.Number, comment.IssueIndex) } } @@ -210,7 +205,7 @@ func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment) assert.True(c.t, ok) for _, comment := range comments { - assert.EqualValues(c.t, pr.Number, comment.IssueIndex) + assert.Equal(c.t, pr.Number, comment.IssueIndex) } } } @@ -218,7 +213,7 @@ func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) func (c *compareDump) assertLoadYAMLFiles(beforeFilename, afterFilename string, before, after any) { _, beforeErr := os.Stat(beforeFilename) _, afterErr := os.Stat(afterFilename) - assert.EqualValues(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist)) + assert.Equal(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist)) if errors.Is(beforeErr, os.ErrNotExist) { return } @@ -270,7 +265,7 @@ func (c *compareDump) assertEqual(filename string, kind any, fields compareField } func (c *compareDump) assertEqualSlices(before, after reflect.Value, fields compareFields) any { - assert.EqualValues(c.t, before.Len(), after.Len()) + assert.Equal(c.t, before.Len(), after.Len()) if before.Len() == after.Len() { for i := 0; i < before.Len(); i++ { _ = c.assertEqualValues( @@ -303,15 +298,15 @@ func (c *compareDump) assertEqualValues(before, after reflect.Value, fields comp assert.True(c.t, ok) as, ok := ai.(string) assert.True(c.t, ok) - assert.EqualValues(c.t, compare.transform(bs), compare.transform(as)) + assert.Equal(c.t, compare.transform(bs), compare.transform(as)) continue } if compare.before != nil && compare.after != nil { // // The fields are expected to have different values // - assert.EqualValues(c.t, compare.before, bi) - assert.EqualValues(c.t, compare.after, ai) + assert.Equal(c.t, compare.before, bi) + assert.Equal(c.t, compare.after, ai) continue } if compare.nested != nil { @@ -322,7 +317,7 @@ func (c *compareDump) assertEqualValues(before, after reflect.Value, fields comp continue } } - assert.EqualValues(c.t, bi, ai) + assert.Equal(c.t, bi, ai) } return after.Interface() } diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index f0f71b80d1..ac47ed0094 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -4,175 +4,452 @@ package integration import ( + "bytes" "fmt" + "io" + "maps" + "mime/multipart" "net/http" "net/http/httptest" "net/url" "path" + "strings" "testing" - "code.gitea.io/gitea/modules/json" - gitea_context "code.gitea.io/gitea/services/context" + 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/git" + "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 - flashCookie := session.GetCookie(gitea_context.CookieNameFlash) - assert.NotNil(t, flashCookie) - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522master%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) +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) - // Request editor page - req = NewRequest(t, "GET", "/user2/repo1/_new/master/") - resp := session.MakeRequest(t, req, http.StatusOK) + // 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) +} - doc := NewHTMLParser(t, resp.Body) - lastCommit := doc.GetInputValueByName("last_commit") - assert.NotEmpty(t, lastCommit) +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) + 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) +} - // 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", - }) +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) +} - resp = session.MakeRequest(t, req, http.StatusOK) - // Check body for error message - assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch "master".") +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 +} - // remove the protected branch - csrf = GetUserCSRFToken(t, session) +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", + }) +} - // Change master branch to protected - req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/1/delete", map[string]string{ - "_csrf": csrf, - }) +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, + }) +} - resp = session.MakeRequest(t, req, http.StatusOK) +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>`) +} - res := make(map[string]string) - assert.NoError(t, json.NewDecoder(resp.Body).Decode(&res)) - assert.EqualValues(t, "/user2/repo1/settings/branches", res["redirect"]) +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()) - // Check if master branch has been locked successfully - flashCookie = session.GetCookie(gitea_context.CookieNameFlash) - assert.NotNil(t, flashCookie) - assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25221%2522%2Bfailed.", flashCookie.Value) + // 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 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)) - resp := session.MakeRequest(t, req, http.StatusOK) +func testEditorWebGitCommitEmail(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + require.True(t, user.KeepEmailPrivate) - 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) + 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 + } - // Verify the change - req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", branch, filePath)) - resp = session.MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, newContent, resp.Body.String()) + session := loginUser(t, user.Name) - return resp -} + 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 + } -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) + 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() - 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) + req := NewRequestWithBody(t, "POST", "/user2/repo1/upload-file", body) + req.Header.Add("Content-Type", uploadForm.FormDataContentType()) + resp := session.MakeRequest(t, req, http.StatusOK) - // Verify the change - req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", targetBranch, filePath)) - resp = session.MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, newContent, resp.Body.String()) + respMap := map[string]string{} + DecodeJSON(t, resp, &respMap) + return respMap["uuid"] + } - return resp -} + 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, + }, "", "") + }) -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") + 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, + }, "", "") + }) + + 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"}, + ) + }) + + 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 +new file mode 100644 +index 0000000000..aaaaaaaaaa +--- /dev/null ++++ b/patch-file-1.txt +@@ -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 +new file mode 100644 +index 0000000000..bbbbbbbbbb +--- /dev/null ++++ b/patch-file-2.txt +@@ -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", test.RedirectURL(resp1)) }) } -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 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 b19774a826..6a8c70f12f 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -10,7 +10,7 @@ import ( "io" "mime/multipart" "net/http" - "net/http/httptest" + "strings" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -22,13 +22,14 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" "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), @@ -36,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) { @@ -60,12 +62,20 @@ func TestEmptyRepoAddFile(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user30") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // test web page req := NewRequest(t, "GET", "/user30/empty") resp := session.MakeRequest(t, req, http.StatusOK) bodyString := resp.Body.String() assert.Contains(t, bodyString, "empty-repo-guide") assert.True(t, test.IsNormalPageCompleted(bodyString)) + // test api + req = NewRequest(t, "GET", "/api/v1/repos/user30/empty/raw/main/README.md").AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusNotFound) + + // create a new file req = NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch) resp = session.MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`) @@ -77,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) @@ -91,22 +101,29 @@ func TestEmptyRepoAddFile(t *testing.T) { assert.Contains(t, resp.Body.String(), "test-file.md") // if the repo is in incorrect state, it should be able to self-heal (recover to correct state) - user30EmptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"}) - user30EmptyRepo.IsEmpty = true - user30EmptyRepo.DefaultBranch = "no-such" - _, err := db.GetEngine(db.DefaultContext).ID(user30EmptyRepo.ID).Cols("is_empty", "default_branch").Update(user30EmptyRepo) - require.NoError(t, err) - user30EmptyRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"}) - assert.True(t, user30EmptyRepo.IsEmpty) - - req = NewRequest(t, "GET", "/user30/empty") - resp = session.MakeRequest(t, req, http.StatusSeeOther) - redirect = test.RedirectURL(resp) - assert.Equal(t, "/user30/empty", redirect) - - req = NewRequest(t, "GET", "/user30/empty") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "test-file.md") + testEmptyOrBrokenRecover := func(t *testing.T, isEmpty, isBroken bool) { + user30EmptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"}) + user30EmptyRepo.IsEmpty = isEmpty + user30EmptyRepo.Status = util.Iif(isBroken, repo_model.RepositoryBroken, repo_model.RepositoryReady) + user30EmptyRepo.DefaultBranch = "no-such" + _, err := db.GetEngine(db.DefaultContext).ID(user30EmptyRepo.ID).Cols("is_empty", "status", "default_branch").Update(user30EmptyRepo) + require.NoError(t, err) + user30EmptyRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"}) + assert.Equal(t, isEmpty, user30EmptyRepo.IsEmpty) + assert.Equal(t, isBroken, user30EmptyRepo.Status == repo_model.RepositoryBroken) + + req = NewRequest(t, "GET", "/user30/empty") + resp = session.MakeRequest(t, req, http.StatusSeeOther) + redirect = test.RedirectURL(resp) + assert.Equal(t, "/user30/empty", redirect) + + req = NewRequest(t, "GET", "/user30/empty") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "test-file.md") + } + testEmptyOrBrokenRecover(t, true, false) + testEmptyOrBrokenRecover(t, false, true) + testEmptyOrBrokenRecover(t, true, true) } func TestEmptyRepoUploadFile(t *testing.T) { @@ -122,7 +139,7 @@ func TestEmptyRepoUploadFile(t *testing.T) { mpForm := multipart.NewWriter(body) _ = mpForm.WriteField("_csrf", GetUserCSRFToken(t, session)) file, _ := mpForm.CreateFormFile("file", "uploaded-file.txt") - _, _ = io.Copy(file, bytes.NewBufferString("newly-uploaded-test-file")) + _, _ = io.Copy(file, strings.NewReader("newly-uploaded-test-file")) _ = mpForm.Close() req = NewRequestWithBody(t, "POST", "/user30/empty/upload-file", body) @@ -137,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) @@ -164,7 +181,7 @@ func TestEmptyRepoAddFileByAPI(t *testing.T) { var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) expectedHTMLURL := setting.AppURL + "user30/empty/src/branch/new_branch/new-file.txt" - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) req = NewRequest(t, "GET", "/user30/empty/src/branch/new_branch/new-file.txt") 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/feed_repo_test.go b/tests/integration/feed_repo_test.go new file mode 100644 index 0000000000..2915b9b3f4 --- /dev/null +++ b/tests/integration/feed_repo_test.go @@ -0,0 +1,35 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "encoding/xml" + "net/http" + "testing" + + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestFeedRepo(t *testing.T) { + t.Run("RSS", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1.rss") + resp := MakeRequest(t, req, http.StatusOK) + + data := resp.Body.String() + assert.Contains(t, data, `<rss version="2.0"`) + + var rss RSS + err := xml.Unmarshal(resp.Body.Bytes(), &rss) + assert.NoError(t, err) + assert.Contains(t, rss.Channel.Link, "/user2/repo1") + assert.NotEmpty(t, rss.Channel.PubDate) + assert.Len(t, rss.Channel.Items, 1) + assert.Equal(t, "issue5", rss.Channel.Items[0].Description) + assert.NotEmpty(t, rss.Channel.Items[0].PubDate) + }) +} diff --git a/tests/integration/api_feed_user_test.go b/tests/integration/feed_user_test.go index c44f9a1951..4315c67f48 100644 --- a/tests/integration/api_feed_user_test.go +++ b/tests/integration/feed_user_test.go @@ -4,6 +4,7 @@ package integration import ( + "encoding/xml" "net/http" "testing" @@ -12,7 +13,23 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFeed(t *testing.T) { +// RSS is a struct to unmarshal RSS feeds test only +type RSS struct { + Channel struct { + Title string `xml:"title"` + Link string `xml:"link"` + Description string `xml:"description"` + PubDate string `xml:"pubDate"` + Items []struct { + Title string `xml:"title"` + Link string `xml:"link"` + Description string `xml:"description"` + PubDate string `xml:"pubDate"` + } `xml:"item"` + } `xml:"channel"` +} + +func TestFeedUser(t *testing.T) { t.Run("User", func(t *testing.T) { t.Run("Atom", func(t *testing.T) { defer tests.PrepareTestEnv(t)() @@ -32,6 +49,12 @@ func TestFeed(t *testing.T) { data := resp.Body.String() assert.Contains(t, data, `<rss version="2.0"`) + + var rss RSS + err := xml.Unmarshal(resp.Body.Bytes(), &rss) + assert.NoError(t, err) + assert.Contains(t, rss.Channel.Link, "/user2") + assert.NotEmpty(t, rss.Channel.PubDate) }) }) } diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go index 5d915d8a51..3b0f9589d2 100644 --- a/tests/integration/git_general_test.go +++ b/tests/integration/git_general_test.go @@ -11,8 +11,10 @@ import ( "net/http" "net/url" "os" + "os/exec" "path" "path/filepath" + "slices" "strconv" "testing" "time" @@ -24,13 +26,14 @@ 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" api "code.gitea.io/gitea/modules/structs" - gitea_context "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" + "github.com/kballard/go-shellquote" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -81,6 +84,7 @@ func testGitGeneral(t *testing.T, u *url.URL) { mediaTest(t, &httpContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "test/head")) + t.Run("CreateProtectedBranch", doCreateProtectedBranch(&httpContext, dstPath)) t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath)) t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath)) t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge")) @@ -105,7 +109,12 @@ func testGitGeneral(t *testing.T, u *url.URL) { // Setup key the user ssh key withKeyFile(t, keyname, func(keyFile string) { - t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile)) + var keyID int64 + t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) { + keyID = key.ID + })) + assert.NotZero(t, keyID) + t.Run("LFSAccessTest", doSSHLFSAccessTest(sshContext, keyID)) // Setup remote link // TODO: get url from api @@ -122,6 +131,7 @@ func testGitGeneral(t *testing.T, u *url.URL) { mediaTest(t, &sshContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "test/head2")) + t.Run("CreateProtectedBranch", doCreateProtectedBranch(&sshContext, dstPath)) t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath)) t.Run("MergeFork", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -135,6 +145,36 @@ func testGitGeneral(t *testing.T, u *url.URL) { }) } +func doSSHLFSAccessTest(_ APITestContext, keyID int64) func(*testing.T) { + return func(t *testing.T) { + sshCommand := os.Getenv("GIT_SSH_COMMAND") // it is set in withKeyFile + sshCmdParts, err := shellquote.Split(sshCommand) // and parse the ssh command to construct some mocked arguments + require.NoError(t, err) + + t.Run("User2AccessOwned", func(t *testing.T) { + sshCmdUser2Self := append(slices.Clone(sshCmdParts), + "-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost, + "git-lfs-authenticate", "user2/repo1.git", "upload", // accessible to own repo + ) + cmd := exec.CommandContext(t.Context(), sshCmdUser2Self[0], sshCmdUser2Self[1:]...) + _, err := cmd.Output() + assert.NoError(t, err) // accessible, no error + }) + + t.Run("User2AccessOther", func(t *testing.T) { + sshCmdUser2Other := append(slices.Clone(sshCmdParts), + "-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost, + "git-lfs-authenticate", "user5/repo4.git", "upload", // inaccessible to other's (user5/repo4) + ) + cmd := exec.CommandContext(t.Context(), sshCmdUser2Other[0], sshCmdUser2Other[1:]...) + _, err := cmd.Output() + var errExit *exec.ExitError + require.ErrorAs(t, err, &errExit) // inaccessible, error + assert.Contains(t, string(errExit.Stderr), fmt.Sprintf("User: 2:user2 with Key: %d:test-key is not authorized to write to user5/repo4.", keyID)) + }) + } +} + func ensureAnonymousClone(t *testing.T, u *url.URL) { dstLocalPath := t.TempDir() t.Run("CloneAnonymous", doGitClone(dstLocalPath, u)) @@ -152,9 +192,9 @@ func lfsCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFil t.Run("CommitAndPushLFS", func(t *testing.T) { defer tests.PrintCurrentTest(t)() prefix := "lfs-data-file-" - err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath}) + err := git.NewCommand("lfs").AddArguments("install").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track").AddDynamicArguments(prefix + "*").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix+"*").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) err = git.AddChanges(dstPath, false, ".gitattributes") assert.NoError(t, err) @@ -270,20 +310,20 @@ func lockTest(t *testing.T, repoPath string) { } func lockFileTest(t *testing.T, filename, repoPath string) { - _, _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err := git.NewCommand("lfs").AddArguments("locks").RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommand("lfs").AddArguments("locks").RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) assert.NoError(t, err) } func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string { name, err := generateCommitWithNewData(size, repoPath, "user2@example.com", "User Two", prefix) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push + _, _, err = git.NewCommand("push", "origin", "master").RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) // Push assert.NoError(t, err) return name } @@ -326,6 +366,34 @@ func generateCommitWithNewData(size int, repoPath, email, fullName, prefix strin return filepath.Base(tmpFile.Name()), err } +func doCreateProtectedBranch(baseCtx *APITestContext, dstPath string) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) + + t.Run("ProtectBranchWithFilePatterns", doProtectBranch(ctx, "release-*", baseCtx.Username, "", "", "config*")) + + // push a new branch without any new commits + t.Run("CreateProtectedBranch-NoChanges", doGitCreateBranch(dstPath, "release-v1.0")) + t.Run("PushProtectedBranch-NoChanges", doGitPushTestRepository(dstPath, "origin", "release-v1.0")) + t.Run("CheckoutMaster-NoChanges", doGitCheckoutBranch(dstPath, "master")) + + // push a new branch with a new unprotected file + t.Run("CreateProtectedBranch-UnprotectedFile", doGitCreateBranch(dstPath, "release-v2.0")) + _, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "abc.txt") + assert.NoError(t, err) + t.Run("PushProtectedBranch-UnprotectedFile", doGitPushTestRepository(dstPath, "origin", "release-v2.0")) + t.Run("CheckoutMaster-UnprotectedFile", doGitCheckoutBranch(dstPath, "master")) + + // push a new branch with a new protected file + t.Run("CreateProtectedBranch-ProtectedFile", doGitCreateBranch(dstPath, "release-v3.0")) + _, err = generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "config") + assert.NoError(t, err) + t.Run("PushProtectedBranch-ProtectedFile", doGitPushTestRepositoryFail(dstPath, "origin", "release-v3.0")) + t.Run("CheckoutMaster-ProtectedFile", doGitCheckoutBranch(dstPath, "master")) + } +} + func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { return func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -335,60 +403,48 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) // Protect branch without any whitelisting - t.Run("ProtectBranchNoWhitelist", func(t *testing.T) { - doProtectBranch(ctx, "protected", "", "", "") - }) + t.Run("ProtectBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "", "")) // Try to push without permissions, which should fail t.Run("TryPushWithoutPermissions", func(t *testing.T) { _, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-") assert.NoError(t, err) - doGitPushTestRepositoryFail(dstPath, "origin", "protected") + doGitPushTestRepositoryFail(dstPath, "origin", "protected")(t) }) // Set up permissions for normal push but not force push - t.Run("SetupNormalPushPermissions", func(t *testing.T) { - doProtectBranch(ctx, "protected", baseCtx.Username, "", "") - }) + t.Run("SetupNormalPushPermissions", doProtectBranch(ctx, "protected", baseCtx.Username, "", "", "")) // Normal push should work t.Run("NormalPushWithPermissions", func(t *testing.T) { _, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-") assert.NoError(t, err) - doGitPushTestRepository(dstPath, "origin", "protected") + doGitPushTestRepository(dstPath, "origin", "protected")(t) }) // Try to force push without force push permissions, which should fail t.Run("ForcePushWithoutForcePermissions", func(t *testing.T) { t.Run("CreateDivergentHistory", func(t *testing.T) { - git.NewCommand(git.DefaultContext, "reset", "--hard", "HEAD~1").Run(&git.RunOpts{Dir: dstPath}) + git.NewCommand("reset", "--hard", "HEAD~1").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) _, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-new") assert.NoError(t, err) }) - doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected") + doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")(t) }) // Set up permissions for force push but not normal push - t.Run("SetupForcePushPermissions", func(t *testing.T) { - doProtectBranch(ctx, "protected", "", baseCtx.Username, "") - }) + t.Run("SetupForcePushPermissions", doProtectBranch(ctx, "protected", "", baseCtx.Username, "", "")) // Try to force push without normal push permissions, which should fail - t.Run("ForcePushWithoutNormalPermissions", func(t *testing.T) { - doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected") - }) + t.Run("ForcePushWithoutNormalPermissions", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")) // Set up permissions for normal and force push (both are required to force push) - t.Run("SetupNormalAndForcePushPermissions", func(t *testing.T) { - doProtectBranch(ctx, "protected", baseCtx.Username, baseCtx.Username, "") - }) + t.Run("SetupNormalAndForcePushPermissions", doProtectBranch(ctx, "protected", baseCtx.Username, baseCtx.Username, "", "")) // Force push should now work - t.Run("ForcePushWithPermissions", func(t *testing.T) { - doGitPushTestRepository(dstPath, "-f", "origin", "protected") - }) + t.Run("ForcePushWithPermissions", doGitPushTestRepository(dstPath, "-f", "origin", "protected")) - t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "")) + t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "", "")) t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected")) var pr api.PullRequest var err error @@ -410,14 +466,14 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) - t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "", "unprotected-file-*")) + t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "", "unprotected-file-*", "")) t.Run("GenerateCommit", func(t *testing.T) { _, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "unprotected-file-") assert.NoError(t, err) }) t.Run("PushUnprotectedFilesToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) - t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "", "")) + t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "", "", "")) t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master")) t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce")) @@ -432,7 +488,7 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes } } -func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhitelistForcePush, unprotectedFilePatterns string) func(t *testing.T) { +func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhitelistForcePush, unprotectedFilePatterns, protectedFilePatterns string) func(t *testing.T) { // We are going to just use the owner to set the protection. return func(t *testing.T) { csrf := GetUserCSRFToken(t, ctx.Session) @@ -441,6 +497,7 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhit "_csrf": csrf, "rule_name": branch, "unprotected_file_patterns": unprotectedFilePatterns, + "protected_file_patterns": protectedFilePatterns, } if userToWhitelistPush != "" { @@ -464,9 +521,8 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhit ctx.Session.MakeRequest(t, req, http.StatusSeeOther) // Check if master branch has been locked successfully - flashCookie := ctx.Session.GetCookie(gitea_context.CookieNameFlash) - assert.NotNil(t, flashCookie) - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522"+url.QueryEscape(branch)+"%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) + flashMsg := ctx.Session.GetCookieFlashMessage() + assert.Equal(t, `Branch protection for rule "`+branch+`" has been updated.`, flashMsg.SuccessMsg) } } @@ -575,7 +631,7 @@ func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { defer tests.PrintCurrentTest(t)() // create a context for a currently non-existent repository - ctx.Reponame = fmt.Sprintf("repo-tmp-push-create-%s", u.Scheme) + ctx.Reponame = "repo-tmp-push-create-" + u.Scheme u.Path = ctx.GitPath() // Create a temporary directory @@ -606,7 +662,7 @@ func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { // Now add a remote that is invalid to "Push To Create" invalidCtx := ctx - invalidCtx.Reponame = fmt.Sprintf("invalid/repo-tmp-push-create-%s", u.Scheme) + invalidCtx.Reponame = "invalid/repo-tmp-push-create-" + u.Scheme u.Path = invalidCtx.GitPath() t.Run("AddInvalidRemote", doGitAddRemote(tmpDir, "invalid", u)) @@ -658,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/", @@ -668,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 @@ -697,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) @@ -705,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) @@ -768,7 +824,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string }) t.Run("Push", func(t *testing.T) { - err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath}) + err := git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1) @@ -786,7 +842,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string assert.Contains(t, "Testing commit 1", prMsg.Body) assert.Equal(t, commit, prMsg.Head.Sha) - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) @@ -834,7 +890,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string }) t.Run("Push2", func(t *testing.T) { - err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath}) + err := git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) @@ -844,7 +900,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string assert.False(t, prMsg.HasMerged) assert.Equal(t, commit, prMsg.Head.Sha) - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index 43b151e0b6..7d42508bfe 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -10,7 +10,6 @@ import ( "net/http" "net/url" "os" - "path" "path/filepath" "strconv" "testing" @@ -35,12 +34,12 @@ func withKeyFile(t *testing.T, keyname string, callback func(string)) { err = ssh.GenKeyPair(keyFile) assert.NoError(t, err) - err = os.WriteFile(path.Join(tmpDir, "ssh"), []byte("#!/bin/bash\n"+ + err = os.WriteFile(filepath.Join(tmpDir, "ssh"), []byte("#!/bin/bash\n"+ "ssh -o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\" -o \"IdentitiesOnly=yes\" -i \""+keyFile+"\" \"$@\""), 0o700) assert.NoError(t, err) // Setup ssh wrapper - t.Setenv("GIT_SSH", path.Join(tmpDir, "ssh")) + t.Setenv("GIT_SSH", filepath.Join(tmpDir, "ssh")) t.Setenv("GIT_SSH_COMMAND", "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i \""+keyFile+"\"") t.Setenv("GIT_SSH_VARIANT", "ssh") @@ -76,7 +75,7 @@ func onGiteaRun[T testing.TB](t T, callback func(T, *url.URL)) { u.Host = listener.Addr().String() defer func() { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute) s.Shutdown(ctx) cancel() }() @@ -89,7 +88,7 @@ func onGiteaRun[T testing.TB](t T, callback func(T, *url.URL)) { func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) { return func(t *testing.T) { - assert.NoError(t, git.CloneWithArgs(context.Background(), git.AllowLFSFiltersArgs(), u.String(), dstLocalPath, git.CloneRepoOptions{})) + assert.NoError(t, git.CloneWithArgs(t.Context(), git.AllowLFSFiltersArgs(), u.String(), dstLocalPath, git.CloneRepoOptions{})) exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md")) assert.NoError(t, err) assert.True(t, exist) @@ -98,7 +97,7 @@ func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) { func doPartialGitClone(dstLocalPath string, u *url.URL) func(*testing.T) { return func(t *testing.T) { - assert.NoError(t, git.CloneWithArgs(context.Background(), git.AllowLFSFiltersArgs(), u.String(), dstLocalPath, git.CloneRepoOptions{ + assert.NoError(t, git.CloneWithArgs(t.Context(), git.AllowLFSFiltersArgs(), u.String(), dstLocalPath, git.CloneRepoOptions{ Filter: "blob:none", })) exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md")) @@ -122,9 +121,9 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { // Init repository in dstPath assert.NoError(t, git.InitRepository(git.DefaultContext, dstPath, false, git.Sha1ObjectFormat.Name())) // forcibly set default branch to master - _, _, err := git.NewCommand(git.DefaultContext, "symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+dstPath), 0o644)) assert.NoError(t, git.AddChanges(dstPath, true)) signature := git.Signature{ Email: "test@example.com", @@ -141,21 +140,21 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "remote", "add").AddDynamicArguments(remoteName, u.String()).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("remote", "add").AddDynamicArguments(remoteName, u.String()).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "push", "-u").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("push", "-u").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "push").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("push").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.Error(t, err) } } @@ -164,7 +163,7 @@ func doGitAddSomeCommits(dstPath, branch string) func(*testing.T) { return func(t *testing.T) { doGitCheckoutBranch(dstPath, branch)(t) - assert.NoError(t, os.WriteFile(filepath.Join(dstPath, fmt.Sprintf("file-%s.txt", branch)), []byte(fmt.Sprintf("file %s", branch)), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, fmt.Sprintf("file-%s.txt", branch)), []byte("file "+branch), 0o644)) assert.NoError(t, git.AddChanges(dstPath, true)) signature := git.Signature{ Email: "test@test.test", @@ -173,35 +172,35 @@ func doGitAddSomeCommits(dstPath, branch string) func(*testing.T) { assert.NoError(t, git.CommitChanges(dstPath, git.CommitChangesOptions{ Committer: &signature, Author: &signature, - Message: fmt.Sprintf("update %s", branch), + Message: "update " + branch, })) } } func doGitCreateBranch(dstPath, branch string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "checkout", "-b").AddDynamicArguments(branch).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("checkout", "-b").AddDynamicArguments(branch).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, git.AllowLFSFiltersArgs()...).AddArguments("checkout").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommandNoGlobals(git.AllowLFSFiltersArgs()...).AddArguments("checkout").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitMerge(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "merge").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("merge").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPull(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, git.AllowLFSFiltersArgs()...).AddArguments("pull").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommandNoGlobals(git.AllowLFSFiltersArgs()...).AddArguments("pull").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } diff --git a/tests/integration/git_lfs_ssh_test.go b/tests/integration/git_lfs_ssh_test.go index 9cb7fd089b..64a403f513 100644 --- a/tests/integration/git_lfs_ssh_test.go +++ b/tests/integration/git_lfs_ssh_test.go @@ -4,7 +4,6 @@ package integration import ( - gocontext "context" "net/url" "slices" "strings" @@ -46,7 +45,7 @@ func TestGitLFSSSH(t *testing.T) { setting.LFS.AllowPureSSH = true require.NoError(t, cfg.Save()) - _, _, cmdErr := git.NewCommand(gocontext.Background(), "config", "lfs.sshtransfer", "always").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, cmdErr := git.NewCommand("config", "lfs.sshtransfer", "always").RunStdString(t.Context(), &git.RunOpts{Dir: dstPath}) assert.NoError(t, cmdErr) lfsCommitAndPushTest(t, dstPath, 10) }) @@ -55,9 +54,14 @@ func TestGitLFSSSH(t *testing.T) { return strings.Contains(s, "POST /api/internal/repo/user2/repo1.git/info/lfs/objects/batch") }) countUpload := slices.ContainsFunc(routerCalls, func(s string) bool { - return strings.Contains(s, "PUT /user2/repo1.git/info/lfs/objects/") + return strings.Contains(s, "PUT /api/internal/repo/user2/repo1.git/info/lfs/objects/") + }) + nonAPIRequests := slices.ContainsFunc(routerCalls, func(s string) bool { + fields := strings.Fields(s) + return !strings.HasPrefix(fields[1], "/api/") }) assert.NotZero(t, countBatch) assert.NotZero(t, countUpload) + assert.Zero(t, nonAPIRequests) }) } diff --git a/tests/integration/git_misc_test.go b/tests/integration/git_misc_test.go index 00ed2a766f..bf9e2c02ab 100644 --- a/tests/integration/git_misc_test.go +++ b/tests/integration/git_misc_test.go @@ -5,7 +5,6 @@ package integration import ( "bytes" - "context" "io" "net/url" "sync" @@ -91,7 +90,7 @@ func TestAgitPullPush(t *testing.T) { dstPath := t.TempDir() doGitClone(dstPath, u)(t) - gitRepo, err := git.OpenRepository(context.Background(), dstPath) + gitRepo, err := git.OpenRepository(t.Context(), dstPath) assert.NoError(t, err) defer gitRepo.Close() @@ -102,10 +101,10 @@ func TestAgitPullPush(t *testing.T) { assert.NoError(t, err) // push to create an agit pull request - err = git.NewCommand(git.DefaultContext, "push", "origin", + err = git.NewCommand("push", "origin", "-o", "title=test-title", "-o", "description=test-description", "HEAD:refs/for/master/test-agit-push", - ).Run(&git.RunOpts{Dir: dstPath}) + ).Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // check pull request exist @@ -119,20 +118,20 @@ func TestAgitPullPush(t *testing.T) { assert.NoError(t, err) // push 2 - err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test-agit-push").Run(&git.RunOpts{Dir: dstPath}) + err = git.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // reset to first commit - err = git.NewCommand(git.DefaultContext, "reset", "--hard", "HEAD~1").Run(&git.RunOpts{Dir: dstPath}) + err = git.NewCommand("reset", "--hard", "HEAD~1").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // test force push without confirm - _, stderr, err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test-agit-push").RunStdString(&git.RunOpts{Dir: dstPath}) + _, stderr, err := git.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.Error(t, err) assert.Contains(t, stderr, "[remote rejected] HEAD -> refs/for/master/test-agit-push (request `force-push` push option)") // test force push with confirm - err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push").Run(&git.RunOpts{Dir: dstPath}) + err = git.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) }) } diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index e68f8bfce2..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) @@ -170,7 +170,7 @@ func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gi dbBranches := make([]*git_model.Branch, 0) require.NoError(t, db.GetEngine(db.DefaultContext).Where("repo_id=?", repo.ID).Find(&dbBranches)) - assert.Equalf(t, len(pushedBranches), len(dbBranches), "mismatched number of branches in db") + assert.Lenf(t, dbBranches, len(pushedBranches), "mismatched number of branches in db") dbBranchesMap := make(map[string]*git_model.Branch, len(dbBranches)) for _, branch := range dbBranches { dbBranchesMap[branch.Name] = branch @@ -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) { @@ -204,8 +204,8 @@ func TestPushPullRefs(t *testing.T) { dstPath := t.TempDir() doGitClone(dstPath, u)(t) - cmd := git.NewCommand(git.DefaultContext, "push", "--delete", "origin", "refs/pull/2/head") - stdout, stderr, err := cmd.RunStdString(&git.RunOpts{ + cmd := git.NewCommand("push", "--delete", "origin", "refs/pull/2/head") + stdout, stderr, err := cmd.RunStdString(git.DefaultContext, &git.RunOpts{ Dir: dstPath, }) assert.Error(t, err) diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go index 15336b9b81..e984fd3aad 100644 --- a/tests/integration/git_smart_http_test.go +++ b/tests/integration/git_smart_http_test.go @@ -9,60 +9,90 @@ import ( "net/url" "testing" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGitSmartHTTP(t *testing.T) { - onGiteaRun(t, testGitSmartHTTP) + onGiteaRun(t, func(t *testing.T, u *url.URL) { + testGitSmartHTTP(t, u) + testRenamedRepoRedirect(t) + }) } func testGitSmartHTTP(t *testing.T, u *url.URL) { kases := []struct { - p string - code int + method, path string + code int }{ { - p: "user2/repo1/info/refs", + path: "user2/repo1/info/refs", code: http.StatusOK, }, { - p: "user2/repo1/HEAD", + method: "HEAD", + path: "user2/repo1/info/refs", + code: http.StatusOK, + }, + { + path: "user2/repo1/HEAD", code: http.StatusOK, }, { - p: "user2/repo1/objects/info/alternates", + path: "user2/repo1/objects/info/alternates", code: http.StatusNotFound, }, { - p: "user2/repo1/objects/info/http-alternates", + path: "user2/repo1/objects/info/http-alternates", code: http.StatusNotFound, }, { - p: "user2/repo1/../../custom/conf/app.ini", + path: "user2/repo1/../../custom/conf/app.ini", code: http.StatusNotFound, }, { - p: "user2/repo1/objects/info/../../../../custom/conf/app.ini", + path: "user2/repo1/objects/info/../../../../custom/conf/app.ini", code: http.StatusNotFound, }, { - p: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, + path: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, code: http.StatusBadRequest, }, } for _, kase := range kases { - t.Run(kase.p, func(t *testing.T) { - p := u.String() + kase.p - req, err := http.NewRequest("GET", p, nil) - assert.NoError(t, err) + t.Run(kase.path, func(t *testing.T) { + req, err := http.NewRequest(util.IfZero(kase.method, "GET"), u.String()+kase.path, nil) + require.NoError(t, err) req.SetBasicAuth("user2", userPassword) resp, err := http.DefaultClient.Do(req) - assert.NoError(t, err) + require.NoError(t, err) defer resp.Body.Close() - assert.EqualValues(t, kase.code, resp.StatusCode) + assert.Equal(t, kase.code, resp.StatusCode) _, err = io.ReadAll(resp.Body) - assert.NoError(t, err) + require.NoError(t, err) }) } } + +func testRenamedRepoRedirect(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() + + // git client requires to get a 301 redirect response before 401 unauthorized response + req := NewRequest(t, "GET", "/user2/oldrepo1/info/refs") + resp := MakeRequest(t, req, http.StatusMovedPermanently) + redirect := resp.Header().Get("Location") + assert.Equal(t, "/user2/repo1/info/refs", redirect) + + req = NewRequest(t, "GET", redirect) + resp = MakeRequest(t, req, http.StatusUnauthorized) + assert.Equal(t, "Unauthorized\n", resp.Body.String()) + + req = NewRequest(t, "GET", redirect).AddBasicAuth("user2") + resp = MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "65f1bf27bc3bf70f64657658635e66094edbcb4d\trefs/tags/v1.1") +} diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_ssh_git_test.go index 31695fb2e1..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") @@ -99,26 +134,14 @@ func TestGPGGit(t *testing.T) { testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateCRUDFile-Always", crudActionCreateFile( t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { - assert.NotNil(t, response.Verification) - if response.Verification == nil { - assert.FailNow(t, "no verification provided with response! %v", response) - } - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - } + require.NotNil(t, response.Verification, "no verification provided with response! %v", response) + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile( t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) { - assert.NotNil(t, response.Verification) - if response.Verification == nil { - assert.FailNow(t, "no verification provided with response! %v", response) - } - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - } + require.NotNil(t, response.Verification, "no verification provided with response! %v", response) + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) }) @@ -129,14 +152,8 @@ func TestGPGGit(t *testing.T) { testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile( t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) { - assert.NotNil(t, response.Verification) - if response.Verification == nil { - assert.FailNow(t, "no verification provided with response! %v", response) - } - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - } + require.NotNil(t, response.Verification, "no verification provided with response! %v", response) + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) }) @@ -147,18 +164,9 @@ func TestGPGGit(t *testing.T) { testCtx := NewAPITestContext(t, username, "initial-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { - assert.NotNil(t, branch.Commit) - if branch.Commit == nil { - assert.FailNow(t, "no commit provided with branch! %v", branch) - } - assert.NotNil(t, branch.Commit.Verification) - if branch.Commit.Verification == nil { - assert.FailNow(t, "no verification provided with branch commit! %v", branch.Commit) - } - assert.True(t, branch.Commit.Verification.Verified) - if !branch.Commit.Verification.Verified { - t.FailNow() - } + require.NotNil(t, branch.Commit, "no commit provided with branch! %v", branch) + require.NotNil(t, branch.Commit.Verification, "no verification provided with branch commit! %v", branch.Commit) + require.True(t, branch.Commit.Verification.Verified) assert.Equal(t, "gitea@fake.local", branch.Commit.Verification.Signer.Email) })) }) @@ -181,11 +189,7 @@ func TestGPGGit(t *testing.T) { t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - return - } + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) }) @@ -197,11 +201,7 @@ func TestGPGGit(t *testing.T) { t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CreateCRUDFile-Always", crudActionCreateFile( t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - return - } + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) }) @@ -273,7 +273,7 @@ func crudActionCreateFile(_ *testing.T, ctx APITestContext, user *user_model.Use Email: user.Email, }, }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))), + ContentBase64: base64.StdEncoding.EncodeToString([]byte("This is new text for " + path)), }, callback...) } diff --git a/tests/integration/html_helper.go b/tests/integration/html_helper.go index 2217ddec2e..4d589b32e7 100644 --- a/tests/integration/html_helper.go +++ b/tests/integration/html_helper.go @@ -42,12 +42,13 @@ func (doc *HTMLDoc) GetCSRF() string { return doc.GetInputValueByName("_csrf") } -// AssertElement check if element by selector exists or does not exist depending on checkExists -func (doc *HTMLDoc) AssertElement(t testing.TB, selector string, checkExists bool) { +// 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) - if checkExists { - assert.Equal(t, 1, sel.Length()) - } else { - assert.Equal(t, 0, sel.Length()) + switch v := any(checkExists).(type) { + case bool: + assert.Equal(t, v, sel.Length() > 0) + case int: + assert.Equal(t, v, sel.Length()) } } diff --git a/tests/integration/incoming_email_test.go b/tests/integration/incoming_email_test.go index e968a2956e..6ccf82a9eb 100644 --- a/tests/integration/incoming_email_test.go +++ b/tests/integration/incoming_email_test.go @@ -50,12 +50,12 @@ func TestIncomingEmail(t *testing.T) { ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload) assert.NoError(t, err) assert.IsType(t, ref, new(issues_model.Issue)) - assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID) + assert.Equal(t, issue.ID, ref.(*issues_model.Issue).ID) ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload) assert.NoError(t, err) assert.IsType(t, ref, new(issues_model.Comment)) - assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID) + assert.Equal(t, comment.ID, ref.(*issues_model.Comment).ID) }) t.Run("Token", func(t *testing.T) { diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 46b93b0a10..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 ( @@ -29,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/routers" gitea_context "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" @@ -118,12 +119,11 @@ type TestSession struct { jar http.CookieJar } -func (s *TestSession) GetCookie(name string) *http.Cookie { +func (s *TestSession) GetRawCookie(name string) *http.Cookie { baseURL, err := url.Parse(setting.AppURL) if err != nil { return nil } - for _, c := range s.jar.Cookies(baseURL) { if c.Name == name { return c @@ -132,6 +132,20 @@ func (s *TestSession) GetCookie(name string) *http.Cookie { return nil } +func (s *TestSession) GetSiteCookie(name string) string { + c := s.GetRawCookie(name) + if c != nil { + v, _ := url.QueryUnescape(c.Value) + return v + } + return "" +} + +func (s *TestSession) GetCookieFlashMessage() *middleware.Flash { + cookie := s.GetSiteCookie(gitea_context.CookieNameFlash) + return middleware.ParseCookieFlashMessage(cookie) +} + func (s *TestSession) MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest.ResponseRecorder { t.Helper() req := rw.Request @@ -235,55 +249,19 @@ func loginUserWithPassword(t testing.TB, userName, password string) *TestSession // token has to be unique this counter take care of var tokenCounter int64 -// getTokenForLoggedInUser returns a token for a logged in user. -// The scope is an optional list of snake_case strings like the frontend form fields, -// but without the "scope_" prefix. +// getTokenForLoggedInUser returns a token for a logged-in user. func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.AccessTokenScope) string { t.Helper() - var token string - req := NewRequest(t, "GET", "/user/settings/applications") - resp := session.MakeRequest(t, req, http.StatusOK) - var csrf string - for _, cookie := range resp.Result().Cookies() { - if cookie.Name != "_csrf" { - continue - } - csrf = cookie.Value - break - } - if csrf == "" { - doc := NewHTMLParser(t, resp.Body) - csrf = doc.GetCSRF() - } - assert.NotEmpty(t, csrf) urlValues := url.Values{} - urlValues.Add("_csrf", csrf) + urlValues.Add("_csrf", GetUserCSRFToken(t, session)) urlValues.Add("name", fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1))) for _, scope := range scopes { - urlValues.Add("scope", string(scope)) + urlValues.Add("scope-dummy", string(scope)) // it only needs to start with "scope-" to be accepted } - req = NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - - // Log the flash values on failure - if !assert.Equal(t, []string{"/user/settings/applications"}, resp.Result().Header["Location"]) { - for _, cookie := range resp.Result().Cookies() { - if cookie.Name != gitea_context.CookieNameFlash { - continue - } - flash, _ := url.ParseQuery(cookie.Value) - for key, value := range flash { - t.Logf("Flash %q: %q", key, value) - } - } - } - - req = NewRequest(t, "GET", "/user/settings/applications") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - token = htmlDoc.doc.Find(".ui.info p").Text() - assert.NotEmpty(t, token) - return token + req := NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues) + session.MakeRequest(t, req, http.StatusSeeOther) + flashes := session.GetCookieFlashMessage() + return flashes.InfoMsg } type RequestWrapper struct { @@ -332,7 +310,7 @@ func NewRequestWithValues(t testing.TB, method, urlStr string, values map[string func NewRequestWithURLValues(t testing.TB, method, urlStr string, urlValues url.Values) *RequestWrapper { t.Helper() - return NewRequestWithBody(t, method, urlStr, bytes.NewBufferString(urlValues.Encode())). + return NewRequestWithBody(t, method, urlStr, strings.NewReader(urlValues.Encode())). SetHeader("Content-Type", "application/x-www-form-urlencoded") } @@ -382,7 +360,7 @@ func MakeRequestNilResponseRecorder(t testing.TB, rw *RequestWrapper, expectedSt recorder := NewNilResponseRecorder() testWebRoutes.ServeHTTP(recorder, req) if expectedStatus != NoExpectedStatus { - if !assert.EqualValues(t, expectedStatus, recorder.Code, + if !assert.Equal(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String()) { logUnexpectedResponse(t, &recorder.ResponseRecorder) } @@ -396,7 +374,7 @@ func MakeRequestNilResponseHashSumRecorder(t testing.TB, rw *RequestWrapper, exp recorder := NewNilResponseHashSumRecorder() testWebRoutes.ServeHTTP(recorder, req) if expectedStatus != NoExpectedStatus { - if !assert.EqualValues(t, expectedStatus, recorder.Code, + if !assert.Equal(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String()) { logUnexpectedResponse(t, &recorder.ResponseRecorder) } @@ -458,9 +436,9 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile // GetUserCSRFToken returns CSRF token for current user func GetUserCSRFToken(t testing.TB, session *TestSession) string { t.Helper() - cookie := session.GetCookie("_csrf") + cookie := session.GetSiteCookie("_csrf") require.NotEmpty(t, cookie) - return cookie.Value + return cookie } // GetUserCSRFToken returns CSRF token for anonymous user (not logged in) diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 4617c5f89a..b5dca58357 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -4,7 +4,6 @@ package integration import ( - "context" "fmt" "html/template" "net/http" @@ -33,7 +32,7 @@ import ( func getIssuesSelection(t testing.TB, htmlDoc *HTMLDoc) *goquery.Selection { issueList := htmlDoc.doc.Find("#issue-list") - assert.EqualValues(t, 1, issueList.Length()) + assert.Equal(t, 1, issueList.Length()) return issueList.Find(".flex-item").Find(".issue-title") } @@ -77,19 +76,16 @@ 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 - } - assert.EqualValues(t, expectedNumIssues, issuesSelection.Length()) + ), setting.UI.IssuePagingNum) + assert.Equal(t, expectedNumIssues, issuesSelection.Length()) issuesSelection.Each(func(_ int, selection *goquery.Selection) { issue := getIssue(t, repo.ID, selection) - assert.EqualValues(t, user.ID, issue.PosterID) + assert.Equal(t, user.ID, issue.PosterID) }) } @@ -101,7 +97,7 @@ func TestViewIssuesKeyword(t *testing.T) { RepoID: repo.ID, Index: 1, }) - issues.UpdateIssueIndexer(context.Background(), issue.ID) + issues.UpdateIssueIndexer(t.Context(), issue.ID) time.Sleep(time.Second * 1) const keyword = "first" req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword) @@ -109,7 +105,7 @@ func TestViewIssuesKeyword(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) issuesSelection := getIssuesSelection(t, htmlDoc) - assert.EqualValues(t, 1, issuesSelection.Length()) + assert.Equal(t, 1, issuesSelection.Length()) issuesSelection.Each(func(_ int, selection *goquery.Selection) { issue := getIssue(t, repo.ID, selection) assert.False(t, issue.IsClosed) @@ -152,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) @@ -174,7 +186,7 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, htmlDoc = NewHTMLParser(t, resp.Body) - val := htmlDoc.doc.Find(".comment-list .comment .render-content p").Eq(commentCount).Text() + val := strings.TrimSpace(htmlDoc.doc.Find(".comment-list .comment .render-content").Eq(commentCount).Text()) assert.Equal(t, content, val) idAttr, has := htmlDoc.doc.Find(".comment-list .comment").Eq(commentCount).Attr("id") @@ -185,6 +197,15 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, return int64(id) } +func testIssueChangeMilestone(t *testing.T, session *TestSession, repoLink string, issueID, milestoneID int64) { + req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/milestone?issue_ids=%d", issueID), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "id": strconv.FormatInt(milestoneID, 10), + }) + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, `{"ok":true}`, strings.TrimSpace(resp.Body.String())) +} + func TestNewIssue(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user2") @@ -196,21 +217,21 @@ func TestEditIssue(t *testing.T) { session := loginUser(t, "user2") issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description") - req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/content", issueURL), map[string]string{ + req := NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{ "_csrf": GetUserCSRFToken(t, session), "content": "modified content", "context": fmt.Sprintf("/%s/%s", "user2", "repo1"), }) session.MakeRequest(t, req, http.StatusOK) - req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/content", issueURL), map[string]string{ + req = NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{ "_csrf": GetUserCSRFToken(t, session), "content": "modified content", "context": fmt.Sprintf("/%s/%s", "user2", "repo1"), }) session.MakeRequest(t, req, http.StatusBadRequest) - req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/content", issueURL), map[string]string{ + req = NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{ "_csrf": GetUserCSRFToken(t, session), "content": "modified content", "content_version": "1", @@ -474,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()) @@ -511,7 +529,7 @@ func TestSearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) assert.Len(t, apiIssues, 20) query.Add("limit", "5") @@ -519,7 +537,7 @@ func TestSearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) assert.Len(t, apiIssues, 5) query = url.Values{"assigned": {"true"}, "state": {"all"}} @@ -568,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") @@ -646,7 +661,7 @@ func TestGetIssueInfo(t *testing.T) { } DecodeJSON(t, resp, &respStruct) - assert.EqualValues(t, issue.ID, respStruct.ConvertedIssue.ID) + assert.Equal(t, issue.ID, respStruct.ConvertedIssue.ID) assert.Contains(t, string(respStruct.RenderedLabels), `"labels-list"`) } @@ -666,7 +681,7 @@ func TestUpdateIssueDeadline(t *testing.T) { req := NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": "2022-04-06"}) session.MakeRequest(t, req, http.StatusOK) issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) - assert.EqualValues(t, "2022-04-06", issueAfter.DeadlineUnix.FormatDate()) + assert.Equal(t, "2022-04-06", issueAfter.DeadlineUnix.FormatDate()) req = NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": ""}) session.MakeRequest(t, req, http.StatusOK) @@ -687,8 +702,8 @@ func TestIssueReferenceURL(t *testing.T) { // the "reference" uses relative URLs, then JS code will convert them to absolute URLs for current origin, in case users are using multiple domains ref, _ := htmlDoc.Find(`.timeline-item.comment.first .reference-issue`).Attr("data-reference") - assert.EqualValues(t, "/user2/repo1/issues/1#issue-1", ref) + assert.Equal(t, "/user2/repo1/issues/1#issue-1", ref) ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.first) .reference-issue`).Attr("data-reference") - assert.EqualValues(t, "/user2/repo1/issues/1#issuecomment-2", ref) + assert.Equal(t, "/user2/repo1/issues/1#issuecomment-2", ref) } diff --git a/tests/integration/lfs_local_endpoint_test.go b/tests/integration/lfs_local_endpoint_test.go index d42888bbe1..e67f0712a3 100644 --- a/tests/integration/lfs_local_endpoint_test.go +++ b/tests/integration/lfs_local_endpoint_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/url" "os" "path/filepath" @@ -41,55 +40,55 @@ func TestDetermineLocalEndpoint(t *testing.T) { { cloneurl: root, lfsurl: "", - expected: str2url(fmt.Sprintf("file://%s", root)), + expected: str2url("file://" + root), }, // case 1 { cloneurl: root, lfsurl: lfsroot, - expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + expected: str2url("file://" + lfsroot), }, // case 2 { cloneurl: "https://git.com/repo.git", lfsurl: lfsroot, - expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + expected: str2url("file://" + lfsroot), }, // case 3 { cloneurl: rootdotgit, lfsurl: "", - expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + expected: str2url("file://" + filepath.Join(rootdotgit, ".git")), }, // case 4 { cloneurl: "", lfsurl: rootdotgit, - expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + expected: str2url("file://" + filepath.Join(rootdotgit, ".git")), }, // case 5 { cloneurl: rootdotgit, lfsurl: rootdotgit, - expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + expected: str2url("file://" + filepath.Join(rootdotgit, ".git")), }, // case 6 { - cloneurl: fmt.Sprintf("file://%s", root), + cloneurl: "file://" + root, lfsurl: "", - expected: str2url(fmt.Sprintf("file://%s", root)), + expected: str2url("file://" + root), }, // case 7 { - cloneurl: fmt.Sprintf("file://%s", root), - lfsurl: fmt.Sprintf("file://%s", lfsroot), - expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + cloneurl: "file://" + root, + lfsurl: "file://" + lfsroot, + expected: str2url("file://" + lfsroot), }, // case 8 { cloneurl: root, - lfsurl: fmt.Sprintf("file://%s", lfsroot), - expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + lfsurl: "file://" + lfsroot, + expected: str2url("file://" + lfsroot), }, // case 9 { diff --git a/tests/integration/lfs_view_test.go b/tests/integration/lfs_view_test.go index a0e004ed58..c26ece22be 100644 --- a/tests/integration/lfs_view_test.go +++ b/tests/integration/lfs_view_test.go @@ -4,7 +4,6 @@ package integration import ( - "context" "fmt" "net/http" "strings" @@ -39,7 +38,7 @@ func TestLFSRender(t *testing.T) { doc := NewHTMLParser(t, resp.Body).doc fileInfo := doc.Find("div.file-info-entry").First().Text() - assert.Contains(t, fileInfo, "Stored with Git LFS") + assert.Contains(t, fileInfo, "LFS") content := doc.Find("div.file-view").Text() assert.Contains(t, content, "Testing documents in LFS") @@ -55,7 +54,7 @@ func TestLFSRender(t *testing.T) { doc := NewHTMLParser(t, resp.Body).doc fileInfo := doc.Find("div.file-info-entry").First().Text() - assert.Contains(t, fileInfo, "Stored with Git LFS") + assert.Contains(t, fileInfo, "LFS") src, exists := doc.Find(".file-view img").Attr("src") assert.True(t, exists, "The image should be in an <img> tag") @@ -69,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, "Stored with Git LFS") + 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 @@ -143,7 +143,7 @@ func TestLFSLockView(t *testing.T) { defer tests.PrintCurrentTest(t)() // make sure the display names are different, or the test is meaningless - require.NoError(t, repo3.LoadOwner(context.Background())) + require.NoError(t, repo3.LoadOwner(t.Context())) require.NotEqual(t, user2.DisplayName(), repo3.Owner.DisplayName()) req := NewRequest(t, "GET", fmt.Sprintf("/%s/settings/lfs/locks", repo3.FullName())) diff --git a/tests/integration/linguist_test.go b/tests/integration/linguist_test.go index 2d50dc599a..d156dd6f3d 100644 --- a/tests/integration/linguist_test.go +++ b/tests/integration/linguist_test.go @@ -4,7 +4,6 @@ package integration import ( - "context" "net/url" "strconv" "strings" @@ -246,7 +245,7 @@ func TestLinguist(t *testing.T) { assert.NoError(t, err) assert.NoError(t, stats.UpdateRepoIndexer(repo)) - assert.NoError(t, queue.GetManager().FlushAll(context.Background(), 10*time.Second)) + assert.NoError(t, queue.GetManager().FlushAll(t.Context(), 10*time.Second)) stats, err := repo_model.GetTopLanguageStats(db.DefaultContext, repo, len(c.FilesToAdd)) assert.NoError(t, err) diff --git a/tests/integration/links_test.go b/tests/integration/links_test.go index b54f670c23..ec59e147d2 100644 --- a/tests/integration/links_test.go +++ b/tests/integration/links_test.go @@ -52,15 +52,19 @@ func TestRedirectsNoLogin(t *testing.T) { redirects := []struct{ from, to string }{ {"/user2/repo1/commits/master", "/user2/repo1/commits/branch/master"}, {"/user2/repo1/src/master", "/user2/repo1/src/branch/master"}, - {"/user2/repo1/src/master/file.txt", "/user2/repo1/src/branch/master/file.txt"}, - {"/user2/repo1/src/master/directory/file.txt", "/user2/repo1/src/branch/master/directory/file.txt"}, - {"/user/avatar/Ghost/-1", "/assets/img/avatar_default.png"}, + {"/user2/repo1/src/master/a%2fb.txt", "/user2/repo1/src/branch/master/a%2fb.txt"}, + {"/user2/repo1/src/master/directory/file.txt?a=1", "/user2/repo1/src/branch/master/directory/file.txt?a=1"}, + {"/user2/repo1/src/branch/master/directory/file.txt?raw=1&other=2", "/user2/repo1/raw/branch/master/directory/file.txt"}, + {"/user2/repo1/tree/a%2fb?a=1", "/user2/repo1/src/a%2fb?a=1"}, + {"/user2/repo1/blob/123456/%20?a=1", "/user2/repo1/src/commit/123456/%20?a=1"}, + {"/user/avatar/GhosT/-1", "/assets/img/avatar_default.png"}, + {"/user/avatar/Gitea-ActionS/0", "/assets/img/avatar_default.png"}, {"/api/v1/swagger", "/api/swagger"}, } for _, c := range redirects { req := NewRequest(t, "GET", c.from) resp := MakeRequest(t, req, http.StatusSeeOther) - assert.EqualValues(t, path.Join(setting.AppSubURL, c.to), test.RedirectURL(resp)) + assert.Equal(t, path.Join(setting.AppSubURL, c.to), test.RedirectURL(resp)) } } diff --git a/tests/integration/markup_external_test.go b/tests/integration/markup_external_test.go index 2d713b0eb9..47d143a29e 100644 --- a/tests/integration/markup_external_test.go +++ b/tests/integration/markup_external_test.go @@ -27,7 +27,7 @@ func TestExternalMarkupRenderer(t *testing.T) { req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") resp := MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) bs, err := io.ReadAll(resp.Body) assert.NoError(t, err) @@ -36,25 +36,25 @@ func TestExternalMarkupRenderer(t *testing.T) { div := doc.Find("div.file-view") data, err := div.Html() assert.NoError(t, err) - assert.EqualValues(t, "<div>\n\ttest external renderer\n</div>", strings.TrimSpace(data)) + assert.Equal(t, "<div>\n\ttest external renderer\n</div>", strings.TrimSpace(data)) r := markup.GetRendererByFileName("a.html").(*external.Renderer) r.RenderContentMode = setting.RenderContentModeIframe req = NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") resp = MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) doc = NewHTMLParser(t, bytes.NewBuffer(bs)) iframe := doc.Find("iframe") - assert.EqualValues(t, "/user30/renderer/render/branch/master/README.html", iframe.AttrOr("src", "")) + assert.Equal(t, "/user30/renderer/render/branch/master/README.html", iframe.AttrOr("src", "")) req = NewRequest(t, "GET", "/user30/renderer/render/branch/master/README.html") resp = MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, "frame-src 'self'; sandbox allow-scripts", resp.Header().Get("Content-Security-Policy")) - assert.EqualValues(t, "<div>\n\ttest external renderer\n</div>\n", string(bs)) + assert.Equal(t, "frame-src 'self'; sandbox allow-scripts", resp.Header().Get("Content-Security-Policy")) + assert.Equal(t, "<div>\n\ttest external renderer\n</div>\n", string(bs)) } diff --git a/tests/integration/migrate_test.go b/tests/integration/migrate_test.go index 59dd6907db..a89dc8b85c 100644 --- a/tests/integration/migrate_test.go +++ b/tests/integration/migrate_test.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "path/filepath" + "strconv" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -84,7 +85,7 @@ func TestMigrateGiteaForm(t *testing.T) { assert.True(t, exists, "The template has changed") serviceInput, exists := form.Find(`input[name="service"]`).Attr("value") assert.True(t, exists) - assert.EqualValues(t, fmt.Sprintf("%d", structs.GiteaService), serviceInput) + assert.Equal(t, fmt.Sprintf("%d", structs.GiteaService), serviceInput) // Step 4: submit the migration to only migrate issues migratedRepoName := "otherrepo" req = NewRequestWithValues(t, "POST", link, map[string]string{ @@ -95,12 +96,12 @@ func TestMigrateGiteaForm(t *testing.T) { "issues": "on", "repo_name": migratedRepoName, "description": "", - "uid": fmt.Sprintf("%d", repoOwner.ID), + "uid": strconv.FormatInt(repoOwner.ID, 10), }) resp = session.MakeRequest(t, req, http.StatusSeeOther) // Step 5: a redirection displays the migrated repository loc := resp.Header().Get("Location") - assert.EqualValues(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc) + assert.Equal(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc) // Step 6: check the repo was created unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: migratedRepoName}) }) diff --git a/tests/integration/migration-test/migration_test.go b/tests/integration/migration-test/migration_test.go index 616ccf291a..0fc8a6e24d 100644 --- a/tests/integration/migration-test/migration_test.go +++ b/tests/integration/migration-test/migration_test.go @@ -38,7 +38,7 @@ var currentEngine *xorm.Engine func initMigrationTest(t *testing.T) func() { testlogger.Init() giteaRoot := test.SetupGiteaRoot() - setting.AppPath = path.Join(giteaRoot, "gitea") + setting.AppPath = filepath.Join(giteaRoot, "gitea") if _, err := os.Stat(setting.AppPath); err != nil { testlogger.Fatalf(fmt.Sprintf("Could not find gitea binary at %s\n", setting.AppPath)) } @@ -47,16 +47,16 @@ func initMigrationTest(t *testing.T) func() { if giteaConf == "" { testlogger.Fatalf("Environment variable $GITEA_CONF not set\n") } else if !path.IsAbs(giteaConf) { - setting.CustomConf = path.Join(giteaRoot, giteaConf) + setting.CustomConf = filepath.Join(giteaRoot, giteaConf) } else { setting.CustomConf = giteaConf } - unittest.InitSettings() + unittest.InitSettingsForTesting() assert.NotEmpty(t, setting.RepoRootPath) - assert.NoError(t, unittest.SyncDirs(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) - assert.NoError(t, git.InitFull(context.Background())) + assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + assert.NoError(t, git.InitFull(t.Context())) setting.LoadDBSetting() setting.InitLoggersForTest() @@ -140,10 +140,10 @@ func restoreOldDB(t *testing.T, version string) { assert.NoError(t, err) defer db.Close() - _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)) + _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name) assert.NoError(t, err) - _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)) + _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + setting.Database.Name) assert.NoError(t, err) db.Close() @@ -170,10 +170,10 @@ func restoreOldDB(t *testing.T, version string) { } defer db.Close() - _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)) + _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name) assert.NoError(t, err) - _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)) + _, err = db.Exec("CREATE DATABASE " + setting.Database.Name) assert.NoError(t, err) db.Close() @@ -195,7 +195,7 @@ func restoreOldDB(t *testing.T, version string) { if !schrows.Next() { // Create and setup a DB schema - _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)) + _, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema) assert.NoError(t, err) } schrows.Close() @@ -248,7 +248,7 @@ func restoreOldDB(t *testing.T, version string) { } } -func wrappedMigrate(x *xorm.Engine) error { +func wrappedMigrate(ctx context.Context, x *xorm.Engine) error { currentEngine = x return migrations.Migrate(x) } @@ -259,13 +259,13 @@ func doMigrationTest(t *testing.T, version string) { setting.InitSQLLoggersForCli(log.INFO) - err := db.InitEngineWithMigration(context.Background(), wrappedMigrate) + err := db.InitEngineWithMigration(t.Context(), wrappedMigrate) assert.NoError(t, err) currentEngine.Close() beans, _ := db.NamesToBean() - err = db.InitEngineWithMigration(context.Background(), func(x *xorm.Engine) error { + err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x *xorm.Engine) error { currentEngine = x return migrate_base.RecreateTables(beans...)(x) }) @@ -273,7 +273,7 @@ func doMigrationTest(t *testing.T, version string) { currentEngine.Close() // We do this a second time to ensure that there is not a problem with retained indices - err = db.InitEngineWithMigration(context.Background(), func(x *xorm.Engine) error { + err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x *xorm.Engine) error { currentEngine = x return migrate_base.RecreateTables(beans...)(x) }) diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index 77050c4bbc..c33b2eb04d 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -4,11 +4,12 @@ package integration import ( - "context" + "slices" "testing" "code.gitea.io/gitea/models/db" 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/modules/git" @@ -20,11 +21,13 @@ import ( "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMirrorPull(t *testing.T) { defer tests.PrepareTestEnv(t)() + ctx := t.Context() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) repoPath := repo_model.RepoPath(user.Name, repo.Name) @@ -36,24 +39,28 @@ func TestMirrorPull(t *testing.T) { Mirror: true, CloneAddr: repoPath, Wiki: true, - Releases: false, + Releases: true, } - mirrorRepo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ + mirrorRepo, err := repo_service.CreateRepositoryDirectly(ctx, user, user, repo_service.CreateRepoOptions{ Name: opts.RepoName, Description: opts.Description, IsPrivate: opts.Private, IsMirror: opts.Mirror, Status: repo_model.RepositoryBeingMigrated, - }) + }, false) assert.NoError(t, err) assert.True(t, mirrorRepo.IsMirror, "expected pull-mirror repo to be marked as a mirror immediately after its creation") - ctx := context.Background() - - mirror, err := repo_service.MigrateRepositoryGitData(ctx, user, mirrorRepo, opts, nil) + mirrorRepo, err = repo_service.MigrateRepositoryGitData(ctx, user, mirrorRepo, opts, nil) assert.NoError(t, err) + // these units should have been enabled + mirrorRepo.Units = nil + require.NoError(t, mirrorRepo.LoadUnits(ctx)) + assert.True(t, slices.ContainsFunc(mirrorRepo.Units, func(u *repo_model.RepoUnit) bool { return u.Type == unit.TypeReleases })) + assert.True(t, slices.ContainsFunc(mirrorRepo.Units, func(u *repo_model.RepoUnit) bool { return u.Type == unit.TypeWiki })) + gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo) assert.NoError(t, err) defer gitRepo.Close() @@ -61,10 +68,11 @@ func TestMirrorPull(t *testing.T) { findOptions := repo_model.FindReleasesOptions{ IncludeDrafts: true, IncludeTags: true, - RepoID: mirror.ID, + RepoID: mirrorRepo.ID, } initCount, err := db.Count[repo_model.Release](db.DefaultContext, findOptions) assert.NoError(t, err) + assert.Zero(t, initCount) // no sync yet, so even though there is a tag in source repo, the mirror's release table is still empty assert.NoError(t, release_service.CreateRelease(gitRepo, &repo_model.Release{ RepoID: repo.ID, @@ -80,24 +88,27 @@ func TestMirrorPull(t *testing.T) { IsTag: true, }, nil, "")) - _, err = repo_model.GetMirrorByRepoID(ctx, mirror.ID) + _, err = repo_model.GetMirrorByRepoID(ctx, mirrorRepo.ID) assert.NoError(t, err) - ok := mirror_service.SyncPullMirror(ctx, mirror.ID) + ok := mirror_service.SyncPullMirror(ctx, mirrorRepo.ID) assert.True(t, ok) + // actually there is a tag in the source repo, so after "sync", that tag will also come into the mirror + initCount++ + count, err := db.Count[repo_model.Release](db.DefaultContext, findOptions) assert.NoError(t, err) - assert.EqualValues(t, initCount+1, count) + assert.Equal(t, initCount+1, count) release, err := repo_model.GetRelease(db.DefaultContext, repo.ID, "v0.2") assert.NoError(t, err) assert.NoError(t, release_service.DeleteReleaseByID(ctx, repo, release, user, true)) - ok = mirror_service.SyncPullMirror(ctx, mirror.ID) + ok = mirror_service.SyncPullMirror(ctx, mirrorRepo.ID) assert.True(t, ok) count, err = db.Count[repo_model.Release](db.DefaultContext, findOptions) assert.NoError(t, err) - assert.EqualValues(t, initCount, count) + assert.Equal(t, initCount, count) } diff --git a/tests/integration/mirror_push_test.go b/tests/integration/mirror_push_test.go index 0dd8919bff..9b6d4c4017 100644 --- a/tests/integration/mirror_push_test.go +++ b/tests/integration/mirror_push_test.go @@ -4,12 +4,10 @@ package integration import ( - "context" "fmt" "net/http" "net/url" "strconv" - "strings" "testing" "time" @@ -20,7 +18,6 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/setting" - gitea_context "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/migrations" mirror_service "code.gitea.io/gitea/services/mirror" repo_service "code.gitea.io/gitea/services/repository" @@ -43,7 +40,7 @@ func testMirrorPush(t *testing.T, u *url.URL) { mirrorRepo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ Name: "test-push-mirror", - }) + }, true) assert.NoError(t, err) session := loginUser(t, user.Name) @@ -55,7 +52,7 @@ func testMirrorPush(t *testing.T, u *url.URL) { assert.NoError(t, err) assert.Len(t, mirrors, 1) - ok := mirror_service.SyncPushMirror(context.Background(), mirrors[0].ID) + ok := mirror_service.SyncPushMirror(t.Context(), mirrors[0].ID) assert.True(t, ok) srcGitRepo, err := gitrepo.OpenRepository(git.DefaultContext, srcRepo) @@ -92,9 +89,8 @@ func testCreatePushMirror(t *testing.T, session *TestSession, owner, repo, addre }) session.MakeRequest(t, req, http.StatusSeeOther) - flashCookie := session.GetCookie(gitea_context.CookieNameFlash) - assert.NotNil(t, flashCookie) - assert.Contains(t, flashCookie.Value, "success") + flashMsg := session.GetCookieFlashMessage() + assert.NotEmpty(t, flashMsg.SuccessMsg) } func doRemovePushMirror(t *testing.T, session *TestSession, owner, repo string, pushMirrorID int64) bool { @@ -104,8 +100,8 @@ func doRemovePushMirror(t *testing.T, session *TestSession, owner, repo string, "push_mirror_id": strconv.FormatInt(pushMirrorID, 10), }) resp := session.MakeRequest(t, req, NoExpectedStatus) - flashCookie := session.GetCookie(gitea_context.CookieNameFlash) - return resp.Code == http.StatusSeeOther && flashCookie != nil && strings.Contains(flashCookie.Value, "success") + flashMsg := session.GetCookieFlashMessage() + return resp.Code == http.StatusSeeOther && assert.NotEmpty(t, flashMsg.SuccessMsg) } func doUpdatePushMirror(t *testing.T, session *TestSession, owner, repo string, pushMirrorID int64, interval string) bool { @@ -132,18 +128,18 @@ func TestRepoSettingPushMirrorUpdate(t *testing.T) { pushMirrors, cnt, err := repo_model.GetPushMirrorsByRepoID(db.DefaultContext, repo2.ID, db.ListOptions{}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - assert.EqualValues(t, 24*time.Hour, pushMirrors[0].Interval) + assert.Equal(t, 24*time.Hour, pushMirrors[0].Interval) repo2PushMirrorID := pushMirrors[0].ID // update repo2 push mirror assert.True(t, doUpdatePushMirror(t, session, "user2", "repo2", repo2PushMirrorID, "10m0s")) pushMirror := unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) - assert.EqualValues(t, 10*time.Minute, pushMirror.Interval) + assert.Equal(t, 10*time.Minute, pushMirror.Interval) // avoid updating repo2 push mirror from repo1 assert.False(t, doUpdatePushMirror(t, session, "user2", "repo1", repo2PushMirrorID, "20m0s")) pushMirror = unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) - assert.EqualValues(t, 10*time.Minute, pushMirror.Interval) // not changed + assert.Equal(t, 10*time.Minute, pushMirror.Interval) // not changed // avoid deleting repo2 push mirror from repo1 assert.False(t, doRemovePushMirror(t, session, "user2", "repo1", repo2PushMirrorID)) diff --git a/tests/integration/nonascii_branches_test.go b/tests/integration/nonascii_branches_test.go index ae348d8173..cc71acf002 100644 --- a/tests/integration/nonascii_branches_test.go +++ b/tests/integration/nonascii_branches_test.go @@ -46,21 +46,21 @@ func TestNonAsciiBranches(t *testing.T) { { from: "master/badfile", to: "branch/master/badfile", - status: http.StatusNotFound, // it does not exists + status: http.StatusNotFound, // it does not exist }, { from: "ГлавнаяВетка", - to: "branch/%D0%93%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F%D0%92%D0%B5%D1%82%D0%BA%D0%B0", + to: "branch/%d0%93%d0%bb%d0%b0%d0%b2%d0%bd%d0%b0%d1%8f%d0%92%d0%b5%d1%82%d0%ba%d0%b0", status: http.StatusOK, }, { from: "а/б/в", - to: "branch/%D0%B0/%D0%B1/%D0%B2", + to: "branch/%d0%b0/%d0%b1/%d0%b2", status: http.StatusOK, }, { from: "Grüßen/README.md", - to: "branch/Gr%C3%BC%C3%9Fen/README.md", + to: "branch/Gr%c3%bc%c3%9fen/README.md", status: http.StatusOK, }, { @@ -70,7 +70,7 @@ func TestNonAsciiBranches(t *testing.T) { }, { from: "Plus+Is+Not+Space/Файл.md", - to: "branch/Plus+Is+Not+Space/%D0%A4%D0%B0%D0%B9%D0%BB.md", + to: "branch/Plus+Is+Not+Space/%d0%a4%d0%b0%d0%b9%d0%bb.md", status: http.StatusOK, }, { @@ -80,29 +80,29 @@ func TestNonAsciiBranches(t *testing.T) { }, { from: "ブランチ", - to: "branch/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81", + to: "branch/%e3%83%96%e3%83%a9%e3%83%b3%e3%83%81", status: http.StatusOK, }, // Tags { from: "Тэг", - to: "tag/%D0%A2%D1%8D%D0%B3", + to: "tag/%d0%a2%d1%8d%d0%b3", status: http.StatusOK, }, { from: "Ё/人", - to: "tag/%D0%81/%E4%BA%BA", + to: "tag/%d0%81/%e4%ba%ba", status: http.StatusOK, }, { from: "タグ", - to: "tag/%E3%82%BF%E3%82%B0", + to: "tag/%e3%82%bf%e3%82%b0", status: http.StatusOK, }, { from: "タグ/ファイル.md", - to: "tag/%E3%82%BF%E3%82%B0/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md", + to: "tag/%e3%82%bf%e3%82%b0/%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab.md", status: http.StatusOK, }, @@ -114,12 +114,12 @@ func TestNonAsciiBranches(t *testing.T) { }, { from: "Файл.md", - to: "branch/Plus+Is+Not+Space/%D0%A4%D0%B0%D0%B9%D0%BB.md", + to: "branch/Plus+Is+Not+Space/%d0%a4%d0%b0%d0%b9%d0%bb.md", status: http.StatusOK, }, { from: "ファイル.md", - to: "branch/Plus+Is+Not+Space/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md", + to: "branch/Plus+Is+Not+Space/%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab.md", status: http.StatusNotFound, // it's not on default branch }, @@ -131,7 +131,7 @@ func TestNonAsciiBranches(t *testing.T) { }, { from: "%E3%82%BF%E3%82%b0", - to: "tag/%E3%82%BF%E3%82%B0", + to: "tag/%E3%82%BF%E3%82%b0", status: http.StatusOK, }, { @@ -141,12 +141,12 @@ func TestNonAsciiBranches(t *testing.T) { }, { from: "%D0%81%2F%E4%BA%BA", - to: "tag/%D0%81/%E4%BA%BA", + to: "tag/%D0%81%2F%E4%BA%BA", status: http.StatusOK, }, { from: "Ё%2F%E4%BA%BA", - to: "tag/%D0%81/%E4%BA%BA", + to: "tag/%d0%81%2F%E4%BA%BA", status: http.StatusOK, }, { diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index d6f1ba33ec..d2228bae79 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -19,7 +19,7 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - oauth2_provider "code.gitea.io/gitea/services/oauth2_provider" + "code.gitea.io/gitea/services/oauth2_provider" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -77,7 +77,7 @@ func TestAuthorizeShow(t *testing.T) { resp := ctx.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - htmlDoc.AssertElement(t, "#authorize-app", true) + AssertHTMLElement(t, htmlDoc, "#authorize-app", true) htmlDoc.GetCSRF() } @@ -691,10 +691,6 @@ func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) { FullRepoName: "user2/commitsonpr", Private: false, }, - { - FullRepoName: "user2/test_commit_revert", - Private: true, - }, } assert.Equal(t, reposExpected, reposCaptured) diff --git a/tests/integration/org_count_test.go b/tests/integration/org_count_test.go index 8a33c218be..c48008e627 100644 --- a/tests/integration/org_count_test.go +++ b/tests/integration/org_count_test.go @@ -4,7 +4,6 @@ package integration import ( - "net/url" "strings" "testing" @@ -14,15 +13,17 @@ import ( "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" ) func TestOrgCounts(t *testing.T) { - onGiteaRun(t, testOrgCounts) + defer tests.PrepareTestEnv(t)() + testOrgCounts(t) } -func testOrgCounts(t *testing.T, u *url.URL) { +func testOrgCounts(t *testing.T) { orgOwner := "user2" orgName := "testOrg" orgCollaborator := "user4" @@ -119,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_team_invite_test.go b/tests/integration/org_team_invite_test.go index 4c1053702e..7444980ea8 100644 --- a/tests/integration/org_team_invite_test.go +++ b/tests/integration/org_team_invite_test.go @@ -58,7 +58,7 @@ func TestOrgTeamEmailInvite(t *testing.T) { session = loginUser(t, user.Name) // join the team - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) + inviteURL := "/org/invite/" + invites[0].Token csrf = GetUserCSRFToken(t, session) req = NewRequestWithValues(t, "POST", inviteURL, map[string]string{ "_csrf": csrf, @@ -108,8 +108,8 @@ func TestOrgTeamEmailInviteRedirectsExistingUser(t *testing.T) { assert.Len(t, invites, 1) // accept the invite - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) - req = NewRequest(t, "GET", fmt.Sprintf("/user/login?redirect_to=%s", url.QueryEscape(inviteURL))) + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/login?redirect_to="+url.QueryEscape(inviteURL)) resp = MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) @@ -179,8 +179,8 @@ func TestOrgTeamEmailInviteRedirectsNewUser(t *testing.T) { assert.Len(t, invites, 1) // accept the invite - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) - req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) resp = MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) @@ -260,8 +260,8 @@ func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) { // new user: accept the invite session = emptyTestSession(t) - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) - req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) session.MakeRequest(t, req, http.StatusOK) req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ "user_name": "doesnotexist", @@ -275,7 +275,7 @@ func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) { assert.NoError(t, err) activationCode := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount}, user) - activateURL := fmt.Sprintf("/user/activate?code=%s", activationCode) + activateURL := "/user/activate?code=" + activationCode req = NewRequestWithValues(t, "POST", activateURL, map[string]string{ "password": "examplePassword!1", }) @@ -337,8 +337,8 @@ func TestOrgTeamEmailInviteRedirectsExistingUserWithLogin(t *testing.T) { session = loginUser(t, "user5") // accept the invite (note: this uses the sign_up url) - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) - req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) resp = session.MakeRequest(t, req, http.StatusSeeOther) assert.Equal(t, inviteURL, test.RedirectURL(resp)) diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index ef4ef2bb9b..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,8 +45,8 @@ 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++ { - assert.EqualValues(t, repos[i], strings.TrimSpace(sel.Eq(i).Text())) + for i := range repos { + assert.Equal(t, repos[i], strings.TrimSpace(sel.Eq(i).Text())) } } }) @@ -151,7 +156,7 @@ func TestOrgRestrictedUser(t *testing.T) { // assert restrictedUser cannot see the org or the public repo restrictedSession := loginUser(t, restrictedUser) - req := NewRequest(t, "GET", fmt.Sprintf("/%s", orgName)) + req := NewRequest(t, "GET", "/"+orgName) restrictedSession.MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName)) @@ -177,9 +182,9 @@ func TestOrgRestrictedUser(t *testing.T) { resp := adminSession.MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam_codereader", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) // teamID := apiTeam.ID // Now we need to add the restricted user to the team @@ -188,7 +193,7 @@ func TestOrgRestrictedUser(t *testing.T) { _ = adminSession.MakeRequest(t, req, http.StatusNoContent) // Now we need to check if the restrictedUser can access the repo - req = NewRequest(t, "GET", fmt.Sprintf("/%s", orgName)) + req = NewRequest(t, "GET", "/"+orgName) restrictedSession.MakeRequest(t, req, http.StatusOK) req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName)) @@ -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/org_worktime_test.go b/tests/integration/org_worktime_test.go new file mode 100644 index 0000000000..fb5216be8d --- /dev/null +++ b/tests/integration/org_worktime_test.go @@ -0,0 +1,293 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration_test + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestTimesByRepos tests TimesByRepos functionality +func testTimesByRepos(t *testing.T) { + kases := []struct { + name string + unixfrom int64 + unixto int64 + orgname int64 + expected []organization.WorktimeSumByRepos + }{ + { + name: "Full sum for org 1", + unixfrom: 0, + unixto: 9223372036854775807, + orgname: 1, + expected: []organization.WorktimeSumByRepos(nil), + }, + { + name: "Full sum for org 2", + unixfrom: 0, + unixto: 9223372036854775807, + orgname: 2, + expected: []organization.WorktimeSumByRepos{ + { + RepoName: "repo1", + SumTime: 4083, + }, + { + RepoName: "repo2", + SumTime: 75, + }, + }, + }, + { + name: "Simple time bound", + unixfrom: 946684801, + unixto: 946684802, + orgname: 2, + expected: []organization.WorktimeSumByRepos{ + { + RepoName: "repo1", + SumTime: 3662, + }, + }, + }, + { + name: "Both times inclusive", + unixfrom: 946684801, + unixto: 946684801, + orgname: 2, + expected: []organization.WorktimeSumByRepos{ + { + RepoName: "repo1", + SumTime: 3661, + }, + }, + }, + { + name: "Should ignore deleted", + unixfrom: 947688814, + unixto: 947688815, + orgname: 2, + expected: []organization.WorktimeSumByRepos{ + { + RepoName: "repo2", + SumTime: 71, + }, + }, + }, + } + + // Run test kases + for _, kase := range kases { + t.Run(kase.name, func(t *testing.T) { + org, err := organization.GetOrgByID(db.DefaultContext, kase.orgname) + assert.NoError(t, err) + results, err := organization.GetWorktimeByRepos(org, kase.unixfrom, kase.unixto) + assert.NoError(t, err) + assert.Equal(t, kase.expected, results) + }) + } +} + +// TestTimesByMilestones tests TimesByMilestones functionality +func testTimesByMilestones(t *testing.T) { + kases := []struct { + name string + unixfrom int64 + unixto int64 + orgname int64 + expected []organization.WorktimeSumByMilestones + }{ + { + name: "Full sum for org 1", + unixfrom: 0, + unixto: 9223372036854775807, + orgname: 1, + expected: []organization.WorktimeSumByMilestones(nil), + }, + { + name: "Full sum for org 2", + unixfrom: 0, + unixto: 9223372036854775807, + orgname: 2, + expected: []organization.WorktimeSumByMilestones{ + { + RepoName: "repo1", + MilestoneName: "", + MilestoneID: 0, + SumTime: 401, + HideRepoName: false, + }, + { + RepoName: "repo1", + MilestoneName: "milestone1", + MilestoneID: 1, + SumTime: 3682, + HideRepoName: true, + }, + { + RepoName: "repo2", + MilestoneName: "", + MilestoneID: 0, + SumTime: 75, + HideRepoName: false, + }, + }, + }, + { + name: "Simple time bound", + unixfrom: 946684801, + unixto: 946684802, + orgname: 2, + expected: []organization.WorktimeSumByMilestones{ + { + RepoName: "repo1", + MilestoneName: "milestone1", + MilestoneID: 1, + SumTime: 3662, + HideRepoName: false, + }, + }, + }, + { + name: "Both times inclusive", + unixfrom: 946684801, + unixto: 946684801, + orgname: 2, + expected: []organization.WorktimeSumByMilestones{ + { + RepoName: "repo1", + MilestoneName: "milestone1", + MilestoneID: 1, + SumTime: 3661, + HideRepoName: false, + }, + }, + }, + { + name: "Should ignore deleted", + unixfrom: 947688814, + unixto: 947688815, + orgname: 2, + expected: []organization.WorktimeSumByMilestones{ + { + RepoName: "repo2", + MilestoneName: "", + MilestoneID: 0, + SumTime: 71, + HideRepoName: false, + }, + }, + }, + } + + // Run test kases + for _, kase := range kases { + t.Run(kase.name, func(t *testing.T) { + org, err := organization.GetOrgByID(db.DefaultContext, kase.orgname) + require.NoError(t, err) + results, err := organization.GetWorktimeByMilestones(org, kase.unixfrom, kase.unixto) + if assert.NoError(t, err) { + assert.Equal(t, kase.expected, results) + } + }) + } +} + +// TestTimesByMembers tests TimesByMembers functionality +func testTimesByMembers(t *testing.T) { + kases := []struct { + name string + unixfrom int64 + unixto int64 + orgname int64 + expected []organization.WorktimeSumByMembers + }{ + { + name: "Full sum for org 1", + unixfrom: 0, + unixto: 9223372036854775807, + orgname: 1, + expected: []organization.WorktimeSumByMembers(nil), + }, + { + // Test case: Sum of times forever in org no. 2 + name: "Full sum for org 2", + unixfrom: 0, + unixto: 9223372036854775807, + orgname: 2, + expected: []organization.WorktimeSumByMembers{ + { + UserName: "user2", + SumTime: 3666, + }, + { + UserName: "user1", + SumTime: 491, + }, + }, + }, + { + name: "Simple time bound", + unixfrom: 946684801, + unixto: 946684802, + orgname: 2, + expected: []organization.WorktimeSumByMembers{ + { + UserName: "user2", + SumTime: 3662, + }, + }, + }, + { + name: "Both times inclusive", + unixfrom: 946684801, + unixto: 946684801, + orgname: 2, + expected: []organization.WorktimeSumByMembers{ + { + UserName: "user2", + SumTime: 3661, + }, + }, + }, + { + name: "Should ignore deleted", + unixfrom: 947688814, + unixto: 947688815, + orgname: 2, + expected: []organization.WorktimeSumByMembers{ + { + UserName: "user1", + SumTime: 71, + }, + }, + }, + } + + // Run test kases + for _, kase := range kases { + t.Run(kase.name, func(t *testing.T) { + org, err := organization.GetOrgByID(db.DefaultContext, kase.orgname) + assert.NoError(t, err) + results, err := organization.GetWorktimeByMembers(org, kase.unixfrom, kase.unixto) + assert.NoError(t, err) + assert.Equal(t, kase.expected, results) + }) + } +} + +func TestOrgWorktime(t *testing.T) { + // we need to run these tests in integration test because there are complex SQL queries + assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("ByRepos", testTimesByRepos) + t.Run("ByMilestones", testTimesByMilestones) + t.Run("ByMembers", testTimesByMembers) +} diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index cdff9aa2fd..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, @@ -78,10 +78,10 @@ func TestMoveRepoProjectColumns(t *testing.T) { columnsAfter, err := project1.GetColumns(db.DefaultContext) assert.NoError(t, err) - assert.Len(t, columns, 3) - assert.EqualValues(t, columns[1].ID, columnsAfter[0].ID) - assert.EqualValues(t, columns[2].ID, columnsAfter[1].ID) - assert.EqualValues(t, columns[0].ID, columnsAfter[2].ID) + assert.Len(t, columnsAfter, 3) + assert.Equal(t, columns[1].ID, columnsAfter[0].ID) + assert.Equal(t, columns[2].ID, columnsAfter[1].ID) + assert.Equal(t, columns[0].ID, columnsAfter[2].ID) assert.NoError(t, project_model.DeleteProjectByID(db.DefaultContext, project1.ID)) } diff --git a/tests/integration/pull_commit_test.go b/tests/integration/pull_commit_test.go index 8d98349fd3..9f3b1a9ef5 100644 --- a/tests/integration/pull_commit_test.go +++ b/tests/integration/pull_commit_test.go @@ -5,30 +5,37 @@ package integration import ( "net/http" - "net/url" "testing" pull_service "code.gitea.io/gitea/services/pull" + "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestListPullCommits(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - session := loginUser(t, "user5") - req := NewRequest(t, "GET", "/user2/repo1/pulls/3/commits/list") - resp := session.MakeRequest(t, req, http.StatusOK) - - var pullCommitList struct { - Commits []pull_service.CommitInfo `json:"commits"` - LastReviewCommitSha string `json:"last_review_commit_sha"` - } - DecodeJSON(t, resp, &pullCommitList) - - if assert.Len(t, pullCommitList.Commits, 2) { - assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", pullCommitList.Commits[0].ID) - assert.Equal(t, "5c050d3b6d2db231ab1f64e324f1b6b9a0b181c2", pullCommitList.Commits[1].ID) - } - assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", pullCommitList.LastReviewCommitSha) + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user5") + req := NewRequest(t, "GET", "/user2/repo1/pulls/3/commits/list") + resp := session.MakeRequest(t, req, http.StatusOK) + + var pullCommitList struct { + Commits []pull_service.CommitInfo `json:"commits"` + LastReviewCommitSha string `json:"last_review_commit_sha"` + } + DecodeJSON(t, resp, &pullCommitList) + + require.Len(t, pullCommitList.Commits, 2) + assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", pullCommitList.Commits[0].ID) + assert.Equal(t, "5c050d3b6d2db231ab1f64e324f1b6b9a0b181c2", pullCommitList.Commits[1].ID) + assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", pullCommitList.LastReviewCommitSha) + + t.Run("CommitBlobExcerpt", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req = NewRequest(t, "GET", "/user2/repo1/blob_excerpt/985f0301dba5e7b34be866819cd15ad3d8f508ee?last_left=0&last_right=0&left=2&right=2&left_hunk_size=2&right_hunk_size=2&path=README.md&style=split&direction=up") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), `<td class="lines-code lines-code-new"><code class="code-inner"># repo1</code>`) }) } diff --git a/tests/integration/pull_compare_test.go b/tests/integration/pull_compare_test.go index 106774aa54..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" @@ -24,23 +23,38 @@ import ( func TestPullCompare(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user2/repo1/pulls") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - link, exists := htmlDoc.doc.Find(".new-pr-button").Attr("href") - assert.True(t, exists, "The template has changed") + t.Run("PullsNewRedirect", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/pulls/new/foo") + resp := MakeRequest(t, req, http.StatusSeeOther) + redirect := test.RedirectURL(resp) + assert.Equal(t, "/user2/repo1/compare/master...foo?expand=1", redirect) - req = NewRequest(t, "GET", link) - resp = session.MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, http.StatusOK, resp.Code) + req = NewRequest(t, "GET", "/user13/repo11/pulls/new/foo") + resp = MakeRequest(t, req, http.StatusSeeOther) + redirect = test.RedirectURL(resp) + assert.Equal(t, "/user12/repo10/compare/master...user13:foo?expand=1", redirect) + }) + + t.Run("ButtonsExist", func(t *testing.T) { + session := loginUser(t, "user2") + + // test the "New PR" button + req := NewRequest(t, "GET", "/user2/repo1/pulls") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find(".new-pr-button").Attr("href") + assert.True(t, exists, "The template has changed") + req = NewRequest(t, "GET", link) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, http.StatusOK, resp.Code) - // test the edit button in the PR diff view - req = NewRequest(t, "GET", "/user2/repo1/pulls/3/files") - resp = session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - editButtonCount := doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length() - assert.Positive(t, editButtonCount, "Expected to find a button to edit a file in the PR diff view but there were none") + // test the edit button in the PR diff view + req = NewRequest(t, "GET", "/user2/repo1/pulls/3/files") + resp = session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + editButtonCount := doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length() + assert.Positive(t, editButtonCount, "Expected to find a button to edit a file in the PR diff view but there were none") + }) onGiteaRun(t, func(t *testing.T, u *url.URL) { defer tests.PrepareTestEnv(t)() @@ -54,24 +68,23 @@ func TestPullCompare(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) issueIndex := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueIndex{GroupID: repo1.ID}, unittest.OrderBy("group_id ASC")) prFilesURL := fmt.Sprintf("/user2/repo1/pulls/%d/files", issueIndex.MaxIndex) - req = NewRequest(t, "GET", prFilesURL) - resp = session.MakeRequest(t, req, http.StatusOK) + req := NewRequest(t, "GET", prFilesURL) + resp := session.MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) editButtonCount := doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length() 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) resp = session.MakeRequest(t, req, http.StatusOK) doc = NewHTMLParser(t, resp.Body) editButtonCount = doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length() - assert.EqualValues(t, 0, editButtonCount, "Expected not to find a button to edit a file in the PR diff view because head repository has been deleted") + assert.Equal(t, 0, editButtonCount, "Expected not to find a button to edit a file in the PR diff view because head repository has been deleted") }) } @@ -95,7 +108,7 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) { // user2 (admin of repo3) goes to the PR files page user2Session := loginUser(t, "user2") - resp = user2Session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("%s/files", prURL)), http.StatusOK) + resp = user2Session.MakeRequest(t, NewRequest(t, "GET", prURL+"/files"), http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) nodes := htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .tippy-target a") if assert.Equal(t, 1, nodes.Length()) { @@ -112,14 +125,14 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) { htmlDoc = NewHTMLParser(t, resp.Body) dataURL, exists := htmlDoc.doc.Find("#allow-edits-from-maintainers").Attr("data-url") assert.True(t, exists) - req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/set_allow_maintainer_edit", dataURL), map[string]string{ + req := NewRequestWithValues(t, "POST", dataURL+"/set_allow_maintainer_edit", map[string]string{ "_csrf": htmlDoc.GetCSRF(), "allow_maintainer_edit": "true", }) user4Session.MakeRequest(t, req, http.StatusOK) // user2 (admin of repo3) goes to the PR files page again - resp = user2Session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("%s/files", prURL)), http.StatusOK) + resp = user2Session.MakeRequest(t, NewRequest(t, "GET", prURL+"/files"), http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) nodes = htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .tippy-target a") if assert.Equal(t, 2, nodes.Length()) { @@ -146,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_create_test.go b/tests/integration/pull_create_test.go index 162ea532c8..179c84e673 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -265,7 +265,7 @@ func TestCreateAgitPullWithReadPermission(t *testing.T) { t.Run("add commit", doGitAddSomeCommits(dstPath, "master")) t.Run("do agit pull create", func(t *testing.T) { - err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + "test-topic").Run(&git.RunOpts{Dir: dstPath}) + err := git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+"test-topic").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) }) }) @@ -293,10 +293,10 @@ func TestCreatePullWhenBlocked(t *testing.T) { // sessionBase := loginUser(t, "user2") token := getUserToken(t, RepoOwner, auth_model.AccessTokenScopeWriteUser) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/blocks/%s", ForkOwner)). + req := NewRequest(t, "GET", "/api/v1/user/blocks/"+ForkOwner). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s", ForkOwner)). + req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+ForkOwner). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) @@ -308,7 +308,7 @@ func TestCreatePullWhenBlocked(t *testing.T) { // Teardown // Unblock user - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", ForkOwner)). + req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+ForkOwner). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) }) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 169df8618e..73b4c22070 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -5,7 +5,6 @@ package integration import ( "bytes" - "context" "fmt" "net/http" "net/http/httptest" @@ -27,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" @@ -67,7 +67,7 @@ func testPullMerge(t *testing.T, session *TestSession, user, repo, pullnum strin }{} DecodeJSON(t, resp, &respJSON) - assert.EqualValues(t, fmt.Sprintf("/%s/%s/pulls/%s", user, repo, pullnum), respJSON.Redirect) + assert.Equal(t, fmt.Sprintf("/%s/%s/pulls/%s", user, repo, pullnum), respJSON.Redirect) return resp } @@ -101,7 +101,7 @@ func TestPullMerge(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) @@ -123,7 +123,7 @@ func TestPullRebase(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebase, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) @@ -145,7 +145,7 @@ func TestPullRebaseMerge(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebaseMerge, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) @@ -168,7 +168,7 @@ func TestPullSquash(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleSquash, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) @@ -186,7 +186,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "feature/test", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) // Check PR branch deletion @@ -199,7 +199,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) { assert.NotEmpty(t, respJSON.Redirect, "Redirected URL is not found") elem = strings.Split(respJSON.Redirect, "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) // Check branch deletion result req := NewRequest(t, "GET", respJSON.Redirect) @@ -208,7 +208,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() - assert.EqualValues(t, "Branch \"user1/repo1:feature/test\" has been deleted.", resultMsg) + assert.Equal(t, "Branch \"user1/repo1:feature/test\" has been deleted.", resultMsg) }) } @@ -266,11 +266,11 @@ func TestCantMergeConflict(t *testing.T) { gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo1) assert.NoError(t, err) - err = pull_service.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT", false) + err = pull_service.Merge(t.Context(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT", false) assert.Error(t, err, "Merge should return an error due to conflict") assert.True(t, pull_service.IsErrMergeConflicts(err), "Merge error is not a conflict error") - err = pull_service.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT", false) + err = pull_service.Merge(t.Context(), pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT", false) assert.Error(t, err, "Merge should return an error due to conflict") assert.True(t, pull_service.IsErrRebaseConflicts(err), "Merge error is not a conflict error") gitRepo.Close() @@ -294,12 +294,12 @@ func TestCantMergeUnrelated(t *testing.T) { }) path := repo_model.RepoPath(user1.Name, repo1.Name) - err := git.NewCommand(git.DefaultContext, "read-tree", "--empty").Run(&git.RunOpts{Dir: path}) + err := git.NewCommand("read-tree", "--empty").Run(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) - stdin := bytes.NewBufferString("Unrelated File") + stdin := strings.NewReader("Unrelated File") var stdout strings.Builder - err = git.NewCommand(git.DefaultContext, "hash-object", "-w", "--stdin").Run(&git.RunOpts{ + err = git.NewCommand("hash-object", "-w", "--stdin").Run(git.DefaultContext, &git.RunOpts{ Dir: path, Stdin: stdin, Stdout: &stdout, @@ -308,10 +308,10 @@ func TestCantMergeUnrelated(t *testing.T) { assert.NoError(t, err) sha := strings.TrimSpace(stdout.String()) - _, _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments("100644", sha, "somewher-over-the-rainbow").RunStdString(&git.RunOpts{Dir: path}) + _, _, err = git.NewCommand("update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments("100644", sha, "somewher-over-the-rainbow").RunStdString(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) - treeSha, _, err := git.NewCommand(git.DefaultContext, "write-tree").RunStdString(&git.RunOpts{Dir: path}) + treeSha, _, err := git.NewCommand("write-tree").RunStdString(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) treeSha = strings.TrimSpace(treeSha) @@ -331,8 +331,8 @@ func TestCantMergeUnrelated(t *testing.T) { _, _ = messageBytes.WriteString("\n") stdout.Reset() - err = git.NewCommand(git.DefaultContext, "commit-tree").AddDynamicArguments(treeSha). - Run(&git.RunOpts{ + err = git.NewCommand("commit-tree").AddDynamicArguments(treeSha). + Run(git.DefaultContext, &git.RunOpts{ Env: env, Dir: path, Stdin: messageBytes, @@ -341,7 +341,7 @@ func TestCantMergeUnrelated(t *testing.T) { assert.NoError(t, err) commitSha := strings.TrimSpace(stdout.String()) - _, _, err = git.NewCommand(git.DefaultContext, "branch", "unrelated").AddDynamicArguments(commitSha).RunStdString(&git.RunOpts{Dir: path}) + _, _, err = git.NewCommand("branch", "unrelated").AddDynamicArguments(commitSha).RunStdString(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") @@ -365,7 +365,7 @@ func TestCantMergeUnrelated(t *testing.T) { BaseBranch: "base", }) - err = pull_service.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED", false) + err = pull_service.Merge(t.Context(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED", false) assert.Error(t, err, "Merge should return an error due to unrelated") assert.True(t, pull_service.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error") gitRepo.Close() @@ -405,7 +405,7 @@ func TestFastForwardOnlyMerge(t *testing.T) { gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) assert.NoError(t, err) - err = pull_service.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "FAST-FORWARD-ONLY", false) + err = pull_service.Merge(t.Context(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "FAST-FORWARD-ONLY", false) assert.NoError(t, err) @@ -447,7 +447,7 @@ func TestCantFastForwardOnlyMergeDiverging(t *testing.T) { gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) assert.NoError(t, err) - err = pull_service.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "DIVERGING", false) + err = pull_service.Merge(t.Context(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "DIVERGING", false) assert.Error(t, err, "Merge should return an error due to being for a diverging branch") assert.True(t, pull_service.IsErrMergeDivergingFastForwardOnly(err), "Merge error is not a diverging fast-forward-only error") @@ -545,11 +545,11 @@ func TestPullRetargetChildOnBranchDelete(t *testing.T) { respBasePR := testPullCreate(t, session, "user2", "repo1", true, "master", "base-pr", "Base Pull Request") elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/") - assert.EqualValues(t, "pulls", elemBasePR[3]) + assert.Equal(t, "pulls", elemBasePR[3]) respChildPR := testPullCreate(t, session, "user1", "repo1", false, "base-pr", "child-pr", "Child Pull Request") elemChildPR := strings.Split(test.RedirectURL(respChildPR), "/") - assert.EqualValues(t, "pulls", elemChildPR[3]) + assert.Equal(t, "pulls", elemChildPR[3]) testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true) @@ -565,8 +565,8 @@ func TestPullRetargetChildOnBranchDelete(t *testing.T) { targetBranch := htmlDoc.doc.Find("#branch_target>a").Text() prStatus := strings.TrimSpace(htmlDoc.doc.Find(".issue-title-meta>.issue-state-label").Text()) - assert.EqualValues(t, "master", targetBranch) - assert.EqualValues(t, "Open", prStatus) + assert.Equal(t, "master", targetBranch) + assert.Equal(t, "Open", prStatus) }) } @@ -579,11 +579,11 @@ func TestPullDontRetargetChildOnWrongRepo(t *testing.T) { respBasePR := testPullCreate(t, session, "user1", "repo1", false, "master", "base-pr", "Base Pull Request") elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/") - assert.EqualValues(t, "pulls", elemBasePR[3]) + assert.Equal(t, "pulls", elemBasePR[3]) respChildPR := testPullCreate(t, session, "user1", "repo1", true, "base-pr", "child-pr", "Child Pull Request") elemChildPR := strings.Split(test.RedirectURL(respChildPR), "/") - assert.EqualValues(t, "pulls", elemChildPR[3]) + assert.Equal(t, "pulls", elemChildPR[3]) defer test.MockVariableValue(&setting.Repository.PullRequest.RetargetChildrenOnMerge, false)() @@ -602,8 +602,8 @@ func TestPullDontRetargetChildOnWrongRepo(t *testing.T) { targetBranch := htmlDoc.doc.Find("#branch_target>span").Text() prStatus := strings.TrimSpace(htmlDoc.doc.Find(".issue-title-meta>.issue-state-label").Text()) - assert.EqualValues(t, "base-pr", targetBranch) - assert.EqualValues(t, "Closed", prStatus) + assert.Equal(t, "base-pr", targetBranch) + assert.Equal(t, "Closed", prStatus) }) } @@ -615,7 +615,7 @@ func TestPullRequestMergedWithNoPermissionDeleteBranch(t *testing.T) { respBasePR := testPullCreate(t, session, "user4", "repo1", false, "master", "base-pr", "Base Pull Request") elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/") - assert.EqualValues(t, "pulls", elemBasePR[3]) + assert.Equal(t, "pulls", elemBasePR[3]) // user2 has no permission to delete branch of repo user1/repo1 session2 := loginUser(t, "user2") @@ -636,7 +636,7 @@ func TestPullMergeIndexerNotifier(t *testing.T) { testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") createPullResp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "Indexer notifier test pull") - assert.NoError(t, queue.GetManager().FlushAll(context.Background(), 0)) + assert.NoError(t, queue.GetManager().FlushAll(t.Context(), 0)) time.Sleep(time.Second) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ @@ -666,7 +666,7 @@ func TestPullMergeIndexerNotifier(t *testing.T) { // merge the pull request elem := strings.Split(test.RedirectURL(createPullResp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) // check if the issue is closed @@ -675,7 +675,7 @@ func TestPullMergeIndexerNotifier(t *testing.T) { }) assert.True(t, issue.IsClosed) - assert.NoError(t, queue.GetManager().FlushAll(context.Background(), 0)) + assert.NoError(t, queue.GetManager().FlushAll(t.Context(), 0)) time.Sleep(time.Second) // search issues again @@ -695,12 +695,12 @@ func testResetRepo(t *testing.T, repoPath, branch, commitID string) { assert.NoError(t, err) f.Close() - repo, err := git.OpenRepository(context.Background(), repoPath) + repo, err := git.OpenRepository(t.Context(), repoPath) assert.NoError(t, err) defer repo.Close() id, err := repo.GetBranchCommitID(branch) assert.NoError(t, err) - assert.EqualValues(t, commitID, id) + assert.Equal(t, commitID, id) } func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { @@ -769,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", }) @@ -849,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", }) @@ -915,13 +915,13 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing. stderrBuf := &bytes.Buffer{} - err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o"). + err = git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o"). AddDynamicArguments(`topic=test/head2`). AddArguments("-o"). AddDynamicArguments(`title="create a test pull request with agit"`). AddArguments("-o"). AddDynamicArguments(`description="This PR is a test pull request which created with agit"`). - Run(&git.RunOpts{Dir: dstPath, Stderr: stderrBuf}) + Run(git.DefaultContext, &git.RunOpts{Dir: dstPath, Stderr: stderrBuf}) assert.NoError(t, err) assert.Contains(t, stderrBuf.String(), setting.AppURL+"user2/repo1/pulls/6") @@ -978,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) @@ -998,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_review_test.go b/tests/integration/pull_review_test.go index 68de421413..1121751c88 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -57,7 +57,7 @@ func TestPullView_CodeOwner(t *testing.T) { AutoInit: true, ObjectFormatName: git.Sha1ObjectFormat.Name(), DefaultBranch: "master", - }) + }, true) assert.NoError(t, err) // add CODEOWNERS to default branch @@ -67,7 +67,7 @@ func TestPullView_CodeOwner(t *testing.T) { { Operation: "create", TreePath: "CODEOWNERS", - ContentReader: strings.NewReader("README.md @user5\n"), + ContentReader: strings.NewReader("README.md @user5\nuser8-file.md @user8\n"), }, }, }) @@ -75,7 +75,7 @@ func TestPullView_CodeOwner(t *testing.T) { t.Run("First Pull Request", func(t *testing.T) { // create a new branch to prepare for pull request - _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ + _, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ NewBranch: "codeowner-basebranch", Files: []*files_service.ChangeRepoFile{ { @@ -95,17 +95,35 @@ func TestPullView_CodeOwner(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 5}) assert.NoError(t, pr.LoadIssue(db.DefaultContext)) - err := issue_service.ChangeTitle(db.DefaultContext, pr.Issue, user2, "[WIP] Test Pull Request") + // update the file on the pr branch + _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ + OldBranch: "codeowner-basebranch", + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: "user8-file.md", + ContentReader: strings.NewReader("# This is a new project2\n"), + }, + }, + }) + assert.NoError(t, err) + + reviewNotifiers, err := issue_service.PullRequestCodeOwnersReview(db.DefaultContext, pr) + assert.NoError(t, err) + assert.Len(t, reviewNotifiers, 1) + assert.EqualValues(t, 8, reviewNotifiers[0].Reviewer.ID) + + err = issue_service.ChangeTitle(db.DefaultContext, pr.Issue, user2, "[WIP] Test Pull Request") assert.NoError(t, err) prUpdated1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) assert.NoError(t, prUpdated1.LoadIssue(db.DefaultContext)) - assert.EqualValues(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title) + assert.Equal(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title) err = issue_service.ChangeTitle(db.DefaultContext, prUpdated1.Issue, user2, "Test Pull Request2") assert.NoError(t, err) prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) assert.NoError(t, prUpdated2.LoadIssue(db.DefaultContext)) - assert.EqualValues(t, "Test Pull Request2", prUpdated2.Issue.Title) + assert.Equal(t, "Test Pull Request2", prUpdated2.Issue.Title) }) // change the default branch CODEOWNERS file to change README.md's codeowner @@ -193,7 +211,7 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, user1Session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) // Grab the CSRF token. @@ -213,7 +231,7 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Editied...again)\n") resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testIssueClose(t, user1Session, elem[1], elem[2], elem[4]) // Grab the CSRF token. diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index ac9036ca96..49326a594a 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -13,9 +13,14 @@ import ( auth_model "code.gitea.io/gitea/models/auth" git_model "code.gitea.io/gitea/models/git" + "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" + "code.gitea.io/gitea/services/pull" "github.com/stretchr/testify/assert" ) @@ -51,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) @@ -86,7 +91,7 @@ func TestPullCreate_CommitStatus(t *testing.T) { commitURL, exists = doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") assert.True(t, exists) assert.NotEmpty(t, commitURL) - assert.EqualValues(t, commitID, path.Base(commitURL)) + assert.Equal(t, commitID, path.Base(commitURL)) cls, ok := doc.doc.Find("#commits-table tbody tr td.message .commit-status").Last().Attr("class") assert.True(t, ok) @@ -95,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.EqualValues(t, api.CommitStatusWarning, css.State) + assert.Equal(t, commitstatus.CommitStatusSuccess, css.State) }) } @@ -124,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, @@ -165,3 +170,74 @@ func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) { assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.") }) } + +func TestPullStatusDelayCheck(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + defer test.MockVariableValue(&setting.Repository.PullRequest.DelayCheckForInactiveDays, 1)() + defer test.MockVariableValue(&pull.AddPullRequestToCheckQueue)() + + session := loginUser(t, "user2") + + run := func(t *testing.T, fn func(*testing.T)) (issue3 *issues.Issue, checkedPrID int64) { + pull.AddPullRequestToCheckQueue = func(prID int64) { + checkedPrID = prID + } + fn(t) + issue3 = unittest.AssertExistsAndLoadBean(t, &issues.Issue{RepoID: 1, Index: 3}) + _ = issue3.LoadPullRequest(t.Context()) + return issue3, checkedPrID + } + + assertReloadingInterval := func(t *testing.T, interval string) { + req := NewRequest(t, "GET", "/user2/repo1/pulls/3") + resp := session.MakeRequest(t, req, http.StatusOK) + attr := "data-pull-merge-box-reloading-interval" + if interval == "" { + assert.NotContains(t, resp.Body.String(), attr) + } else { + assert.Contains(t, resp.Body.String(), fmt.Sprintf(`%s="%v"`, attr, interval)) + } + } + + // PR issue3 is merageable at the beginning + issue3, checkedPrID := run(t, func(t *testing.T) {}) + assert.Equal(t, issues.PullRequestStatusMergeable, issue3.PullRequest.Status) + assert.Zero(t, checkedPrID) + assertReloadingInterval(t, "") // the PR is mergeable, so no need to reload the merge box + + // setting.IsProd = false // it would cause data-race because the queue handlers might be running and reading its value + // assertReloadingInterval(t, "1") // make sure dev mode always do merge box reloading, to make sure the UI logic won't break + // setting.IsProd = true + + // when base branch changes, PR status should be updated, but it is inactive for long time, so no real check + issue3, checkedPrID = run(t, func(t *testing.T) { + testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 1") + }) + assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status) + assert.Zero(t, checkedPrID) + assertReloadingInterval(t, "2000") // the PR status is "checking", so try to reload the merge box + + // view a PR with status=checking, it starts the real check + issue3, checkedPrID = run(t, func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/pulls/3") + session.MakeRequest(t, req, http.StatusOK) + }) + assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status) + assert.Equal(t, issue3.PullRequest.ID, checkedPrID) + + // when base branch changes, still so no real check + issue3, checkedPrID = run(t, func(t *testing.T) { + testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 2") + }) + assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status) + assert.Zero(t, checkedPrID) + + // then allow to check PRs without delay, when base branch changes, the PRs will be checked + setting.Repository.PullRequest.DelayCheckForInactiveDays = -1 + issue3, checkedPrID = run(t, func(t *testing.T) { + testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 3") + }) + assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status) + assert.Equal(t, issue3.PullRequest.ID, checkedPrID) + }) +} diff --git a/tests/integration/pull_update_test.go b/tests/integration/pull_update_test.go index dfe47c1053..d28537112d 100644 --- a/tests/integration/pull_update_test.go +++ b/tests/integration/pull_update_test.go @@ -33,8 +33,8 @@ func TestAPIPullUpdate(t *testing.T) { // Test GetDiverging diffCount, err := pull_service.GetDiverging(git.DefaultContext, pr) assert.NoError(t, err) - assert.EqualValues(t, 1, diffCount.Behind) - assert.EqualValues(t, 1, diffCount.Ahead) + assert.Equal(t, 1, diffCount.Behind) + assert.Equal(t, 1, diffCount.Ahead) assert.NoError(t, pr.LoadBaseRepo(db.DefaultContext)) assert.NoError(t, pr.LoadIssue(db.DefaultContext)) @@ -47,8 +47,8 @@ func TestAPIPullUpdate(t *testing.T) { // Test GetDiverging after update diffCount, err = pull_service.GetDiverging(git.DefaultContext, pr) assert.NoError(t, err) - assert.EqualValues(t, 0, diffCount.Behind) - assert.EqualValues(t, 2, diffCount.Ahead) + assert.Equal(t, 0, diffCount.Behind) + assert.Equal(t, 2, diffCount.Ahead) }) } @@ -62,8 +62,8 @@ func TestAPIPullUpdateByRebase(t *testing.T) { // Test GetDiverging diffCount, err := pull_service.GetDiverging(git.DefaultContext, pr) assert.NoError(t, err) - assert.EqualValues(t, 1, diffCount.Behind) - assert.EqualValues(t, 1, diffCount.Ahead) + assert.Equal(t, 1, diffCount.Behind) + assert.Equal(t, 1, diffCount.Ahead) assert.NoError(t, pr.LoadBaseRepo(db.DefaultContext)) assert.NoError(t, pr.LoadIssue(db.DefaultContext)) @@ -76,8 +76,8 @@ func TestAPIPullUpdateByRebase(t *testing.T) { // Test GetDiverging after update diffCount, err = pull_service.GetDiverging(git.DefaultContext, pr) assert.NoError(t, err) - assert.EqualValues(t, 0, diffCount.Behind) - assert.EqualValues(t, 1, diffCount.Ahead) + assert.Equal(t, 0, diffCount.Behind) + assert.Equal(t, 1, diffCount.Ahead) }) } @@ -115,12 +115,12 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_mod OldBranch: "master", NewBranch: "master", Author: &files_service.IdentityOptions{ - Name: actor.Name, - Email: actor.Email, + GitUserName: actor.Name, + GitUserEmail: actor.Email, }, Committer: &files_service.IdentityOptions{ - Name: actor.Name, - Email: actor.Email, + GitUserName: actor.Name, + GitUserEmail: actor.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), @@ -142,12 +142,12 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_mod OldBranch: "master", NewBranch: "newBranch", Author: &files_service.IdentityOptions{ - Name: actor.Name, - Email: actor.Email, + GitUserName: actor.Name, + GitUserEmail: actor.Email, }, Committer: &files_service.IdentityOptions{ - Name: actor.Name, - Email: actor.Email, + GitUserName: actor.Name, + GitUserEmail: actor.Email, }, Dates: &files_service.CommitDateOptions{ Author: time.Now(), diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index 1a7f9e38b5..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" @@ -54,12 +53,12 @@ func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, ver htmlDoc := NewHTMLParser(t, resp.Body) labelText := htmlDoc.doc.Find("#release-list > li .detail .label").First().Text() - assert.EqualValues(t, label, labelText) + assert.Equal(t, label, labelText) titleText := htmlDoc.doc.Find("#release-list > li .detail h4 a").First().Text() - assert.EqualValues(t, version, titleText) + assert.Equal(t, version, titleText) releaseList := htmlDoc.doc.Find("#release-list > li") - assert.EqualValues(t, count, releaseList.Length()) + assert.Equal(t, count, releaseList.Length()) } func TestViewReleases(t *testing.T) { @@ -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) } @@ -157,14 +153,14 @@ func TestViewReleaseListNoLogin(t *testing.T) { commitsToMain = append(commitsToMain, s.Find(".ahead > a").Text()) }) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "/user2/repo-release/releases/tag/empty-target-branch", "/user2/repo-release/releases/tag/non-existing-target-branch", "/user2/repo-release/releases/tag/v2.0", "/user2/repo-release/releases/tag/v1.1", "/user2/repo-release/releases/tag/v1.0", }, links) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "1 commits", // like v1.1 "1 commits", // like v1.1 "0 commits", @@ -182,8 +178,8 @@ func TestViewSingleRelease(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) // check the "number of commits to main since this release" releaseList := htmlDoc.doc.Find("#release-list .ahead > a") - assert.EqualValues(t, 1, releaseList.Length()) - assert.EqualValues(t, "3 commits", releaseList.First().Text()) + assert.Equal(t, 1, releaseList.Length()) + assert.Equal(t, "3 commits", releaseList.First().Text()) }) t.Run("Login", func(t *testing.T) { session := loginUser(t, "user1") @@ -218,7 +214,7 @@ func TestViewReleaseListLogin(t *testing.T) { links = append(links, link) }) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "/user2/repo1/releases/tag/draft-release", "/user2/repo1/releases/tag/v1.0", "/user2/repo1/releases/tag/v1.1", @@ -245,7 +241,7 @@ func TestViewTagsList(t *testing.T) { tagNames = append(tagNames, s.Text()) }) - assert.EqualValues(t, []string{"v1.0", "delete-tag", "v1.1"}, tagNames) + assert.Equal(t, []string{"v1.0", "delete-tag", "v1.1"}, tagNames) } func TestDownloadReleaseAttachment(t *testing.T) { diff --git a/tests/integration/rename_branch_test.go b/tests/integration/rename_branch_test.go index 576264ba95..492fdf781b 100644 --- a/tests/integration/rename_branch_test.go +++ b/tests/integration/rename_branch_test.go @@ -11,7 +11,6 @@ import ( git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" - gitea_context "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -82,9 +81,8 @@ func testRenameBranch(t *testing.T, u *url.URL) { "to": "branch1", }) session.MakeRequest(t, req, http.StatusSeeOther) - flashCookie := session.GetCookie(gitea_context.CookieNameFlash) - assert.NotNil(t, flashCookie) - assert.Contains(t, flashCookie.Value, "error") + flashMsg := session.GetCookieFlashMessage() + assert.NotEmpty(t, flashMsg.ErrorMsg) branch2 = unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch2"}) assert.Equal(t, "branch2", branch2.Name) @@ -110,9 +108,8 @@ func testRenameBranch(t *testing.T, u *url.URL) { }) session.MakeRequest(t, req, http.StatusSeeOther) - flashCookie = session.GetCookie(gitea_context.CookieNameFlash) - assert.NotNil(t, flashCookie) - assert.Contains(t, flashCookie.Value, "success") + flashMsg = session.GetCookieFlashMessage() + assert.NotEmpty(t, flashMsg.SuccessMsg) unittest.AssertNotExistsBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch2"}) branch1 = unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch1"}) diff --git a/tests/integration/repo_activity_test.go b/tests/integration/repo_activity_test.go index b04560379d..d5025decba 100644 --- a/tests/integration/repo_activity_test.go +++ b/tests/integration/repo_activity_test.go @@ -9,7 +9,9 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" @@ -24,7 +26,7 @@ func TestRepoActivity(t *testing.T) { testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n") @@ -61,5 +63,14 @@ func TestRepoActivity(t *testing.T) { // Should be 3 new issues list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc") assert.Len(t, list.Nodes, 3) + + // Non-existing default branch + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) + repo1.DefaultBranch = "no-such-branch" + _, _ = db.GetEngine(t.Context()).Cols("default_branch").Update(repo1) + req = NewRequest(t, "GET", "/user2/repo1/activity") + req.Header.Add("Accept", "text/html") + resp = session.MakeRequest(t, req, http.StatusNotFound) + assert.Contains(t, resp.Body.String(), `Default branch "no-such-branch" does not exist.`) }) } diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index f9cf13112a..50ceb65330 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -138,7 +138,7 @@ func TestCreateBranchInvalidCSRF(t *testing.T) { } func prepareRecentlyPushedBranchTest(t *testing.T, headSession *TestSession, baseRepo, headRepo *repo_model.Repository) { - refSubURL := fmt.Sprintf("branch/%s", headRepo.DefaultBranch) + refSubURL := "branch/" + headRepo.DefaultBranch baseRepoPath := baseRepo.OwnerName + "/" + baseRepo.Name headRepoPath := headRepo.OwnerName + "/" + headRepo.Name // Case 1: Normal branch changeset to display pushed message @@ -168,7 +168,7 @@ func prepareRecentlyPushedBranchTest(t *testing.T, headSession *TestSession, bas } func prepareRecentlyPushedBranchSpecialTest(t *testing.T, session *TestSession, baseRepo, headRepo *repo_model.Repository) { - refSubURL := fmt.Sprintf("branch/%s", headRepo.DefaultBranch) + refSubURL := "branch/" + headRepo.DefaultBranch baseRepoPath := baseRepo.OwnerName + "/" + baseRepo.Name headRepoPath := headRepo.OwnerName + "/" + headRepo.Name // create branch with no new commit @@ -196,7 +196,7 @@ func testCreatePullToDefaultBranch(t *testing.T, session *TestSession, baseRepo, } func prepareRepoPR(t *testing.T, baseSession, headSession *TestSession, baseRepo, headRepo *repo_model.Repository) { - refSubURL := fmt.Sprintf("branch/%s", headRepo.DefaultBranch) + refSubURL := "branch/" + headRepo.DefaultBranch testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, refSubURL, "new-commit", http.StatusSeeOther) // create opening PR diff --git a/tests/integration/repo_commits_search_test.go b/tests/integration/repo_commits_search_test.go index 74ac25c0f5..9b05e36399 100644 --- a/tests/integration/repo_commits_search_test.go +++ b/tests/integration/repo_commits_search_test.go @@ -23,7 +23,7 @@ func testRepoCommitsSearch(t *testing.T, query, commit string) { doc := NewHTMLParser(t, resp.Body) sel := doc.doc.Find("#commits-table tbody tr td.sha a") - assert.EqualValues(t, commit, strings.TrimSpace(sel.Text())) + assert.Equal(t, commit, strings.TrimSpace(sel.Text())) } func TestRepoCommitsSearch(t *testing.T) { diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index fc066e06d3..0097a7f62e 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -12,11 +12,13 @@ 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" "code.gitea.io/gitea/tests" + "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" ) @@ -35,6 +37,28 @@ func TestRepoCommits(t *testing.T) { assert.NotEmpty(t, commitURL) } +func Test_ReposGitCommitListNotMaster(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo16/commits/branch/master") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + var commits []string + doc.doc.Find("#commits-table .commit-id-short").Each(func(i int, s *goquery.Selection) { + commitURL, _ := s.Attr("href") + commits = append(commits, path.Base(commitURL)) + }) + assert.Equal(t, []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, commits) + + var userHrefs []string + doc.doc.Find("#commits-table .author-wrapper").Each(func(i int, s *goquery.Selection) { + userHref, _ := s.Attr("href") + userHrefs = append(userHrefs, userHref) + }) + assert.Equal(t, []string{"/user2", "/user21", "/user2"}, userHrefs) +} + func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { defer tests.PrepareTestEnv(t)() @@ -53,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", @@ -97,10 +121,10 @@ 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.Equal(t, "", statuses[0].Description) + assert.Empty(t, statuses[0].Description) assert.Equal(t, "testci", statuses[0].Context) assert.Len(t, status.Statuses, 1) @@ -145,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", @@ -182,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", @@ -199,7 +223,7 @@ func TestRepoCommitsStatusMultiple(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) doc = NewHTMLParser(t, resp.Body) - // Check that the data-tippy="commit-statuses" (for trigger) and commit-status (svg) are present - sel := doc.doc.Find("#commits-table .message [data-tippy=\"commit-statuses\"] .commit-status") + // Check that the data-global-init="initCommitStatuses" (for trigger) and commit-status (svg) are present + sel := doc.doc.Find(`#commits-table .message [data-global-init="initCommitStatuses"] .commit-status`) assert.Equal(t, 1, sel.Length()) } diff --git a/tests/integration/repo_fork_test.go b/tests/integration/repo_fork_test.go index 267fd0d56e..95325eefeb 100644 --- a/tests/integration/repo_fork_test.go +++ b/tests/integration/repo_fork_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strconv" "testing" "code.gitea.io/gitea/models/db" @@ -14,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" @@ -46,11 +48,12 @@ func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkO assert.True(t, exists, "Fork owner '%s' is not present in select box", forkOwnerName) req = NewRequestWithValues(t, "POST", link, map[string]string{ "_csrf": htmlDoc.GetCSRF(), - "uid": fmt.Sprintf("%d", forkOwner.ID), + "uid": strconv.FormatInt(forkOwner.ID, 10), "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) @@ -81,14 +84,14 @@ 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"}) // fork to a limited org limitedOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22}) - assert.EqualValues(t, structs.VisibleTypeLimited, limitedOrg.Visibility) + assert.Equal(t, structs.VisibleTypeLimited, limitedOrg.Visibility) ownerTeam1, err := org_model.OrgFromUser(limitedOrg).GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam1, user1)) @@ -98,7 +101,7 @@ func TestForkListLimitedAndPrivateRepos(t *testing.T) { user4Sess := loginUser(t, "user4") user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user4"}) privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) - assert.EqualValues(t, structs.VisibleTypePrivate, privateOrg.Visibility) + assert.Equal(t, structs.VisibleTypePrivate, privateOrg.Visibility) ownerTeam2, err := org_model.OrgFromUser(privateOrg).GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user4)) @@ -109,7 +112,7 @@ func TestForkListLimitedAndPrivateRepos(t *testing.T) { req := NewRequest(t, "GET", "/user2/repo1/forks") resp := MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - assert.EqualValues(t, 0, htmlDoc.Find(forkItemSelector).Length()) + assert.Equal(t, 0, htmlDoc.Find(forkItemSelector).Length()) }) t.Run("Logged in", func(t *testing.T) { @@ -118,11 +121,12 @@ func TestForkListLimitedAndPrivateRepos(t *testing.T) { req := NewRequest(t, "GET", "/user2/repo1/forks") resp := user1Sess.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - assert.EqualValues(t, 1, htmlDoc.Find(forkItemSelector).Length()) + // since user1 is an admin, he can get both of the forked repositories + assert.Equal(t, 2, htmlDoc.Find(forkItemSelector).Length()) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user1)) resp = user1Sess.MakeRequest(t, req, http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) - assert.EqualValues(t, 2, htmlDoc.Find(forkItemSelector).Length()) + assert.Equal(t, 2, htmlDoc.Find(forkItemSelector).Length()) }) } diff --git a/tests/integration/repo_generate_test.go b/tests/integration/repo_generate_test.go index ff2aa220d3..fca4e92982 100644 --- a/tests/integration/repo_generate_test.go +++ b/tests/integration/repo_generate_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strconv" "strings" "testing" @@ -31,20 +32,20 @@ func testRepoGenerate(t *testing.T, session *TestSession, templateID, templateOw // Step2: click the "Use this template" button htmlDoc := NewHTMLParser(t, resp.Body) - link, exists := htmlDoc.doc.Find("a.ui.button[href^=\"/repo/create\"]").Attr("href") + link, exists := htmlDoc.doc.Find(`a.ui.button[href^="/repo/create"]`).Attr("href") assert.True(t, exists, "The template has changed") req = NewRequest(t, "GET", link) resp = session.MakeRequest(t, req, http.StatusOK) - // Step3: fill the form of the create + // Step3: fill the form on the "create" page htmlDoc = NewHTMLParser(t, resp.Body) - link, exists = htmlDoc.doc.Find("form.ui.form[action^=\"/repo/create\"]").Attr("action") + link, exists = htmlDoc.doc.Find(`form.ui.form[action^="/repo/create"]`).Attr("action") assert.True(t, exists, "The template has changed") - _, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", generateOwner.ID)).Attr("data-value") + _, exists = htmlDoc.doc.Find(fmt.Sprintf(`#repo_owner_dropdown .item[data-value="%d"]`, generateOwner.ID)).Attr("data-value") assert.True(t, exists, "Generate owner '%s' is not present in select box", generateOwnerName) req = NewRequestWithValues(t, "POST", link, map[string]string{ "_csrf": htmlDoc.GetCSRF(), - "uid": fmt.Sprintf("%d", generateOwner.ID), + "uid": strconv.FormatInt(generateOwner.ID, 10), "repo_name": generateRepoName, "repo_template": templateID, "git_content": "true", diff --git a/tests/integration/repo_merge_upstream_test.go b/tests/integration/repo_merge_upstream_test.go index e3e423c51d..d33d31c646 100644 --- a/tests/integration/repo_merge_upstream_test.go +++ b/tests/integration/repo_merge_upstream_test.go @@ -60,25 +60,54 @@ func TestRepoMergeUpstream(t *testing.T) { t.Run("HeadBeforeBase", func(t *testing.T) { // add a file in base repo + sessionBaseUser := loginUser(t, baseUser.Name) require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "new-file.txt", "master", "test-content-1")) - // the repo shows a prompt to "sync fork" var mergeUpstreamLink string - require.Eventually(t, func() bool { - resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork/src/branch/fork-branch", forkUser.Name), http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - mergeUpstreamLink = queryMergeUpstreamButtonLink(htmlDoc) - if mergeUpstreamLink == "" { - return false - } - respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html() - return strings.Contains(respMsg, `This branch is 1 commit behind <a href="/user2/repo1/src/branch/master">user2/repo1:master</a>`) - }, 5*time.Second, 100*time.Millisecond) + t.Run("DetectDefaultBranch", func(t *testing.T) { + // the repo shows a prompt to "sync fork" (defaults to the default branch) + require.Eventually(t, func() bool { + resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork/src/branch/fork-branch", forkUser.Name), http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + mergeUpstreamLink = queryMergeUpstreamButtonLink(htmlDoc) + if mergeUpstreamLink == "" { + return false + } + respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html() + return strings.Contains(respMsg, `This branch is 1 commit behind <a href="/user2/repo1/src/branch/master">user2/repo1:master</a>`) + }, 5*time.Second, 100*time.Millisecond) + }) + + t.Run("DetectSameBranch", func(t *testing.T) { + // if the fork-branch name also exists in the base repo, then use that branch instead + req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/_new/branch/master", map[string]string{ + "_csrf": GetUserCSRFToken(t, sessionBaseUser), + "new_branch_name": "fork-branch", + }) + sessionBaseUser.MakeRequest(t, req, http.StatusSeeOther) + + require.Eventually(t, func() bool { + resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork/src/branch/fork-branch", forkUser.Name), http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + mergeUpstreamLink = queryMergeUpstreamButtonLink(htmlDoc) + if mergeUpstreamLink == "" { + return false + } + respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html() + return strings.Contains(respMsg, `This branch is 1 commit behind <a href="/user2/repo1/src/branch/fork-branch">user2/repo1:fork-branch</a>`) + }, 5*time.Second, 100*time.Millisecond) + }) // click the "sync fork" button req = NewRequestWithValues(t, "POST", mergeUpstreamLink, map[string]string{"_csrf": GetUserCSRFToken(t, session)}) session.MakeRequest(t, req, http.StatusOK) checkFileContent("fork-branch", "test-content-1") + + // delete the "fork-branch" from the base repo + req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/delete?name=fork-branch", map[string]string{ + "_csrf": GetUserCSRFToken(t, sessionBaseUser), + }) + sessionBaseUser.MakeRequest(t, req, http.StatusOK) }) t.Run("BaseChangeAfterHeadChange", func(t *testing.T) { @@ -118,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_mergecommit_revert_test.go b/tests/integration/repo_mergecommit_revert_test.go deleted file mode 100644 index 103fb47e2b..0000000000 --- a/tests/integration/repo_mergecommit_revert_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - "code.gitea.io/gitea/tests" - - "github.com/stretchr/testify/assert" -) - -func TestRepoMergeCommitRevert(t *testing.T) { - defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - - req := NewRequest(t, "GET", "/user2/test_commit_revert/_cherrypick/deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7/main?ref=main&refType=branch&cherry-pick-type=revert") - resp := session.MakeRequest(t, req, http.StatusOK) - - htmlDoc := NewHTMLParser(t, resp.Body) - req = NewRequestWithValues(t, "POST", "/user2/test_commit_revert/_cherrypick/deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7/main", map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - "last_commit": "deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7", - "page_has_posted": "true", - "revert": "true", - "commit_summary": "reverting test commit", - "commit_message": "test message", - "commit_choice": "direct", - "new_branch_name": "test-revert-branch-1", - }) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - - // A successful revert redirects to the main branch - assert.EqualValues(t, "/user2/test_commit_revert/src/branch/main", resp.Header().Get("Location")) -} diff --git a/tests/integration/repo_search_test.go b/tests/integration/repo_search_test.go index 29d1517f4e..36a2e81f3b 100644 --- a/tests/integration/repo_search_test.go +++ b/tests/integration/repo_search_test.go @@ -57,5 +57,5 @@ func testSearch(t *testing.T, url string, expected []string) { resp := MakeRequest(t, req, http.StatusOK) filenames := resultFilenames(NewHTMLParser(t, resp.Body)) - assert.EqualValues(t, expected, filenames) + assert.Equal(t, expected, filenames) } diff --git a/tests/integration/repo_tag_test.go b/tests/integration/repo_tag_test.go index 5638826ea0..8ea7508559 100644 --- a/tests/integration/repo_tag_test.go +++ b/tests/integration/repo_tag_test.go @@ -55,10 +55,10 @@ func TestCreateNewTagProtected(t *testing.T) { doGitClone(dstPath, u)(t) - _, _, err := git.NewCommand(git.DefaultContext, "tag", "v-2").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("tag", "v-2").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "--tags").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.Error(t, err) assert.Contains(t, err.Error(), "Tag v-2 is protected") }) @@ -75,20 +75,20 @@ func TestCreateNewTagProtected(t *testing.T) { doGitClone(dstPath, u)(t) - _, _, err := git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update", "--force").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("tag", "v-1.1", "-m", "force update", "--force").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "--tags").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "--tags").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.Error(t, err) assert.Contains(t, err.Error(), "the tag already exists in the remote") - _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags", "--force").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "--tags", "--force").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName()) resp := MakeRequest(t, req, http.StatusOK) @@ -137,15 +137,15 @@ func TestRepushTag(t *testing.T) { doGitClone(dstPath, u)(t) // create and push a tag - _, _, err := git.NewCommand(git.DefaultContext, "tag", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("tag", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin", "--tags", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // create a release for the tag createdRelease := createNewReleaseUsingAPI(t, token, owner, repo, "v2.0", "", "Release of v2.0", "desc") assert.False(t, createdRelease.IsDraft) // delete the tag - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--delete", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin", "--delete", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // query the release by API and it should be a draft req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) @@ -154,7 +154,7 @@ func TestRepushTag(t *testing.T) { DecodeJSON(t, resp, &respRelease) assert.True(t, respRelease.IsDraft) // re-push the tag - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin", "--tags", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // query the release by API and it should not be a draft req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 8c568a1272..adfe07519f 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -6,21 +6,51 @@ package integration import ( "fmt" "net/http" + "os" "path" + "strconv" "strings" "testing" "time" + "code.gitea.io/gitea/models/db" + 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/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/tests" "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestViewRepo(t *testing.T) { +func TestRepoView(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("ViewRepoPublic", testViewRepoPublic) + t.Run("ViewRepoWithCache", testViewRepoWithCache) + t.Run("ViewRepoPrivate", testViewRepoPrivate) + t.Run("ViewRepo1CloneLinkAnonymous", testViewRepo1CloneLinkAnonymous) + t.Run("ViewRepo1CloneLinkAuthorized", testViewRepo1CloneLinkAuthorized) + t.Run("ViewRepoWithSymlinks", testViewRepoWithSymlinks) + t.Run("ViewFileInRepo", testViewFileInRepo) + 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) + t.Run("ViewCommit", testViewCommit) +} + +func testViewRepoPublic(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -41,87 +71,118 @@ func TestViewRepo(t *testing.T) { session.MakeRequest(t, req, http.StatusNotFound) } -func testViewRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/org3/repo3") - session := loginUser(t, "user2") - resp := session.MakeRequest(t, req, http.StatusOK) - - htmlDoc := NewHTMLParser(t, resp.Body) - files := htmlDoc.doc.Find("#repo-files-table .repo-file-item") - - type file struct { - fileName string - commitID string - commitMsg string - commitTime string - } +func testViewRepoWithCache(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testView := func(t *testing.T) { + req := NewRequest(t, "GET", "/org3/repo3") + session := loginUser(t, "user2") + resp := session.MakeRequest(t, req, http.StatusOK) - var items []file - - files.Each(func(i int, s *goquery.Selection) { - tds := s.Find(".repo-file-cell") - var f file - tds.Each(func(i int, s *goquery.Selection) { - if i == 0 { - f.fileName = strings.TrimSpace(s.Text()) - } else if i == 1 { - a := s.Find("a") - f.commitMsg = strings.TrimSpace(a.Text()) - l, _ := a.Attr("href") - f.commitID = path.Base(l) - } + htmlDoc := NewHTMLParser(t, resp.Body) + files := htmlDoc.doc.Find("#repo-files-table .repo-file-item") + + type file struct { + fileName string + commitID string + commitMsg string + commitTime string + } + + var items []file + + files.Each(func(i int, s *goquery.Selection) { + tds := s.Find(".repo-file-cell") + var f file + tds.Each(func(i int, s *goquery.Selection) { + if i == 0 { + f.fileName = strings.TrimSpace(s.Text()) + } else if i == 1 { + a := s.Find("a") + f.commitMsg = strings.TrimSpace(a.Text()) + l, _ := a.Attr("href") + f.commitID = path.Base(l) + } + }) + + // convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC" + htmlTimeString, _ := s.Find("relative-time").Attr("datetime") + htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString) + f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123) + items = append(items, f) }) - // convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC" - htmlTimeString, _ := s.Find("relative-time").Attr("datetime") - htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString) - f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123) - items = append(items, f) - }) - - commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123) - assert.EqualValues(t, []file{ - { - fileName: "doc", - commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", - commitMsg: "init project", - commitTime: commitT, - }, - { - fileName: "README.md", - commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", - commitMsg: "init project", - commitTime: commitT, - }, - }, items) -} + commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123) + assert.Equal(t, []file{ + { + fileName: "doc", + commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", + commitMsg: "init project", + commitTime: commitT, + }, + { + fileName: "README.md", + commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", + commitMsg: "init project", + commitTime: commitT, + }, + }, items) + } -func TestViewRepo2(t *testing.T) { + // FIXME: these test don't seem quite right, no enough assert // no last commit cache - testViewRepo(t) - + testView(t) // enable last commit cache for all repositories oldCommitsCount := setting.CacheService.LastCommit.CommitsCount setting.CacheService.LastCommit.CommitsCount = 0 // first view will not hit the cache - testViewRepo(t) + testView(t) // second view will hit the cache - testViewRepo(t) + testView(t) setting.CacheService.LastCommit.CommitsCount = oldCommitsCount } -func TestViewRepo3(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepoPrivate(t *testing.T) { + defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", "/org3/repo3") - session := loginUser(t, "user4") - session.MakeRequest(t, req, http.StatusOK) + MakeRequest(t, req, http.StatusNotFound) + + t.Run("OrgMemberAccess", func(t *testing.T) { + req = NewRequest(t, "GET", "/org3/repo3") + session := loginUser(t, "user4") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), `<div id="repo-files-table"`) + }) + + t.Run("PublicAccess-AnonymousAccess", func(t *testing.T) { + session := loginUser(t, "user1") + + // set unit code to "anonymous read" + req = NewRequestWithValues(t, "POST", "/org3/repo3/settings/public_access", map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "repo-unit-access-" + strconv.Itoa(int(unit.TypeCode)): "anonymous-read", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // try to "anonymous read" (ok) + req = NewRequest(t, "GET", "/org3/repo3") + resp := MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), `<span class="ui basic orange label">Public Access</span>`) + + // remove "anonymous read" + req = NewRequestWithValues(t, "POST", "/org3/repo3/settings/public_access", map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // try to "anonymous read" (not found) + req = NewRequest(t, "GET", "/org3/repo3") + MakeRequest(t, req, http.StatusNotFound) + }) } -func TestViewRepo1CloneLinkAnonymous(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepo1CloneLinkAnonymous(t *testing.T) { + defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", "/user2/repo1") resp := MakeRequest(t, req, http.StatusOK) @@ -130,12 +191,17 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) { link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link") assert.True(t, exists, "The template has changed") assert.Equal(t, setting.AppURL+"user2/repo1.git", link) + _, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") assert.False(t, exists) + + link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link") + assert.True(t, exists, "The template has changed") + assert.Equal(t, "tea clone user2/repo1", link) } -func TestViewRepo1CloneLinkAuthorized(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepo1CloneLinkAuthorized(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -146,15 +212,20 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) { link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link") assert.True(t, exists, "The template has changed") assert.Equal(t, setting.AppURL+"user2/repo1.git", link) + link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") assert.True(t, exists, "The template has changed") sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port) assert.Equal(t, sshURL, link) -} -func TestViewRepoWithSymlinks(t *testing.T) { - defer tests.PrepareTestEnv(t)() + link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link") + assert.True(t, exists, "The template has changed") + assert.Equal(t, "tea clone user2/repo1", link) +} +func testViewRepoWithSymlinks(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.UI.FileIconTheme, "basic")() session := loginUser(t, "user2") req := NewRequest(t, "GET", "/user2/repo20.git") @@ -176,8 +247,8 @@ func TestViewRepoWithSymlinks(t *testing.T) { } // TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file -func TestViewFileInRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewFileInRepo(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -189,14 +260,14 @@ func TestViewFileInRepo(t *testing.T) { repoTopics := htmlDoc.doc.Find("#repo-topics") repoSummary := htmlDoc.doc.Find(".repository-summary") - assert.EqualValues(t, 0, description.Length()) - assert.EqualValues(t, 0, repoTopics.Length()) - assert.EqualValues(t, 0, repoSummary.Length()) + assert.Equal(t, 0, description.Length()) + assert.Equal(t, 0, repoTopics.Length()) + assert.Equal(t, 0, repoSummary.Length()) } // TestBlameFileInRepo repo description, topics and summary should not be displayed when running blame on a file -func TestBlameFileInRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testBlameFileInRepo(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -208,14 +279,14 @@ func TestBlameFileInRepo(t *testing.T) { repoTopics := htmlDoc.doc.Find("#repo-topics") repoSummary := htmlDoc.doc.Find(".repository-summary") - assert.EqualValues(t, 0, description.Length()) - assert.EqualValues(t, 0, repoTopics.Length()) - assert.EqualValues(t, 0, repoSummary.Length()) + assert.Equal(t, 0, description.Length()) + assert.Equal(t, 0, repoTopics.Length()) + assert.Equal(t, 0, repoSummary.Length()) } // TestViewRepoDirectory repo description, topics and summary should not be displayed when within a directory -func TestViewRepoDirectory(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepoDirectory(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -236,8 +307,8 @@ func TestViewRepoDirectory(t *testing.T) { } // ensure that the all the different ways to find and render a README work -func TestViewRepoDirectoryReadme(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepoDirectoryReadme(t *testing.T) { + defer tests.PrintCurrentTest(t)() // there are many combinations: // - READMEs can be .md, .txt, or have no extension @@ -343,8 +414,23 @@ func TestViewRepoDirectoryReadme(t *testing.T) { missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/") } -func TestMarkDownReadmeImage(t *testing.T) { - defer tests.PrepareTestEnv(t)() +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)() session := loginUser(t, "user2") @@ -365,8 +451,8 @@ func TestMarkDownReadmeImage(t *testing.T) { assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src) } -func TestMarkDownReadmeImageSubfolder(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testMarkDownReadmeImageSubfolder(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -388,8 +474,8 @@ func TestMarkDownReadmeImageSubfolder(t *testing.T) { assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src) } -func TestGeneratedSourceLink(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testGeneratedSourceLink(t *testing.T) { + defer tests.PrintCurrentTest(t)() t.Run("Rendered file", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -424,11 +510,54 @@ func TestGeneratedSourceLink(t *testing.T) { }) } -func TestViewCommit(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewCommit(t *testing.T) { + defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", "/user2/repo1/commit/0123456789012345678901234567890123456789") req.Header.Add("Accept", "text/html") resp := MakeRequest(t, req, http.StatusNotFound) assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "non-existing commit should render 404 page") } + +// TestGenerateRepository the test cannot succeed when moved as a unit test +func TestGenerateRepository(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // a successful generate from template + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo44 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) + + generatedRepo, err := repo_service.GenerateRepository(git.DefaultContext, user2, user2, repo44, repo_service.GenerateRepoOptions{ + Name: "generated-from-template-44", + GitContent: true, + }) + assert.NoError(t, err) + assert.NotNil(t, generatedRepo) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name)) + assert.NoError(t, err) + assert.True(t, exist) + + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name}) + + err = repo_service.DeleteRepositoryDirectly(db.DefaultContext, generatedRepo.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "generated-from-template-44"), os.ModePerm)) + + generatedRepo2, err := repo_service.GenerateRepository(db.DefaultContext, user2, user2, repo44, repo_service.GenerateRepoOptions{ + Name: "generated-from-template-44", + GitContent: true, + }) + assert.Nil(t, generatedRepo2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name)) + assert.NoError(t, err) + assert.False(t, exist) +} diff --git a/tests/integration/repo_topic_test.go b/tests/integration/repo_topic_test.go index f198397007..7f9594b9fd 100644 --- a/tests/integration/repo_topic_test.go +++ b/tests/integration/repo_topic_test.go @@ -25,7 +25,7 @@ func TestTopicSearch(t *testing.T) { res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 6) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // pagination search topics topics.TopicNames = nil @@ -35,7 +35,7 @@ func TestTopicSearch(t *testing.T) { res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 4) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // second page topics.TopicNames = nil @@ -45,7 +45,7 @@ func TestTopicSearch(t *testing.T) { res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 2) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // add keyword search topics.TopicNames = nil @@ -63,7 +63,7 @@ func TestTopicSearch(t *testing.T) { DecodeJSON(t, res, &topics) if assert.Len(t, topics.TopicNames, 1) { assert.EqualValues(t, 2, topics.TopicNames[0].ID) - assert.EqualValues(t, "database", topics.TopicNames[0].Name) - assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount) + assert.Equal(t, "database", topics.TopicNames[0].Name) + assert.Equal(t, 1, topics.TopicNames[0].RepoCount) } } diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index ef44a9e2d0..1da7bc9d3c 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -4,14 +4,31 @@ package integration import ( + "fmt" + "io" "net/http" + "net/http/httptest" + "net/url" + "path" "strings" "testing" + 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" + webhook_module "code.gitea.io/gitea/modules/webhook" "code.gitea.io/gitea/tests" + runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewWebHookLink(t *testing.T) { @@ -39,3 +56,1169 @@ func TestNewWebHookLink(t *testing.T) { }) } } + +func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string, branchFilter ...string) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + var branchFilterString string + if len(branchFilter) > 0 { + branchFilterString = branchFilter[0] + } + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+userName+"/"+repoName+"/hooks", api.CreateHookOption{ + Type: "gitea", + Config: api.CreateHookOptionConfig{ + "content_type": "json", + "url": url, + }, + Events: []string{event}, + Active: true, + BranchFilter: branchFilterString, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) +} + +func testCreateWebhookForRepo(t *testing.T, session *TestSession, webhookType, userName, repoName, url, eventKind string) { + csrf := GetUserCSRFToken(t, session) + req := NewRequestWithValues(t, "POST", "/"+userName+"/"+repoName+"/settings/hooks/"+webhookType+"/new", map[string]string{ + "_csrf": csrf, + "payload_url": url, + "events": eventKind, + "active": "true", + "content_type": fmt.Sprintf("%d", webhook.ContentTypeJSON), + "http_method": "POST", + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + +func testAPICreateWebhookForOrg(t *testing.T, session *TestSession, userName, url, event string) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/"+userName+"/hooks", api.CreateHookOption{ + Type: "gitea", + Config: api.CreateHookOptionConfig{ + "content_type": "json", + "url": url, + }, + Events: []string{event}, + Active: true, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) +} + +type mockWebhookProvider struct { + server *httptest.Server +} + +func newMockWebhookProvider(callback func(r *http.Request), status int) *mockWebhookProvider { + m := &mockWebhookProvider{} + m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callback(r) + w.WriteHeader(status) + })) + return m +} + +func (m *mockWebhookProvider) URL() string { + if m.server == nil { + return "" + } + return m.server.URL +} + +// Close closes the mock webhook http server +func (m *mockWebhookProvider) Close() { + if m.server != nil { + m.server.Close() + m.server = nil + } +} + +func Test_WebhookCreate(t *testing.T) { + 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") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "create") + + // 2. trigger the webhook + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + + // 3. validate the webhook is triggered + assert.Len(t, payloads, 1) + assert.Equal(t, string(webhook_module.HookEventCreate), triggeredEvent) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Equal(t, "master2", payloads[0].Ref) + assert.Equal(t, "branch", payloads[0].RefType) + }) +} + +func Test_WebhookDelete(t *testing.T) { + 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") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "delete") + + // 2. trigger the webhook + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + testAPIDeleteBranch(t, "master2", http.StatusNoContent) + + // 3. validate the webhook is triggered + assert.Equal(t, "delete", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Equal(t, "master2", payloads[0].Ref) + assert.Equal(t, "branch", payloads[0].RefType) + }) +} + +func Test_WebhookFork(t *testing.T) { + 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") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "fork") + + // 2. trigger the webhook + testRepoFork(t, session, "user2", "repo1", "user1", "repo1-fork", "master") + + // 3. validate the webhook is triggered + assert.Equal(t, "fork", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "repo1-fork", payloads[0].Repo.Name) + assert.Equal(t, "user1/repo1-fork", payloads[0].Repo.FullName) + assert.Equal(t, "repo1", payloads[0].Forkee.Name) + assert.Equal(t, "user2/repo1", payloads[0].Forkee.FullName) + }) +} + +func Test_WebhookIssueComment(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 = "issue_comment" + }, 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(), "issue_comment") + + t.Run("create comment", func(t *testing.T) { + // 2. trigger the webhook + issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2") + testIssueAddComment(t, session, issueURL, "issue title2 comment1", "") + + // 3. validate the webhook is triggered + assert.Equal(t, "issue_comment", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "Title2", payloads[0].Issue.Title) + assert.Equal(t, "Description2", payloads[0].Issue.Body) + assert.Equal(t, "issue title2 comment1", payloads[0].Comment.Body) + }) + + t.Run("update comment", func(t *testing.T) { + payloads = make([]api.IssueCommentPayload, 0, 2) + triggeredEvent = "" + + // 2. trigger the webhook + issueURL := testNewIssue(t, session, "user2", "repo1", "Title3", "Description3") + commentID := testIssueAddComment(t, session, issueURL, "issue title3 comment1", "") + modifiedContent := "issue title2 comment1 - modified" + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "content": modifiedContent, + }) + session.MakeRequest(t, req, http.StatusOK) + + // 3. validate the webhook is triggered + assert.Equal(t, "issue_comment", triggeredEvent) + assert.Len(t, payloads, 2) + assert.EqualValues(t, "edited", 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, "Title3", payloads[1].Issue.Title) + assert.Equal(t, "Description3", payloads[1].Issue.Body) + assert.Equal(t, modifiedContent, payloads[1].Comment.Body) + }) + + t.Run("Update comment with no content change", func(t *testing.T) { + payloads = make([]api.IssueCommentPayload, 0, 2) + triggeredEvent = "" + commentContent := "issue title3 comment1" + + // 2. trigger the webhook + issueURL := testNewIssue(t, session, "user2", "repo1", "Title3", "Description3") + commentID := testIssueAddComment(t, session, issueURL, commentContent, "") + + payloads = make([]api.IssueCommentPayload, 0, 2) + triggeredEvent = "" + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "content": commentContent, + }) + session.MakeRequest(t, req, http.StatusOK) + + // 3. validate the webhook is not triggered because no content change + assert.Empty(t, triggeredEvent) + assert.Empty(t, payloads) + }) + }) +} + +func Test_WebhookRelease(t *testing.T) { + 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") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "release") + + // 2. trigger the webhook + createNewRelease(t, session, "/user2/repo1", "v0.0.99", "v0.0.99", false, false) + + // 3. validate the webhook is triggered + assert.Equal(t, "release", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "repo1", payloads[0].Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repository.FullName) + assert.Equal(t, "v0.0.99", payloads[0].Release.TagName) + assert.False(t, payloads[0].Release.IsDraft) + assert.False(t, payloads[0].Release.IsPrerelease) + }) +} + +func Test_WebhookPush(t *testing.T) { + 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") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push") + + // 2. trigger the webhook + testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push") + + // 3. validate the webhook is triggered + assert.Equal(t, "push", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Len(t, payloads[0].Commits, 1) + assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added) + }) +} + +func Test_WebhookPushDevBranch(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) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + + // only for dev branch + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "develop") + + // 2. this should not trigger the webhook + testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push") + assert.Empty(t, triggeredEvent) + assert.Empty(t, payloads) + + // 3. trigger the webhook + testCreateFile(t, session, "user2", "repo1", "develop", "test_webhook_push.md", "# a test file for webhook push") + + // 4. validate the webhook is triggered + assert.Equal(t, "push", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "develop", payloads[0].Branch()) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Len(t, payloads[0].Commits, 1) + assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added) + }) +} + +func Test_WebhookIssue(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 = "issues" + }, 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") + + // 2. trigger the webhook + testNewIssue(t, session, "user2", "repo1", "Title1", "Description1") + + // 3. validate the webhook is triggered + assert.Equal(t, "issues", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "opened", payloads[0].Action) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + 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) + }) +} + +func Test_WebhookIssueMilestone(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) { + // create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_milestone") + + t.Run("assign a milestone", func(t *testing.T) { + // trigger the webhook + testIssueChangeMilestone(t, session, repo1.Link(), 1, 1) + + // validate the webhook is triggered + assert.Equal(t, "issues", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "milestoned", string(payloads[0].Action)) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "issue1", payloads[0].Issue.Title) + assert.Equal(t, "content for the first issue", payloads[0].Issue.Body) + assert.EqualValues(t, 1, payloads[0].Issue.Milestone.ID) + }) + + t.Run("change a milestong", func(t *testing.T) { + // trigger the webhook again + triggeredEvent = "" + payloads = make([]api.IssuePayload, 0, 1) + // change milestone to 2 + testIssueChangeMilestone(t, session, repo1.Link(), 1, 2) + + // validate the webhook is triggered + assert.Equal(t, "issues", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "milestoned", string(payloads[0].Action)) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "issue1", payloads[0].Issue.Title) + assert.Equal(t, "content for the first issue", payloads[0].Issue.Body) + assert.EqualValues(t, 2, payloads[0].Issue.Milestone.ID) + }) + + t.Run("remove a milestone", func(t *testing.T) { + // trigger the webhook again + triggeredEvent = "" + payloads = make([]api.IssuePayload, 0, 1) + // change milestone to 0 + testIssueChangeMilestone(t, session, repo1.Link(), 1, 0) + + // validate the webhook is triggered + assert.Equal(t, "issues", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "demilestoned", string(payloads[0].Action)) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "issue1", payloads[0].Issue.Title) + assert.Equal(t, "content for the first issue", payloads[0].Issue.Body) + assert.Nil(t, payloads[0].Issue.Milestone) + }) + }) +} + +func Test_WebhookPullRequest(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) + // 2. trigger the webhook + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") + + // 3. validate the webhook is triggered + assert.Equal(t, "pull_request", triggeredEvent) + require.Len(t, payloads, 1) + assert.Equal(t, "repo1", payloads[0].PullRequest.Base.Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName) + assert.Equal(t, "repo1", payloads[0].PullRequest.Head.Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Head.Repository.FullName) + assert.Equal(t, 0, *payloads[0].PullRequest.Additions) + assert.Equal(t, 0, *payloads[0].PullRequest.ChangedFiles) + assert.Equal(t, 0, *payloads[0].PullRequest.Deletions) + }) +} + +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") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_comment") + + // 2. trigger the webhook + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + prID := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") + + testIssueAddComment(t, session, "/user2/repo1/pulls/"+prID, "pull title2 comment1", "") + + // 3. validate the webhook is triggered + assert.Equal(t, "pull_request_comment", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "first pull request", payloads[0].Issue.Title) + assert.Empty(t, payloads[0].Issue.Body) + assert.Equal(t, "pull title2 comment1", payloads[0].Comment.Body) + }) +} + +func Test_WebhookWiki(t *testing.T) { + 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") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "wiki") + + // 2. trigger the webhook + testAPICreateWikiPage(t, session, "user2", "repo1", "Test Wiki Page", http.StatusCreated) + + // 3. validate the webhook is triggered + assert.Equal(t, "wiki", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.Equal(t, "repo1", payloads[0].Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repository.FullName) + assert.Equal(t, "Test-Wiki-Page", payloads[0].Page) + }) +} + +func Test_WebhookRepository(t *testing.T) { + 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") + + testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "repository") + + // 2. trigger the webhook + testAPIOrgCreateRepo(t, session, "org3", "repo_new", http.StatusCreated) + + // 3. validate the webhook is triggered + assert.Equal(t, "repository", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.Equal(t, "org3", payloads[0].Organization.UserName) + assert.Equal(t, "repo_new", payloads[0].Repository.Name) + assert.Equal(t, "org3/repo_new", payloads[0].Repository.FullName) + }) +} + +func Test_WebhookPackage(t *testing.T) { + 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") + + testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "package") + + // 2. trigger the webhook + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s", "org3", "gitea", "v1.24.0") + req := NewRequestWithBody(t, "PUT", url+"/gitea", strings.NewReader("This is a dummy file")). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + // 3. validate the webhook is triggered + assert.Equal(t, "package", triggeredEvent) + assert.Len(t, payloads, 1) + assert.EqualValues(t, "created", payloads[0].Action) + assert.Equal(t, "gitea", payloads[0].Package.Name) + assert.Equal(t, "generic", payloads[0].Package.Type) + assert.Equal(t, "org3", payloads[0].Organization.UserName) + assert.Equal(t, "v1.24.0", payloads[0].Package.Version) + }) +} + +func Test_WebhookStatus(t *testing.T) { + 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") + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "status") + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + + gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1) + assert.NoError(t, err) + commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) + assert.NoError(t, err) + + // 2. trigger the webhook + testCtx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeAll) + + // update a status for a commit via API + doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{ + State: commitstatus.CommitStatusSuccess, + TargetURL: "http://test.ci/", + Description: "", + Context: "testci", + })(t) + + // 3. validate the webhook is triggered + assert.Equal(t, "status", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, commitID, payloads[0].Commit.ID) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Equal(t, "testci", payloads[0].Context) + assert.Equal(t, commitID, payloads[0].SHA) + }) +} + +func Test_WebhookStatus_NoWrongTrigger(t *testing.T) { + 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") + + // create a push_only webhook from web UI + testCreateWebhookForRepo(t, session, "gitea", "user2", "repo1", provider.URL(), "push_only") + + // 2. trigger the webhook with a push action + testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push") + + // 3. validate the webhook is triggered with right event + assert.Equal(t, "push", trigger) + }) +} + +func Test_WebhookWorkflowJob(t *testing.T) { + 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") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "workflow_job") + + 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. 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_job", triggeredEvent) + assert.Len(t, payloads, 2) + assert.Equal(t, "queued", payloads[0].Action) + assert.Equal(t, "queued", payloads[0].WorkflowJob.Status) + assert.Equal(t, []string{"ubuntu-latest"}, payloads[0].WorkflowJob.Labels) + assert.Equal(t, commitID, payloads[0].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + + assert.Equal(t, "waiting", payloads[1].Action) + assert.Equal(t, "waiting", payloads[1].WorkflowJob.Status) + assert.Equal(t, commitID, payloads[1].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[1].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[1].Repo.FullName) + + // 4. Execute a single Job + task := runner.fetchTask(t) + outcome := &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + } + runner.execTask(t, task, outcome) + + // 5. validate the webhook is triggered + assert.Equal(t, "workflow_job", triggeredEvent) + assert.Len(t, payloads, 5) + assert.Equal(t, "in_progress", payloads[2].Action) + assert.Equal(t, "in_progress", payloads[2].WorkflowJob.Status) + assert.Equal(t, "mock-runner", payloads[2].WorkflowJob.RunnerName) + assert.Equal(t, commitID, payloads[2].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[2].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[2].Repo.FullName) + + assert.Equal(t, "completed", payloads[3].Action) + assert.Equal(t, "completed", payloads[3].WorkflowJob.Status) + assert.Equal(t, "mock-runner", payloads[3].WorkflowJob.RunnerName) + assert.Equal(t, "success", payloads[3].WorkflowJob.Conclusion) + 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/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) + + assert.Equal(t, "queued", payloads[4].Action) + assert.Equal(t, "queued", payloads[4].WorkflowJob.Status) + assert.Equal(t, []string{"ubuntu-latest"}, payloads[4].WorkflowJob.Labels) + assert.Equal(t, commitID, payloads[4].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[4].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[4].Repo.FullName) + + // 6. Execute a single Job + 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_job", triggeredEvent) + assert.Len(t, payloads, 7) + assert.Equal(t, "in_progress", payloads[5].Action) + assert.Equal(t, "in_progress", payloads[5].WorkflowJob.Status) + assert.Equal(t, "mock-runner", payloads[5].WorkflowJob.RunnerName) + + assert.Equal(t, commitID, payloads[5].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[5].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[5].Repo.FullName) + + assert.Equal(t, "completed", payloads[6].Action) + assert.Equal(t, "completed", payloads[6].WorkflowJob.Status) + assert.Equal(t, "failure", payloads[6].WorkflowJob.Conclusion) + assert.Equal(t, "mock-runner", payloads[6].WorkflowJob.RunnerName) + 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/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 d86dcc01fe..dc389f5680 100644 --- a/tests/integration/repofiles_change_test.go +++ b/tests/integration/repofiles_change_test.go @@ -4,8 +4,9 @@ package integration import ( + "fmt" "net/url" - "path/filepath" + "path" "strings" "testing" "time" @@ -16,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" @@ -57,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{ @@ -71,14 +107,14 @@ func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang NewBranch: repo.DefaultBranch, Message: "Deletes README.md", Author: &files_service.IdentityOptions{ - Name: "Bob Smith", - Email: "bob@smith.com", + GitUserName: "Bob Smith", + GitUserEmail: "bob@smith.com", }, Committer: nil, } } -func getExpectedFileResponseForRepofilesDelete() *api.FileResponse { +func getExpectedFileResponseForRepoFilesDelete() *api.FileResponse { // Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined return &api.FileResponse{ Content: nil, @@ -106,7 +142,7 @@ func getExpectedFileResponseForRepofilesDelete() *api.FileResponse { } } -func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) *api.FileResponse { +func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.Commit) *api.FileResponse { treePath := "new/file.txt" encoding := "base64" content := "VGhpcyBpcyBhIE5FVyBmaWxl" @@ -116,18 +152,20 @@ func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) * downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath return &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 18, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: path.Base(treePath), + Path: treePath, + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", + LastCommitSHA: util.ToPointer(lastCommit.ID.String()), + LastCommitterDate: util.ToPointer(lastCommit.Committer.When), + LastAuthorDate: util.ToPointer(lastCommit.Author.When), + Type: "file", + Size: 18, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -160,7 +198,7 @@ func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) * SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", }, }, - Message: "Updates README.md\n", + Message: "Creates new/file.txt\n", Tree: &api.CommitMeta{ URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc", SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc", @@ -175,7 +213,7 @@ func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) * } } -func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA string) *api.FileResponse { +func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA string, lastCommitterWhen, lastAuthorWhen time.Time) *api.FileResponse { encoding := "base64" content := "VGhpcyBpcyBVUERBVEVEIGNvbnRlbnQgZm9yIHRoZSBSRUFETUUgZmlsZQ==" selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + filename + "?ref=master" @@ -184,18 +222,20 @@ func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + filename return &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filename, - Path: filename, - SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 43, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: filename, + Path: filename, + SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", + LastCommitSHA: util.ToPointer(lastCommitSHA), + LastCommitterDate: util.ToPointer(lastCommitterWhen), + LastAuthorDate: util.ToPointer(lastAuthorWhen), + Type: "file", + Size: 43, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -243,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: util.ToPointer(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) { @@ -268,14 +416,14 @@ func TestChangeRepoFilesForCreate(t *testing.T) { commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) lastCommit, _ := gitRepo.GetCommitByPath("new/file.txt") - expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID, lastCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForRepoFilesCreate(commitID, lastCommit) assert.NotNil(t, expectedFileResponse) if expectedFileResponse != nil { - assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0]) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) + assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) + assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) } }) } @@ -305,12 +453,12 @@ func TestChangeRepoFilesForUpdate(t *testing.T) { commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) - assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0]) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) + assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) + assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) }) } @@ -341,7 +489,7 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) { commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) // assert that the old file no longer exists in the last commit of the branch fromEntry, err := commit.GetTreeEntryByPath(opts.Files[0].FromTreePath) switch err.(type) { @@ -355,12 +503,44 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) { assert.Nil(t, fromEntry) // Should no longer exist here assert.NotNil(t, toEntry) // Should exist here // assert SHA has remained the same but paths use the new file name - assert.EqualValues(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA) - assert.EqualValues(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name) - assert.EqualValues(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path) - assert.EqualValues(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name) + assert.Equal(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path) + assert.Equal(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL) + assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + }) +} + +func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) { + 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 = nil, nil // there might be different time in one operation, so we ignore them + } + assert.Len(t, filesResponse.Files, 4) + assert.Equal(t, expectedFileResponse.Files, filesResponse.Files) }) } @@ -392,8 +572,8 @@ func TestChangeRepoFilesWithoutBranchNames(t *testing.T) { commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch) lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) - assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0]) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) + assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) }) } @@ -418,13 +598,13 @@ func testDeleteRepoFiles(t *testing.T, u *url.URL) { t.Run("Delete README.md file", func(t *testing.T) { filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts) assert.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepofilesDelete() + expectedFileResponse := getExpectedFileResponseForRepoFilesDelete() assert.NotNil(t, filesResponse) assert.Nil(t, filesResponse.Files[0]) - assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) - assert.EqualValues(t, expectedFileResponse.Verification, filesResponse.Verification) + assert.Equal(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) + assert.Equal(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) + assert.Equal(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) + assert.Equal(t, expectedFileResponse.Verification, filesResponse.Verification) }) t.Run("Verify README.md has been deleted", func(t *testing.T) { @@ -460,13 +640,13 @@ func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) { t.Run("Delete README.md without Branch Name", func(t *testing.T) { filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts) assert.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepofilesDelete() + expectedFileResponse := getExpectedFileResponseForRepoFilesDelete() assert.NotNil(t, filesResponse) assert.Nil(t, filesResponse.Files[0]) - assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) - assert.EqualValues(t, expectedFileResponse.Verification, filesResponse.Verification) + assert.Equal(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) + assert.Equal(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) + assert.Equal(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) + assert.Equal(t, expectedFileResponse.Verification, filesResponse.Verification) }) } @@ -490,7 +670,7 @@ func TestChangeRepoFilesErrors(t *testing.T) { filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts) assert.Error(t, err) assert.Nil(t, filesResponse) - expectedError := "branch does not exist [name: " + opts.OldBranch + "]" + expectedError := fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", repo.ID, opts.OldBranch) assert.EqualError(t, err, expectedError) }) diff --git a/tests/integration/session_test.go b/tests/integration/session_test.go index b18a25827d..f72e2e24b7 100644 --- a/tests/integration/session_test.go +++ b/tests/integration/session_test.go @@ -27,11 +27,11 @@ func Test_RegenerateSession(t *testing.T) { sess, err := auth.RegenerateSession(db.DefaultContext, "", key) assert.NoError(t, err) - assert.EqualValues(t, key, sess.Key) + assert.Equal(t, key, sess.Key) assert.Empty(t, sess.Data) sess, err = auth.ReadSession(db.DefaultContext, key2) assert.NoError(t, err) - assert.EqualValues(t, key2, sess.Key) + assert.Equal(t, key2, sess.Key) assert.Empty(t, sess.Data) } 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/signin_test.go b/tests/integration/signin_test.go index d7c0b1bcd3..67af5b5877 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -15,8 +15,12 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" + "github.com/markbates/goth" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -32,7 +36,7 @@ func testLoginFailed(t *testing.T, username, password, message string) { htmlDoc := NewHTMLParser(t, resp.Body) resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() - assert.EqualValues(t, message, resultMsg) + assert.Equal(t, message, resultMsg) } func TestSignin(t *testing.T) { @@ -76,7 +80,7 @@ func TestSigninWithRememberMe(t *testing.T) { }) session.MakeRequest(t, req, http.StatusSeeOther) - c := session.GetCookie(setting.CookieRememberName) + c := session.GetRawCookie(setting.CookieRememberName) assert.NotNil(t, c) session = emptyTestSession(t) @@ -95,19 +99,32 @@ func TestSigninWithRememberMe(t *testing.T) { session.MakeRequest(t, req, http.StatusOK) } -func TestEnablePasswordSignInForm(t *testing.T) { +func TestEnablePasswordSignInFormAndEnablePasskeyAuth(t *testing.T) { defer tests.PrepareTestEnv(t)() + mockLinkAccount := func(ctx *context.Context) { + gothUser := goth.User{Email: "invalid-email", Name: "."} + _ = ctx.Session.Set("linkAccountGothUser", gothUser) + } + t.Run("EnablePasswordSignInForm=false", func(t *testing.T) { defer tests.PrintCurrentTest(t)() defer test.MockVariableValue(&setting.Service.EnablePasswordSignInForm, false)() req := NewRequest(t, "GET", "/user/login") resp := MakeRequest(t, req, http.StatusOK) - NewHTMLParser(t, resp.Body).AssertElement(t, "form[action='/user/login']", false) + doc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, "form[action='/user/login']", false) req = NewRequest(t, "POST", "/user/login") MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", "/user/link_account") + defer web.RouteMockReset() + web.RouteMock(web.MockAfterMiddlewares, mockLinkAccount) + resp = MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, "form[action='/user/link_account_signin']", false) }) t.Run("EnablePasswordSignInForm=true", func(t *testing.T) { @@ -116,9 +133,66 @@ func TestEnablePasswordSignInForm(t *testing.T) { req := NewRequest(t, "GET", "/user/login") resp := MakeRequest(t, req, http.StatusOK) - NewHTMLParser(t, resp.Body).AssertElement(t, "form[action='/user/login']", true) + doc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, "form[action='/user/login']", true) req = NewRequest(t, "POST", "/user/login") MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/user/link_account") + defer web.RouteMockReset() + web.RouteMock(web.MockAfterMiddlewares, mockLinkAccount) + resp = MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, "form[action='/user/link_account_signin']", true) + }) + + t.Run("EnablePasskeyAuth=false", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, false)() + + req := NewRequest(t, "GET", "/user/login") + resp := MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, ".signin-passkey", false) + }) + + t.Run("EnablePasskeyAuth=true", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, true)() + + req := NewRequest(t, "GET", "/user/login") + resp := MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, ".signin-passkey", true) + }) +} + +func TestRequireSignInView(t *testing.T) { + defer tests.PrepareTestEnv(t)() + t.Run("NoRequireSignInView", func(t *testing.T) { + require.False(t, setting.Service.RequireSignInViewStrict) + require.False(t, setting.Service.BlockAnonymousAccessExpensive) + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master") + MakeRequest(t, req, http.StatusOK) + }) + t.Run("RequireSignInView", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master") + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/login", resp.Header().Get("Location")) + }) + t.Run("BlockAnonymousAccessExpensive", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, false)() + defer test.MockVariableValue(&setting.Service.BlockAnonymousAccessExpensive, true)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() + + req := NewRequest(t, "GET", "/user2/repo1") + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/user2/repo1/src/branch/master") + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/login", resp.Header().Get("Location")) }) } 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 eb3a3e926a..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", @@ -51,7 +51,7 @@ func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { ctx := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) ctxWithDeleteRepo := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - keyname := fmt.Sprintf("%s-push", ctx.Reponame) + keyname := ctx.Reponame + "-push" u.Path = ctx.GitPath() t.Run("CreateEmptyRepository", doAPICreateRepository(ctx, true)) @@ -89,7 +89,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { reponame := "ssh-key-test-repo" username := "user2" u.Path = fmt.Sprintf("%s/%s.git", username, reponame) - keyname := fmt.Sprintf("%s-push", reponame) + keyname := reponame + "-push" // OK login ctx := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) diff --git a/tests/integration/timetracking_test.go b/tests/integration/timetracking_test.go index d459de5df6..4e8109be96 100644 --- a/tests/integration/timetracking_test.go +++ b/tests/integration/timetracking_test.go @@ -42,15 +42,15 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo htmlDoc := NewHTMLParser(t, resp.Body) - htmlDoc.AssertElement(t, ".issue-start-time", canTrackTime) - htmlDoc.AssertElement(t, ".issue-add-time", canTrackTime) + AssertHTMLElement(t, htmlDoc, ".issue-start-time", canTrackTime) + 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) @@ -59,16 +59,16 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo events := htmlDoc.doc.Find(".event > span.text") assert.Contains(t, events.Last().Text(), "started working") - htmlDoc.AssertElement(t, ".issue-stop-time", true) - htmlDoc.AssertElement(t, ".issue-cancel-time", true) + AssertHTMLElement(t, htmlDoc, ".issue-stop-time", true) + AssertHTMLElement(t, htmlDoc, ".issue-cancel-time", true) // 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_avatar_test.go b/tests/integration/user_avatar_test.go index caca9a3e56..14ea012ac8 100644 --- a/tests/integration/user_avatar_test.go +++ b/tests/integration/user_avatar_test.go @@ -5,89 +5,87 @@ package integration import ( "bytes" - "fmt" "image/png" "io" "mime/multipart" "net/http" - "net/url" "testing" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/avatar" + "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" ) func TestUserAvatar(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org + defer tests.PrepareTestEnv(t)() + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org - seed := user2.Email - if len(seed) == 0 { - seed = user2.Name - } + seed := user2.Email + if len(seed) == 0 { + seed = user2.Name + } - img, err := avatar.RandomImage([]byte(seed)) - if err != nil { - assert.NoError(t, err) - return - } + img, err := avatar.RandomImage([]byte(seed)) + if err != nil { + assert.NoError(t, err) + return + } - session := loginUser(t, "user2") - csrf := GetUserCSRFToken(t, session) + session := loginUser(t, "user2") + csrf := GetUserCSRFToken(t, session) - imgData := &bytes.Buffer{} + imgData := &bytes.Buffer{} - body := &bytes.Buffer{} + body := &bytes.Buffer{} - // Setup multi-part - writer := multipart.NewWriter(body) - writer.WriteField("source", "local") - part, err := writer.CreateFormFile("avatar", "avatar-for-testuseravatar.png") - if err != nil { - assert.NoError(t, err) - return - } + // Setup multi-part + writer := multipart.NewWriter(body) + writer.WriteField("source", "local") + part, err := writer.CreateFormFile("avatar", "avatar-for-testuseravatar.png") + if err != nil { + assert.NoError(t, err) + return + } - if err := png.Encode(imgData, img); err != nil { - assert.NoError(t, err) - return - } + if err := png.Encode(imgData, img); err != nil { + assert.NoError(t, err) + return + } - if _, err := io.Copy(part, imgData); err != nil { - assert.NoError(t, err) - return - } + if _, err := io.Copy(part, imgData); err != nil { + assert.NoError(t, err) + return + } - if err := writer.Close(); err != nil { - assert.NoError(t, err) - return - } + if err := writer.Close(); err != nil { + assert.NoError(t, err) + return + } - req := NewRequestWithBody(t, "POST", "/user/settings/avatar", body) - req.Header.Add("X-Csrf-Token", csrf) - req.Header.Add("Content-Type", writer.FormDataContentType()) + req := NewRequestWithBody(t, "POST", "/user/settings/avatar", body) + req.Header.Add("X-Csrf-Token", csrf) + req.Header.Add("Content-Type", writer.FormDataContentType()) - session.MakeRequest(t, req, http.StatusSeeOther) + session.MakeRequest(t, req, http.StatusSeeOther) - user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org + user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org - req = NewRequest(t, "GET", user2.AvatarLinkWithSize(db.DefaultContext, 0)) - _ = session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", user2.AvatarLinkWithSize(db.DefaultContext, 0)) + _ = session.MakeRequest(t, req, http.StatusOK) - testGetAvatarRedirect(t, user2) + testGetAvatarRedirect(t, user2) - // Can't test if the response matches because the image is re-generated on upload but checking that this at least doesn't give a 404 should be enough. - }) + // Can't test if the response matches because the image is re-generated on upload but checking that this at least doesn't give a 404 should be enough. } func testGetAvatarRedirect(t *testing.T, user *user_model.User) { - t.Run(fmt.Sprintf("getAvatarRedirect_%s", user.Name), func(t *testing.T) { + t.Run("getAvatarRedirect_"+user.Name, func(t *testing.T) { req := NewRequestf(t, "GET", "/%s.png", user.Name) resp := MakeRequest(t, req, http.StatusSeeOther) - assert.EqualValues(t, fmt.Sprintf("/avatars/%s", user.Avatar), resp.Header().Get("location")) + assert.Equal(t, "/avatars/"+user.Avatar, resp.Header().Get("location")) }) } diff --git a/tests/integration/user_settings_test.go b/tests/integration/user_settings_test.go index d8402eb25f..eab1a72ed5 100644 --- a/tests/integration/user_settings_test.go +++ b/tests/integration/user_settings_test.go @@ -19,21 +19,21 @@ import ( func assertNavbar(t *testing.T, doc *HTMLDoc) { // Only show the account page if users can change their email notifications, delete themselves, or manage credentials if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion, setting.UserFeatureManageCredentials) && !setting.Service.EnableNotifyMail { - doc.AssertElement(t, ".menu a[href='/user/settings/account']", false) + AssertHTMLElement(t, doc, ".menu a[href='/user/settings/account']", false) } else { - doc.AssertElement(t, ".menu a[href='/user/settings/account']", true) + AssertHTMLElement(t, doc, ".menu a[href='/user/settings/account']", true) } if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageMFA, setting.UserFeatureManageCredentials) { - doc.AssertElement(t, ".menu a[href='/user/settings/security']", false) + AssertHTMLElement(t, doc, ".menu a[href='/user/settings/security']", false) } else { - doc.AssertElement(t, ".menu a[href='/user/settings/security']", true) + AssertHTMLElement(t, doc, ".menu a[href='/user/settings/security']", true) } if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys, setting.UserFeatureManageGPGKeys) { - doc.AssertElement(t, ".menu a[href='/user/settings/keys']", false) + AssertHTMLElement(t, doc, ".menu a[href='/user/settings/keys']", false) } else { - doc.AssertElement(t, ".menu a[href='/user/settings/keys']", true) + AssertHTMLElement(t, doc, ".menu a[href='/user/settings/keys']", true) } } @@ -64,11 +64,11 @@ func TestUserSettingsAccount(t *testing.T) { doc := NewHTMLParser(t, resp.Body) // account navbar should display - doc.AssertElement(t, ".menu a[href='/user/settings/account']", true) + AssertHTMLElement(t, doc, ".menu a[href='/user/settings/account']", true) - doc.AssertElement(t, "#password", true) - doc.AssertElement(t, "#email", true) - doc.AssertElement(t, "#delete-form", true) + AssertHTMLElement(t, doc, "#password", true) + AssertHTMLElement(t, doc, "#email", true) + AssertHTMLElement(t, doc, "#delete-form", true) }) t.Run("credentials disabled", func(t *testing.T) { @@ -83,9 +83,9 @@ func TestUserSettingsAccount(t *testing.T) { assertNavbar(t, doc) - doc.AssertElement(t, "#password", false) - doc.AssertElement(t, "#email", false) - doc.AssertElement(t, "#delete-form", true) + AssertHTMLElement(t, doc, "#password", false) + AssertHTMLElement(t, doc, "#email", false) + AssertHTMLElement(t, doc, "#delete-form", true) }) t.Run("deletion disabled", func(t *testing.T) { @@ -100,9 +100,9 @@ func TestUserSettingsAccount(t *testing.T) { assertNavbar(t, doc) - doc.AssertElement(t, "#password", true) - doc.AssertElement(t, "#email", true) - doc.AssertElement(t, "#delete-form", false) + AssertHTMLElement(t, doc, "#password", true) + AssertHTMLElement(t, doc, "#email", true) + AssertHTMLElement(t, doc, "#delete-form", false) }) t.Run("deletion, credentials and email notifications are disabled", func(t *testing.T) { @@ -249,7 +249,7 @@ func TestUserSettingsSecurity(t *testing.T) { assertNavbar(t, doc) - doc.AssertElement(t, "#register-webauthn", true) + AssertHTMLElement(t, doc, "#register-webauthn", true) }) t.Run("mfa disabled", func(t *testing.T) { @@ -263,7 +263,7 @@ func TestUserSettingsSecurity(t *testing.T) { assertNavbar(t, doc) - doc.AssertElement(t, "#register-webauthn", false) + AssertHTMLElement(t, doc, "#register-webauthn", false) }) t.Run("credentials and mfa disabled", func(t *testing.T) { @@ -356,8 +356,8 @@ func TestUserSettingsKeys(t *testing.T) { assertNavbar(t, doc) - doc.AssertElement(t, "#add-ssh-button", true) - doc.AssertElement(t, "#add-gpg-key-panel", true) + AssertHTMLElement(t, doc, "#add-ssh-button", true) + AssertHTMLElement(t, doc, "#add-gpg-key-panel", true) }) t.Run("ssh keys disabled", func(t *testing.T) { @@ -372,8 +372,8 @@ func TestUserSettingsKeys(t *testing.T) { assertNavbar(t, doc) - doc.AssertElement(t, "#add-ssh-button", false) - doc.AssertElement(t, "#add-gpg-key-panel", true) + AssertHTMLElement(t, doc, "#add-ssh-button", false) + AssertHTMLElement(t, doc, "#add-gpg-key-panel", true) }) t.Run("gpg keys disabled", func(t *testing.T) { @@ -388,8 +388,8 @@ func TestUserSettingsKeys(t *testing.T) { assertNavbar(t, doc) - doc.AssertElement(t, "#add-ssh-button", true) - doc.AssertElement(t, "#add-gpg-key-panel", false) + AssertHTMLElement(t, doc, "#add-ssh-button", true) + AssertHTMLElement(t, doc, "#add-gpg-key-panel", false) }) t.Run("ssh & gpg keys disabled", func(t *testing.T) { diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go index 5b6f28d1ff..34692d9cab 100644 --- a/tests/integration/user_test.go +++ b/tests/integration/user_test.go @@ -134,8 +134,7 @@ Note: This user hasn't uploaded any GPG keys. =twTO ------END PGP PUBLIC KEY BLOCK----- -`) +-----END PGP PUBLIC KEY BLOCK-----`) // Import key // User1 <user1@example.com> session := loginUser(t, "user1") @@ -169,8 +168,7 @@ C0TLXKur6NVYQMn01iyL+FZzRpEWNuYF3f9QeeLJ/+l2DafESNhNTy17+RPmacK6 7XhJ1v6JYuh8kaYaEz8OpZDeh7f6Ho6PzJrsy/TKTKhGgZNINj1iaPFyOkQgKR5M GrE0MHOxUbc9tbtyk0F1SuzREUBH =DDXw ------END PGP PUBLIC KEY BLOCK----- -`) +-----END PGP PUBLIC KEY BLOCK-----`) // Export new key testExportUserGPGKeys(t, "user1", `-----BEGIN PGP PUBLIC KEY BLOCK----- @@ -201,8 +199,7 @@ C0TLXKur6NVYQMn01iyL+FZzRpEWNuYF3f9QeeLJ/+l2DafESNhNTy17+RPmacK6 7XhJ1v6JYuh8kaYaEz8OpZDeh7f6Ho6PzJrsy/TKTKhGgZNINj1iaPFyOkQgKR5M GrE0MHOxUbc9tbtyk0F1SuzREUBH =WFf5 ------END PGP PUBLIC KEY BLOCK----- -`) +-----END PGP PUBLIC KEY BLOCK-----`) } func testExportUserGPGKeys(t *testing.T, user, expected string) { @@ -220,12 +217,12 @@ func TestGetUserRss(t *testing.T) { user34 := "the_34-user.with.all.allowedChars" req := NewRequestf(t, "GET", "/%s.rss", user34) resp := MakeRequest(t, req, http.StatusOK) - if assert.EqualValues(t, "application/rss+xml;charset=utf-8", resp.Header().Get("Content-Type")) { + if assert.Equal(t, "application/rss+xml;charset=utf-8", resp.Header().Get("Content-Type")) { rssDoc := NewHTMLParser(t, resp.Body).Find("channel") title, _ := rssDoc.ChildrenFiltered("title").Html() - assert.EqualValues(t, "Feed of "the_1-user.with.all.allowedChars"", title) + assert.Equal(t, "Feed of "the_1-user.with.all.allowedChars"", title) description, _ := rssDoc.ChildrenFiltered("description").Html() - assert.EqualValues(t, "<p dir="auto">some <a href="https://commonmark.org/" rel="nofollow">commonmark</a>!</p>\n", description) + assert.Equal(t, "<p dir="auto">some <a href="https://commonmark.org/" rel="nofollow">commonmark</a>!</p>\n", description) } req = NewRequestf(t, "GET", "/non-existent-user.rss") @@ -250,18 +247,18 @@ func TestListStopWatches(t *testing.T) { stopwatch := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: owner.ID}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: stopwatch.IssueID}) if assert.Len(t, apiWatches, 1) { - assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) - assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex) - assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle) - assert.EqualValues(t, repo.Name, apiWatches[0].RepoName) - assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName) + assert.Equal(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) + assert.Equal(t, issue.Index, apiWatches[0].IssueIndex) + assert.Equal(t, issue.Title, apiWatches[0].IssueTitle) + assert.Equal(t, repo.Name, apiWatches[0].RepoName) + assert.Equal(t, repo.OwnerName, apiWatches[0].RepoOwnerName) assert.Positive(t, apiWatches[0].Seconds) } } 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{ @@ -276,5 +273,5 @@ func TestUserLocationMapLink(t *testing.T) { req = NewRequest(t, "GET", "/user2/") resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - htmlDoc.AssertElement(t, `a[href="https://example/foo/A%2Fb"]`, true) + AssertHTMLElement(t, htmlDoc, `a[href="https://example/foo/A%2Fb"]`, true) } diff --git a/tests/integration/view_test.go b/tests/integration/view_test.go index f434446801..9ed3e30857 100644 --- a/tests/integration/view_test.go +++ b/tests/integration/view_test.go @@ -25,3 +25,43 @@ func TestRenderFileSVGIsInImgTag(t *testing.T) { assert.True(t, exists, "The SVG image should be in an <img> tag so that scripts in the SVG are not run") assert.Equal(t, "/user2/repo2/raw/branch/master/line.svg", src) } + +func TestCommitListActions(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + + t.Run("WikiRevisionList", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo1/wiki/Home?action=_revision") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, htmlDoc, ".commit-list .copy-commit-id", true) + AssertHTMLElement(t, htmlDoc, `.commit-list .view-single-diff`, false) + AssertHTMLElement(t, htmlDoc, `.commit-list .view-commit-path`, false) + }) + + t.Run("RepoCommitList", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + AssertHTMLElement(t, htmlDoc, `.commit-list .copy-commit-id`, true) + AssertHTMLElement(t, htmlDoc, `.commit-list .view-single-diff`, false) + AssertHTMLElement(t, htmlDoc, `.commit-list .view-commit-path`, true) + }) + + t.Run("RepoFileHistory", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master/README.md") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + AssertHTMLElement(t, htmlDoc, `.commit-list .copy-commit-id`, true) + AssertHTMLElement(t, htmlDoc, `.commit-list .view-single-diff`, true) + AssertHTMLElement(t, htmlDoc, `.commit-list .view-commit-path`, true) + }) +} diff --git a/tests/integration/webfinger_test.go b/tests/integration/webfinger_test.go index a1abc8d32b..757d442cd2 100644 --- a/tests/integration/webfinger_test.go +++ b/tests/integration/webfinger_test.go @@ -7,11 +7,13 @@ import ( "fmt" "net/http" "net/url" + "strconv" "testing" "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" @@ -19,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}) @@ -52,7 +50,7 @@ func TestWebfinger(t *testing.T) { var jrd webfingerJRD DecodeJSON(t, resp, &jrd) assert.Equal(t, "acct:user2@"+appURL.Host, jrd.Subject) - assert.ElementsMatch(t, []string{user.HTMLURL(), appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(user.ID)}, jrd.Aliases) + assert.ElementsMatch(t, []string{user.HTMLURL(), appURL.String() + "api/v1/activitypub/user-id/" + strconv.FormatInt(user.ID, 10)}, jrd.Aliases) req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", user.LowerName, "unknown.host")) MakeRequest(t, req, http.StatusBadRequest) @@ -63,6 +61,6 @@ func TestWebfinger(t *testing.T) { req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", "user31", appURL.Host)) session.MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=mailto:%s", user.Email)) + req = NewRequest(t, "GET", "/.well-known/webfinger?resource=mailto:"+user.Email) MakeRequest(t, req, http.StatusNotFound) } diff --git a/tests/integration/wiki_test.go b/tests/integration/wiki_test.go index bb73f71a45..ac458af378 100644 --- a/tests/integration/wiki_test.go +++ b/tests/integration/wiki_test.go @@ -4,8 +4,6 @@ package integration import ( - "context" - "fmt" "net/http" "net/url" "os" @@ -30,7 +28,7 @@ func assertFileExist(t *testing.T, p string) { func assertFileEqual(t *testing.T, p string, content []byte) { bs, err := os.ReadFile(p) assert.NoError(t, err) - assert.EqualValues(t, content, bs) + assert.Equal(t, content, bs) } func TestRepoCloneWiki(t *testing.T) { @@ -39,11 +37,11 @@ func TestRepoCloneWiki(t *testing.T) { dstPath := t.TempDir() - r := fmt.Sprintf("%suser2/repo1.wiki.git", u.String()) + r := u.String() + "user2/repo1.wiki.git" u, _ = url.Parse(r) u.User = url.UserPassword("user2", userPassword) t.Run("Clone", func(t *testing.T) { - assert.NoError(t, git.CloneWithArgs(context.Background(), git.AllowLFSFiltersArgs(), u.String(), dstPath, git.CloneRepoOptions{})) + assert.NoError(t, git.CloneWithArgs(t.Context(), git.AllowLFSFiltersArgs(), u.String(), dstPath, git.CloneRepoOptions{})) assertFileEqual(t, filepath.Join(dstPath, "Home.md"), []byte("# Home page\n\nThis is the home page!\n")) assertFileExist(t, filepath.Join(dstPath, "Page-With-Image.md")) assertFileExist(t, filepath.Join(dstPath, "Page-With-Spaced-Name.md")) @@ -70,6 +68,6 @@ func Test_RepoWikiPages(t *testing.T) { href, _ := firstAnchor.Attr("href") pagePath := strings.TrimPrefix(href, "/user2/repo1/wiki/") - assert.EqualValues(t, expectedPagePaths[i], pagePath) + assert.Equal(t, expectedPagePaths[i], pagePath) }) } 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) +} diff --git a/tests/integration/xss_test.go b/tests/integration/xss_test.go index a8eaa5fc62..9058fc210a 100644 --- a/tests/integration/xss_test.go +++ b/tests/integration/xss_test.go @@ -32,8 +32,8 @@ func TestXSSUserFullName(t *testing.T) { req = NewRequestf(t, "GET", "/%s", user.Name) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - assert.EqualValues(t, 0, htmlDoc.doc.Find("script.evil").Length()) - assert.EqualValues(t, fullName, + assert.Equal(t, 0, htmlDoc.doc.Find("script.evil").Length()) + assert.Equal(t, fullName, htmlDoc.doc.Find("div.content").Find(".header.text.center").Text(), ) } |