Skip to content

Commit d692bd1

Browse files
Zettat123GiteaBot
authored andcommitted
Fix actions schedule update issue (#35767)
Fix #34472 Add integration tests for actions schedule update.
1 parent 88a8571 commit d692bd1

File tree

2 files changed

+304
-12
lines changed

2 files changed

+304
-12
lines changed

services/actions/notifier_helper.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,12 @@ func notify(ctx context.Context, input *notifyInput) error {
236236
}
237237

238238
if shouldDetectSchedules {
239-
if err := handleSchedules(ctx, schedules, commit, input, ref.String()); err != nil {
239+
if err := handleSchedules(ctx, schedules, commit, input, ref); err != nil {
240240
return err
241241
}
242242
}
243243

244-
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
244+
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
245245
}
246246

247247
func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) bool {
@@ -293,7 +293,7 @@ func handleWorkflows(
293293
detectedWorkflows []*actions_module.DetectedWorkflow,
294294
commit *git.Commit,
295295
input *notifyInput,
296-
ref string,
296+
ref git.RefName,
297297
) error {
298298
if len(detectedWorkflows) == 0 {
299299
log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
@@ -329,7 +329,7 @@ func handleWorkflows(
329329
WorkflowID: dwf.EntryName,
330330
TriggerUserID: input.Doer.ID,
331331
TriggerUser: input.Doer,
332-
Ref: ref,
332+
Ref: ref.String(),
333333
CommitSHA: commit.ID.String(),
334334
IsForkPullRequest: isForkPullRequest,
335335
Event: input.Event,
@@ -500,13 +500,9 @@ func handleSchedules(
500500
detectedWorkflows []*actions_module.DetectedWorkflow,
501501
commit *git.Commit,
502502
input *notifyInput,
503-
ref string,
503+
ref git.RefName,
504504
) error {
505-
branch, err := commit.GetBranchName()
506-
if err != nil {
507-
return err
508-
}
509-
if branch != input.Repo.DefaultBranch {
505+
if ref.BranchName() != input.Repo.DefaultBranch {
510506
log.Trace("commit branch is not default branch in repo")
511507
return nil
512508
}
@@ -552,7 +548,7 @@ func handleSchedules(
552548
WorkflowID: dwf.EntryName,
553549
TriggerUserID: user_model.ActionsUserID,
554550
TriggerUser: user_model.NewActionsUser(),
555-
Ref: ref,
551+
Ref: ref.String(),
556552
CommitSHA: commit.ID.String(),
557553
Event: input.Event,
558554
EventPayload: string(p),
@@ -614,5 +610,5 @@ func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository)
614610
// so we use action user as the Doer of the notifyInput
615611
notifyInput := newNotifyInputForSchedules(repo)
616612

617-
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
613+
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, git.RefNameFromBranch(repo.DefaultBranch))
618614
}
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package integration
5+
6+
import (
7+
"net/url"
8+
"strconv"
9+
"strings"
10+
"testing"
11+
12+
actions_model "code.gitea.io/gitea/models/actions"
13+
auth_model "code.gitea.io/gitea/models/auth"
14+
git_model "code.gitea.io/gitea/models/git"
15+
issues_model "code.gitea.io/gitea/models/issues"
16+
repo_model "code.gitea.io/gitea/models/repo"
17+
unit_model "code.gitea.io/gitea/models/unit"
18+
"code.gitea.io/gitea/models/unittest"
19+
user_model "code.gitea.io/gitea/models/user"
20+
"code.gitea.io/gitea/modules/migration"
21+
api "code.gitea.io/gitea/modules/structs"
22+
"code.gitea.io/gitea/modules/util"
23+
mirror_service "code.gitea.io/gitea/services/mirror"
24+
repo_service "code.gitea.io/gitea/services/repository"
25+
files_service "code.gitea.io/gitea/services/repository/files"
26+
27+
"github.com/stretchr/testify/assert"
28+
)
29+
30+
func TestScheduleUpdate(t *testing.T) {
31+
t.Run("Push", testScheduleUpdatePush)
32+
t.Run("PullMerge", testScheduleUpdatePullMerge)
33+
t.Run("DisableAndEnableActionsUnit", testScheduleUpdateDisableAndEnableActionsUnit)
34+
t.Run("ArchiveAndUnarchive", testScheduleUpdateArchiveAndUnarchive)
35+
t.Run("MirrorSync", testScheduleUpdateMirrorSync)
36+
}
37+
38+
func testScheduleUpdatePush(t *testing.T) {
39+
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
40+
newCron := "30 5 * * 1,3"
41+
pushScheduleChange(t, u, repo, newCron)
42+
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
43+
assert.NoError(t, err)
44+
return branch.CommitID, newCron
45+
})
46+
}
47+
48+
func testScheduleUpdatePullMerge(t *testing.T) {
49+
newBranchName := "feat1"
50+
workflowTreePath := ".gitea/workflows/actions-schedule.yml"
51+
workflowContent := `name: actions-schedule
52+
on:
53+
schedule:
54+
- cron: '@every 2m' # update to 2m
55+
jobs:
56+
job:
57+
runs-on: ubuntu-latest
58+
steps:
59+
- run: echo 'schedule workflow'
60+
`
61+
62+
mergeStyles := []repo_model.MergeStyle{
63+
repo_model.MergeStyleMerge,
64+
repo_model.MergeStyleRebase,
65+
repo_model.MergeStyleRebaseMerge,
66+
repo_model.MergeStyleSquash,
67+
repo_model.MergeStyleFastForwardOnly,
68+
}
69+
70+
for _, mergeStyle := range mergeStyles {
71+
t.Run(string(mergeStyle), func(t *testing.T) {
72+
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
73+
// update workflow file
74+
_, err := files_service.ChangeRepoFiles(t.Context(), repo, user, &files_service.ChangeRepoFilesOptions{
75+
NewBranch: newBranchName,
76+
Files: []*files_service.ChangeRepoFile{
77+
{
78+
Operation: "update",
79+
TreePath: workflowTreePath,
80+
ContentReader: strings.NewReader(workflowContent),
81+
},
82+
},
83+
Message: "update workflow schedule",
84+
})
85+
assert.NoError(t, err)
86+
87+
// create pull request
88+
apiPull, err := doAPICreatePullRequest(testContext, repo.OwnerName, repo.Name, repo.DefaultBranch, newBranchName)(t)
89+
assert.NoError(t, err)
90+
91+
// merge pull request
92+
testPullMerge(t, testContext.Session, repo.OwnerName, repo.Name, strconv.FormatInt(apiPull.Index, 10), MergeOptions{
93+
Style: mergeStyle,
94+
})
95+
96+
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
97+
return pull.MergedCommitID, "@every 2m"
98+
})
99+
})
100+
}
101+
102+
t.Run(string(repo_model.MergeStyleManuallyMerged), func(t *testing.T) {
103+
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
104+
// enable manual-merge
105+
doAPIEditRepository(testContext, &api.EditRepoOption{
106+
HasPullRequests: util.ToPointer(true),
107+
AllowManualMerge: util.ToPointer(true),
108+
})(t)
109+
110+
// update workflow file
111+
fileResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user, &files_service.ChangeRepoFilesOptions{
112+
NewBranch: newBranchName,
113+
Files: []*files_service.ChangeRepoFile{
114+
{
115+
Operation: "update",
116+
TreePath: workflowTreePath,
117+
ContentReader: strings.NewReader(workflowContent),
118+
},
119+
},
120+
Message: "update workflow schedule",
121+
})
122+
assert.NoError(t, err)
123+
124+
// merge and push
125+
dstPath := t.TempDir()
126+
u.Path = repo.FullName() + ".git"
127+
u.User = url.UserPassword(repo.OwnerName, userPassword)
128+
doGitClone(dstPath, u)(t)
129+
doGitMerge(dstPath, "origin/"+newBranchName)(t)
130+
doGitPushTestRepository(dstPath, "origin", repo.DefaultBranch)(t)
131+
132+
// create pull request
133+
apiPull, err := doAPICreatePullRequest(testContext, repo.OwnerName, repo.Name, repo.DefaultBranch, newBranchName)(t)
134+
assert.NoError(t, err)
135+
136+
// merge pull request manually
137+
doAPIManuallyMergePullRequest(testContext, repo.OwnerName, repo.Name, fileResp.Commit.SHA, apiPull.Index)(t)
138+
139+
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
140+
assert.Equal(t, issues_model.PullRequestStatusManuallyMerged, pull.Status)
141+
return pull.MergedCommitID, "@every 2m"
142+
})
143+
})
144+
}
145+
146+
func testScheduleUpdateMirrorSync(t *testing.T) {
147+
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
148+
// create mirror repo
149+
opts := migration.MigrateOptions{
150+
RepoName: "actions-schedule-mirror",
151+
Description: "Test mirror for actions-schedule",
152+
Private: false,
153+
Mirror: true,
154+
CloneAddr: repo.CloneLinkGeneral(t.Context()).HTTPS,
155+
}
156+
mirrorRepo, err := repo_service.CreateRepositoryDirectly(t.Context(), user, user, repo_service.CreateRepoOptions{
157+
Name: opts.RepoName,
158+
Description: opts.Description,
159+
IsPrivate: opts.Private,
160+
IsMirror: opts.Mirror,
161+
DefaultBranch: repo.DefaultBranch,
162+
Status: repo_model.RepositoryBeingMigrated,
163+
}, false)
164+
assert.NoError(t, err)
165+
assert.True(t, mirrorRepo.IsMirror)
166+
mirrorRepo, err = repo_service.MigrateRepositoryGitData(t.Context(), user, mirrorRepo, opts, nil)
167+
assert.NoError(t, err)
168+
mirrorContext := NewAPITestContext(t, user.Name, mirrorRepo.Name, auth_model.AccessTokenScopeWriteRepository)
169+
170+
// enable actions unit for mirror repo
171+
assert.False(t, mirrorRepo.UnitEnabled(t.Context(), unit_model.TypeActions))
172+
doAPIEditRepository(mirrorContext, &api.EditRepoOption{
173+
HasActions: util.ToPointer(true),
174+
})(t)
175+
actionSchedule := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: mirrorRepo.ID})
176+
scheduleSpec := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: mirrorRepo.ID, ScheduleID: actionSchedule.ID})
177+
assert.Equal(t, "@every 1m", scheduleSpec.Spec)
178+
179+
// update remote repo
180+
newCron := "30 5,17 * * 2,4"
181+
pushScheduleChange(t, u, repo, newCron)
182+
repoDefaultBranch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
183+
assert.NoError(t, err)
184+
185+
// sync
186+
ok := mirror_service.SyncPullMirror(t.Context(), mirrorRepo.ID)
187+
assert.True(t, ok)
188+
mirrorRepoDefaultBranch, err := git_model.GetBranch(t.Context(), mirrorRepo.ID, mirrorRepo.DefaultBranch)
189+
assert.NoError(t, err)
190+
assert.Equal(t, repoDefaultBranch.CommitID, mirrorRepoDefaultBranch.CommitID)
191+
192+
// check updated schedule
193+
actionSchedule = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: mirrorRepo.ID})
194+
scheduleSpec = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: mirrorRepo.ID, ScheduleID: actionSchedule.ID})
195+
assert.Equal(t, newCron, scheduleSpec.Spec)
196+
197+
return repoDefaultBranch.CommitID, newCron
198+
})
199+
}
200+
201+
func testScheduleUpdateArchiveAndUnarchive(t *testing.T) {
202+
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
203+
doAPIEditRepository(testContext, &api.EditRepoOption{
204+
Archived: util.ToPointer(true),
205+
})(t)
206+
assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
207+
doAPIEditRepository(testContext, &api.EditRepoOption{
208+
Archived: util.ToPointer(false),
209+
})(t)
210+
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
211+
assert.NoError(t, err)
212+
return branch.CommitID, "@every 1m"
213+
})
214+
}
215+
216+
func testScheduleUpdateDisableAndEnableActionsUnit(t *testing.T) {
217+
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
218+
doAPIEditRepository(testContext, &api.EditRepoOption{
219+
HasActions: util.ToPointer(false),
220+
})(t)
221+
assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
222+
doAPIEditRepository(testContext, &api.EditRepoOption{
223+
HasActions: util.ToPointer(true),
224+
})(t)
225+
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
226+
assert.NoError(t, err)
227+
return branch.CommitID, "@every 1m"
228+
})
229+
}
230+
231+
type scheduleUpdateTrigger func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string)
232+
233+
func doTestScheduleUpdate(t *testing.T, updateTrigger scheduleUpdateTrigger) {
234+
onGiteaRun(t, func(t *testing.T, u *url.URL) {
235+
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
236+
session := loginUser(t, user2.Name)
237+
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
238+
239+
apiRepo := createActionsTestRepo(t, token, "actions-schedule", false)
240+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
241+
assert.NoError(t, repo.LoadAttributes(t.Context()))
242+
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
243+
defer doAPIDeleteRepository(httpContext)(t)
244+
245+
wfTreePath := ".gitea/workflows/actions-schedule.yml"
246+
wfFileContent := `name: actions-schedule
247+
on:
248+
schedule:
249+
- cron: '@every 1m'
250+
jobs:
251+
job:
252+
runs-on: ubuntu-latest
253+
steps:
254+
- run: echo 'schedule workflow'
255+
`
256+
257+
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wfTreePath, wfFileContent)
258+
apiFileResp := createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts1)
259+
260+
actionSchedule := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: repo.ID, CommitSHA: apiFileResp.Commit.SHA})
261+
scheduleSpec := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: repo.ID, ScheduleID: actionSchedule.ID})
262+
assert.Equal(t, "@every 1m", scheduleSpec.Spec)
263+
264+
commitID, expectedSpec := updateTrigger(t, u, httpContext, user2, repo)
265+
266+
actionSchedule = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: repo.ID, CommitSHA: commitID})
267+
scheduleSpec = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: repo.ID, ScheduleID: actionSchedule.ID})
268+
assert.Equal(t, expectedSpec, scheduleSpec.Spec)
269+
})
270+
}
271+
272+
func pushScheduleChange(t *testing.T, u *url.URL, repo *repo_model.Repository, newCron string) {
273+
workflowTreePath := ".gitea/workflows/actions-schedule.yml"
274+
workflowContent := `name: actions-schedule
275+
on:
276+
schedule:
277+
- cron: '` + newCron + `'
278+
jobs:
279+
job:
280+
runs-on: ubuntu-latest
281+
steps:
282+
- run: echo 'schedule workflow'
283+
`
284+
285+
dstPath := t.TempDir()
286+
u.Path = repo.FullName() + ".git"
287+
u.User = url.UserPassword(repo.OwnerName, userPassword)
288+
doGitClone(dstPath, u)(t)
289+
doGitCheckoutWriteFileCommit(localGitAddCommitOptions{
290+
LocalRepoPath: dstPath,
291+
CheckoutBranch: repo.DefaultBranch,
292+
TreeFilePath: workflowTreePath,
293+
TreeFileContent: workflowContent,
294+
})(t)
295+
doGitPushTestRepository(dstPath, "origin", repo.DefaultBranch)(t)
296+
}

0 commit comments

Comments
 (0)