|  | 
|  | 1 | +// Copyright 2025 The Gitea Authors. All rights reserved. | 
|  | 2 | +// SPDX-License-Identifier: MIT | 
|  | 3 | + | 
|  | 4 | +package integration | 
|  | 5 | + | 
|  | 6 | +import ( | 
|  | 7 | +	"fmt" | 
|  | 8 | +	"net/http" | 
|  | 9 | +	"net/url" | 
|  | 10 | +	"testing" | 
|  | 11 | + | 
|  | 12 | +	auth_model "code.gitea.io/gitea/models/auth" | 
|  | 13 | +	repo_model "code.gitea.io/gitea/models/repo" | 
|  | 14 | +	"code.gitea.io/gitea/models/unittest" | 
|  | 15 | +	user_model "code.gitea.io/gitea/models/user" | 
|  | 16 | + | 
|  | 17 | +	runnerv1 "code.gitea.io/actions-proto-go/runner/v1" | 
|  | 18 | +) | 
|  | 19 | + | 
|  | 20 | +func TestActionsRerun(t *testing.T) { | 
|  | 21 | +	onGiteaRun(t, func(t *testing.T, u *url.URL) { | 
|  | 22 | +		user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | 
|  | 23 | +		session := loginUser(t, user2.Name) | 
|  | 24 | +		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) | 
|  | 25 | + | 
|  | 26 | +		apiRepo := createActionsTestRepo(t, token, "actions-rerun", false) | 
|  | 27 | +		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) | 
|  | 28 | +		httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) | 
|  | 29 | +		defer doAPIDeleteRepository(httpContext)(t) | 
|  | 30 | + | 
|  | 31 | +		runner := newMockRunner() | 
|  | 32 | +		runner.registerAsRepoRunner(t, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) | 
|  | 33 | + | 
|  | 34 | +		wfTreePath := ".gitea/workflows/actions-rerun-workflow-1.yml" | 
|  | 35 | +		wfFileContent := `name: actions-rerun-workflow-1 | 
|  | 36 | +on:  | 
|  | 37 | +  push: | 
|  | 38 | +    paths: | 
|  | 39 | +      - '.gitea/workflows/actions-rerun-workflow-1.yml' | 
|  | 40 | +jobs: | 
|  | 41 | +  job1: | 
|  | 42 | +    runs-on: ubuntu-latest | 
|  | 43 | +    steps: | 
|  | 44 | +      - run: echo 'job1' | 
|  | 45 | +  job2: | 
|  | 46 | +    runs-on: ubuntu-latest | 
|  | 47 | +    needs: [job1] | 
|  | 48 | +    steps: | 
|  | 49 | +      - run: echo 'job2' | 
|  | 50 | +` | 
|  | 51 | + | 
|  | 52 | +		opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create"+wfTreePath, wfFileContent) | 
|  | 53 | +		createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts) | 
|  | 54 | + | 
|  | 55 | +		// fetch and exec job1 | 
|  | 56 | +		job1Task := runner.fetchTask(t) | 
|  | 57 | +		_, _, run := getTaskAndJobAndRunByTaskID(t, job1Task.Id) | 
|  | 58 | +		runner.execTask(t, job1Task, &mockTaskOutcome{ | 
|  | 59 | +			result: runnerv1.Result_RESULT_SUCCESS, | 
|  | 60 | +		}) | 
|  | 61 | +		// RERUN-FAILURE: the run is not done | 
|  | 62 | +		req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.Index), map[string]string{ | 
|  | 63 | +			"_csrf": GetUserCSRFToken(t, session), | 
|  | 64 | +		}) | 
|  | 65 | +		session.MakeRequest(t, req, http.StatusBadRequest) | 
|  | 66 | +		// fetch and exec job2 | 
|  | 67 | +		job2Task := runner.fetchTask(t) | 
|  | 68 | +		runner.execTask(t, job2Task, &mockTaskOutcome{ | 
|  | 69 | +			result: runnerv1.Result_RESULT_SUCCESS, | 
|  | 70 | +		}) | 
|  | 71 | + | 
|  | 72 | +		// RERUN-1: rerun the run | 
|  | 73 | +		req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.Index), map[string]string{ | 
|  | 74 | +			"_csrf": GetUserCSRFToken(t, session), | 
|  | 75 | +		}) | 
|  | 76 | +		session.MakeRequest(t, req, http.StatusOK) | 
|  | 77 | +		// fetch and exec job1 | 
|  | 78 | +		job1TaskR1 := runner.fetchTask(t) | 
|  | 79 | +		runner.execTask(t, job1TaskR1, &mockTaskOutcome{ | 
|  | 80 | +			result: runnerv1.Result_RESULT_SUCCESS, | 
|  | 81 | +		}) | 
|  | 82 | +		// fetch and exec job2 | 
|  | 83 | +		job2TaskR1 := runner.fetchTask(t) | 
|  | 84 | +		runner.execTask(t, job2TaskR1, &mockTaskOutcome{ | 
|  | 85 | +			result: runnerv1.Result_RESULT_SUCCESS, | 
|  | 86 | +		}) | 
|  | 87 | + | 
|  | 88 | +		// RERUN-2: rerun job1 | 
|  | 89 | +		req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.Index, 0), map[string]string{ | 
|  | 90 | +			"_csrf": GetUserCSRFToken(t, session), | 
|  | 91 | +		}) | 
|  | 92 | +		session.MakeRequest(t, req, http.StatusOK) | 
|  | 93 | +		// job2 needs job1, so rerunning job1 will also rerun job2 | 
|  | 94 | +		// fetch and exec job1 | 
|  | 95 | +		job1TaskR2 := runner.fetchTask(t) | 
|  | 96 | +		runner.execTask(t, job1TaskR2, &mockTaskOutcome{ | 
|  | 97 | +			result: runnerv1.Result_RESULT_SUCCESS, | 
|  | 98 | +		}) | 
|  | 99 | +		// fetch and exec job2 | 
|  | 100 | +		job2TaskR2 := runner.fetchTask(t) | 
|  | 101 | +		runner.execTask(t, job2TaskR2, &mockTaskOutcome{ | 
|  | 102 | +			result: runnerv1.Result_RESULT_SUCCESS, | 
|  | 103 | +		}) | 
|  | 104 | + | 
|  | 105 | +		// RERUN-3: rerun job2 | 
|  | 106 | +		req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.Index, 1), map[string]string{ | 
|  | 107 | +			"_csrf": GetUserCSRFToken(t, session), | 
|  | 108 | +		}) | 
|  | 109 | +		session.MakeRequest(t, req, http.StatusOK) | 
|  | 110 | +		// only job2 will rerun | 
|  | 111 | +		// fetch and exec job2 | 
|  | 112 | +		job2TaskR3 := runner.fetchTask(t) | 
|  | 113 | +		runner.execTask(t, job2TaskR3, &mockTaskOutcome{ | 
|  | 114 | +			result: runnerv1.Result_RESULT_SUCCESS, | 
|  | 115 | +		}) | 
|  | 116 | +		runner.fetchNoTask(t) | 
|  | 117 | +	}) | 
|  | 118 | +} | 
0 commit comments