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