Skip to content

Commit 532a7b2

Browse files
Zettat123Mithilesh Gupta
authored andcommitted
Fix actions schedule update issue (go-gitea#35767)
Fix go-gitea#34472 Add integration tests for actions schedule update.
1 parent a1e4d7b commit 532a7b2

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
@@ -234,12 +234,12 @@ func notify(ctx context.Context, input *notifyInput) error {
234234
}
235235

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

242-
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
242+
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
243243
}
244244

245245
func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) bool {
@@ -291,7 +291,7 @@ func handleWorkflows(
291291
detectedWorkflows []*actions_module.DetectedWorkflow,
292292
commit *git.Commit,
293293
input *notifyInput,
294-
ref string,
294+
ref git.RefName,
295295
) error {
296296
if len(detectedWorkflows) == 0 {
297297
log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RelativePath(), commit.ID)
@@ -327,7 +327,7 @@ func handleWorkflows(
327327
WorkflowID: dwf.EntryName,
328328
TriggerUserID: input.Doer.ID,
329329
TriggerUser: input.Doer,
330-
Ref: ref,
330+
Ref: ref.String(),
331331
CommitSHA: commit.ID.String(),
332332
IsForkPullRequest: isForkPullRequest,
333333
Event: input.Event,
@@ -442,13 +442,9 @@ func handleSchedules(
442442
detectedWorkflows []*actions_module.DetectedWorkflow,
443443
commit *git.Commit,
444444
input *notifyInput,
445-
ref string,
445+
ref git.RefName,
446446
) error {
447-
branch, err := commit.GetBranchName()
448-
if err != nil {
449-
return err
450-
}
451-
if branch != input.Repo.DefaultBranch {
447+
if ref.BranchName() != input.Repo.DefaultBranch {
452448
log.Trace("commit branch is not default branch in repo")
453449
return nil
454450
}
@@ -494,7 +490,7 @@ func handleSchedules(
494490
WorkflowID: dwf.EntryName,
495491
TriggerUserID: user_model.ActionsUserID,
496492
TriggerUser: user_model.NewActionsUser(),
497-
Ref: ref,
493+
Ref: ref.String(),
498494
CommitSHA: commit.ID.String(),
499495
Event: input.Event,
500496
EventPayload: string(p),
@@ -538,5 +534,5 @@ func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository)
538534
// so we use action user as the Doer of the notifyInput
539535
notifyInput := newNotifyInputForSchedules(repo)
540536

541-
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
537+
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, git.RefNameFromBranch(repo.DefaultBranch))
542538
}
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)