mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-02 13:44:16 +09:00
Deleting an Actions run (UI, API or `RUN_RETENTION_DAYS`) left its commit statuses linking to the deleted run, so "Details" gave a 404. `DeleteRun` now clears the `target_url` of the run's statuses and of the commit's status summary on the run's commit, in the same transaction. States are kept, so required status checks and merging are unaffected. This differs from GitHub, which deletes the checks together with the run. Fixes https://github.com/go-gitea/gitea/issues/40079 Co-authored-by: silverwind <me@silverwind.io>
161 lines
5.4 KiB
Go
161 lines
5.4 KiB
Go
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
runnerv1 "gitea.dev/actionslib/runner/v1"
|
|
actions_model "gitea.dev/models/actions"
|
|
auth_model "gitea.dev/models/auth"
|
|
"gitea.dev/models/db"
|
|
git_model "gitea.dev/models/git"
|
|
"gitea.dev/models/unittest"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/commitstatus"
|
|
"gitea.dev/modules/json"
|
|
"gitea.dev/routers/web/repo/actions"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
func TestActionsDeleteRun(t *testing.T) {
|
|
now := time.Now()
|
|
outcomes := make(map[string]*mockTaskOutcome)
|
|
for _, jobName := range []string{"job1", "job2", "job3"} {
|
|
outcomes[jobName] = &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: jobName,
|
|
},
|
|
{
|
|
Time: timestamppb.New(now.Add(6 * time.Second)),
|
|
Content: "\U0001F3C1 Job succeeded",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
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: outcomes,
|
|
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)
|
|
|
|
var runID int64
|
|
var commitID string
|
|
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()
|
|
parsedRunIndex, err := strconv.ParseInt(runIndex, 10, 64)
|
|
assert.NoError(t, err)
|
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: apiRepo.ID, Index: parsedRunIndex})
|
|
runID = run.ID
|
|
commitID = run.CommitSHA
|
|
}
|
|
|
|
jobs, err := actions_model.GetLatestAttemptJobsByRepoAndRunID(t.Context(), apiRepo.ID, runID)
|
|
assert.NoError(t, err)
|
|
|
|
for i := 0; i < len(testCase.outcomes); i++ {
|
|
jobID := jobs[i].ID
|
|
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, apiRepo.Name, runID, jobID))
|
|
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/%d/jobs/%d/logs", user2.Name, apiRepo.Name, runID, jobID)).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
}
|
|
|
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, apiRepo.Name, runID))
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/delete", user2.Name, apiRepo.Name, runID))
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
statuses, err := git_model.GetLatestCommitStatus(t.Context(), apiRepo.ID, commitID, db.ListOptionsAll)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, statuses, 3)
|
|
for _, status := range statuses {
|
|
assert.Equal(t, commitstatus.CommitStatusSuccess, status.State)
|
|
assert.Empty(t, status.TargetURL)
|
|
}
|
|
assert.Empty(t, unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatusSummary{RepoID: apiRepo.ID, SHA: commitID}).TargetURL)
|
|
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/delete", user2.Name, apiRepo.Name, runID))
|
|
session.MakeRequest(t, req, http.StatusNotFound)
|
|
|
|
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, apiRepo.Name, runID))
|
|
session.MakeRequest(t, req, http.StatusNotFound)
|
|
|
|
for i := 0; i < len(testCase.outcomes); i++ {
|
|
jobID := jobs[i].ID
|
|
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d", user2.Name, apiRepo.Name, runID, jobID))
|
|
session.MakeRequest(t, req, http.StatusNotFound)
|
|
|
|
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/logs", user2.Name, apiRepo.Name, runID, jobID)).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
})
|
|
}
|