mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00:00 
			
		
		
		
	Implements - https://docs.github.com/en/rest/actions/workflow-jobs?apiVersion=2022-11-28#list-jobs-for-a-workflow-run--code-samples - https://docs.github.com/en/rest/actions/workflow-jobs?apiVersion=2022-11-28#get-a-job-for-a-workflow-run--code-samples - https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository - https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#get-a-workflow-run - `/actions/runs` for global + user + org (Gitea only) - `/actions/jobs` for global + user + org + repository (Gitea only) - workflow_run webhook + action trigger - limitations - workflow id is assigned to a string, this may result into problems in strongly typed clients Fixes - workflow_job webhook url to no longer contain the `runs/<run>` part to align with api - workflow instance does now use it's name inside the file instead of filename if set Refactoring - Moved a lot of logic from workflows/workflow_job into a shared module used by both webhook and api TODO - [x] Verify Keda Compatibility - [x] Edit Webhook API bug is resolved Closes https://github.com/go-gitea/gitea/issues/23670 Closes https://github.com/go-gitea/gitea/issues/23796 Closes https://github.com/go-gitea/gitea/issues/24898 Replaces https://github.com/go-gitea/gitea/pull/28047 and is much more complete --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			168 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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)
 | |
| }
 |