|
| 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 | +funcTestScheduleUpdate(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 | +functestScheduleUpdatePush(t*testing.T) { |
| 39 | +doTestScheduleUpdate(t,func(t*testing.T,u*url.URL,testContextAPITestContext,user*user_model.User,repo*repo_model.Repository) (commitID,expectedSpecstring) { |
| 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 | +returnbranch.CommitID,newCron |
| 45 | +}) |
| 46 | +} |
| 47 | + |
| 48 | +functestScheduleUpdatePullMerge(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:=rangemergeStyles { |
| 71 | +t.Run(string(mergeStyle),func(t*testing.T) { |
| 72 | +doTestScheduleUpdate(t,func(t*testing.T,u*url.URL,testContextAPITestContext,user*user_model.User,repo*repo_model.Repository) (commitID,expectedSpecstring) { |
| 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),mergeStyle,false) |
| 93 | + |
| 94 | +pull:=unittest.AssertExistsAndLoadBean(t,&issues_model.PullRequest{ID:apiPull.ID}) |
| 95 | +returnpull.MergedCommitID,"@every 2m" |
| 96 | +}) |
| 97 | +}) |
| 98 | +} |
| 99 | + |
| 100 | +t.Run(string(repo_model.MergeStyleManuallyMerged),func(t*testing.T) { |
| 101 | +doTestScheduleUpdate(t,func(t*testing.T,u*url.URL,testContextAPITestContext,user*user_model.User,repo*repo_model.Repository) (commitID,expectedSpecstring) { |
| 102 | +// enable manual-merge |
| 103 | +doAPIEditRepository(testContext,&api.EditRepoOption{ |
| 104 | +HasPullRequests:util.ToPointer(true), |
| 105 | +AllowManualMerge:util.ToPointer(true), |
| 106 | +})(t) |
| 107 | + |
| 108 | +// update workflow file |
| 109 | +fileResp,err:=files_service.ChangeRepoFiles(t.Context(),repo,user,&files_service.ChangeRepoFilesOptions{ |
| 110 | +NewBranch:newBranchName, |
| 111 | +Files: []*files_service.ChangeRepoFile{ |
| 112 | +{ |
| 113 | +Operation:"update", |
| 114 | +TreePath:workflowTreePath, |
| 115 | +ContentReader:strings.NewReader(workflowContent), |
| 116 | +}, |
| 117 | +}, |
| 118 | +Message:"update workflow schedule", |
| 119 | +}) |
| 120 | +assert.NoError(t,err) |
| 121 | + |
| 122 | +// merge and push |
| 123 | +dstPath:=t.TempDir() |
| 124 | +u.Path=repo.FullName()+".git" |
| 125 | +u.User=url.UserPassword(repo.OwnerName,userPassword) |
| 126 | +doGitClone(dstPath,u)(t) |
| 127 | +doGitMerge(dstPath,"origin/"+newBranchName)(t) |
| 128 | +doGitPushTestRepository(dstPath,"origin",repo.DefaultBranch)(t) |
| 129 | + |
| 130 | +// create pull request |
| 131 | +apiPull,err:=doAPICreatePullRequest(testContext,repo.OwnerName,repo.Name,repo.DefaultBranch,newBranchName)(t) |
| 132 | +assert.NoError(t,err) |
| 133 | + |
| 134 | +// merge pull request manually |
| 135 | +doAPIManuallyMergePullRequest(testContext,repo.OwnerName,repo.Name,fileResp.Commit.SHA,apiPull.Index)(t) |
| 136 | + |
| 137 | +pull:=unittest.AssertExistsAndLoadBean(t,&issues_model.PullRequest{ID:apiPull.ID}) |
| 138 | +assert.Equal(t,issues_model.PullRequestStatusManuallyMerged,pull.Status) |
| 139 | +returnpull.MergedCommitID,"@every 2m" |
| 140 | +}) |
| 141 | +}) |
| 142 | +} |
| 143 | + |
| 144 | +functestScheduleUpdateMirrorSync(t*testing.T) { |
| 145 | +doTestScheduleUpdate(t,func(t*testing.T,u*url.URL,testContextAPITestContext,user*user_model.User,repo*repo_model.Repository) (commitID,expectedSpecstring) { |
| 146 | +// create mirror repo |
| 147 | +opts:= migration.MigrateOptions{ |
| 148 | +RepoName:"actions-schedule-mirror", |
| 149 | +Description:"Test mirror for actions-schedule", |
| 150 | +Private:false, |
| 151 | +Mirror:true, |
| 152 | +CloneAddr:repo.CloneLinkGeneral(t.Context()).HTTPS, |
| 153 | +} |
| 154 | +mirrorRepo,err:=repo_service.CreateRepositoryDirectly(t.Context(),user,user, repo_service.CreateRepoOptions{ |
| 155 | +Name:opts.RepoName, |
| 156 | +Description:opts.Description, |
| 157 | +IsPrivate:opts.Private, |
| 158 | +IsMirror:opts.Mirror, |
| 159 | +DefaultBranch:repo.DefaultBranch, |
| 160 | +Status:repo_model.RepositoryBeingMigrated, |
| 161 | +},false) |
| 162 | +assert.NoError(t,err) |
| 163 | +assert.True(t,mirrorRepo.IsMirror) |
| 164 | +mirrorRepo,err=repo_service.MigrateRepositoryGitData(t.Context(),user,mirrorRepo,opts,nil) |
| 165 | +assert.NoError(t,err) |
| 166 | +mirrorContext:=NewAPITestContext(t,user.Name,mirrorRepo.Name,auth_model.AccessTokenScopeWriteRepository) |
| 167 | + |
| 168 | +// enable actions unit for mirror repo |
| 169 | +assert.False(t,mirrorRepo.UnitEnabled(t.Context(),unit_model.TypeActions)) |
| 170 | +doAPIEditRepository(mirrorContext,&api.EditRepoOption{ |
| 171 | +HasActions:util.ToPointer(true), |
| 172 | +})(t) |
| 173 | +actionSchedule:=unittest.AssertExistsAndLoadBean(t,&actions_model.ActionSchedule{RepoID:mirrorRepo.ID}) |
| 174 | +scheduleSpec:=unittest.AssertExistsAndLoadBean(t,&actions_model.ActionScheduleSpec{RepoID:mirrorRepo.ID,ScheduleID:actionSchedule.ID}) |
| 175 | +assert.Equal(t,"@every 1m",scheduleSpec.Spec) |
| 176 | + |
| 177 | +// update remote repo |
| 178 | +newCron:="30 5,17 * * 2,4" |
| 179 | +pushScheduleChange(t,u,repo,newCron) |
| 180 | +repoDefaultBranch,err:=git_model.GetBranch(t.Context(),repo.ID,repo.DefaultBranch) |
| 181 | +assert.NoError(t,err) |
| 182 | + |
| 183 | +// sync |
| 184 | +ok:=mirror_service.SyncPullMirror(t.Context(),mirrorRepo.ID) |
| 185 | +assert.True(t,ok) |
| 186 | +mirrorRepoDefaultBranch,err:=git_model.GetBranch(t.Context(),mirrorRepo.ID,mirrorRepo.DefaultBranch) |
| 187 | +assert.NoError(t,err) |
| 188 | +assert.Equal(t,repoDefaultBranch.CommitID,mirrorRepoDefaultBranch.CommitID) |
| 189 | + |
| 190 | +// check updated schedule |
| 191 | +actionSchedule=unittest.AssertExistsAndLoadBean(t,&actions_model.ActionSchedule{RepoID:mirrorRepo.ID}) |
| 192 | +scheduleSpec=unittest.AssertExistsAndLoadBean(t,&actions_model.ActionScheduleSpec{RepoID:mirrorRepo.ID,ScheduleID:actionSchedule.ID}) |
| 193 | +assert.Equal(t,newCron,scheduleSpec.Spec) |
| 194 | + |
| 195 | +returnrepoDefaultBranch.CommitID,newCron |
| 196 | +}) |
| 197 | +} |
| 198 | + |
| 199 | +functestScheduleUpdateArchiveAndUnarchive(t*testing.T) { |
| 200 | +doTestScheduleUpdate(t,func(t*testing.T,u*url.URL,testContextAPITestContext,user*user_model.User,repo*repo_model.Repository) (commitID,expectedSpecstring) { |
| 201 | +doAPIEditRepository(testContext,&api.EditRepoOption{ |
| 202 | +Archived:util.ToPointer(true), |
| 203 | +})(t) |
| 204 | +assert.Zero(t,unittest.GetCount(t,&actions_model.ActionSchedule{RepoID:repo.ID})) |
| 205 | +doAPIEditRepository(testContext,&api.EditRepoOption{ |
| 206 | +Archived:util.ToPointer(false), |
| 207 | +})(t) |
| 208 | +branch,err:=git_model.GetBranch(t.Context(),repo.ID,repo.DefaultBranch) |
| 209 | +assert.NoError(t,err) |
| 210 | +returnbranch.CommitID,"@every 1m" |
| 211 | +}) |
| 212 | +} |
| 213 | + |
| 214 | +functestScheduleUpdateDisableAndEnableActionsUnit(t*testing.T) { |
| 215 | +doTestScheduleUpdate(t,func(t*testing.T,u*url.URL,testContextAPITestContext,user*user_model.User,repo*repo_model.Repository) (commitID,expectedSpecstring) { |
| 216 | +doAPIEditRepository(testContext,&api.EditRepoOption{ |
| 217 | +HasActions:util.ToPointer(false), |
| 218 | +})(t) |
| 219 | +assert.Zero(t,unittest.GetCount(t,&actions_model.ActionSchedule{RepoID:repo.ID})) |
| 220 | +doAPIEditRepository(testContext,&api.EditRepoOption{ |
| 221 | +HasActions:util.ToPointer(true), |
| 222 | +})(t) |
| 223 | +branch,err:=git_model.GetBranch(t.Context(),repo.ID,repo.DefaultBranch) |
| 224 | +assert.NoError(t,err) |
| 225 | +returnbranch.CommitID,"@every 1m" |
| 226 | +}) |
| 227 | +} |
| 228 | + |
| 229 | +typescheduleUpdateTriggerfunc(t*testing.T,u*url.URL,testContextAPITestContext,user*user_model.User,repo*repo_model.Repository) (commitID,expectedSpecstring) |
| 230 | + |
| 231 | +funcdoTestScheduleUpdate(t*testing.T,updateTriggerscheduleUpdateTrigger) { |
| 232 | +onGiteaRun(t,func(t*testing.T,u*url.URL) { |
| 233 | +user2:=unittest.AssertExistsAndLoadBean(t,&user_model.User{ID:2}) |
| 234 | +session:=loginUser(t,user2.Name) |
| 235 | +token:=getTokenForLoggedInUser(t,session,auth_model.AccessTokenScopeWriteRepository,auth_model.AccessTokenScopeWriteUser) |
| 236 | + |
| 237 | +apiRepo:=createActionsTestRepo(t,token,"actions-schedule",false) |
| 238 | +repo:=unittest.AssertExistsAndLoadBean(t,&repo_model.Repository{ID:apiRepo.ID}) |
| 239 | +assert.NoError(t,repo.LoadAttributes(t.Context())) |
| 240 | +httpContext:=NewAPITestContext(t,user2.Name,repo.Name,auth_model.AccessTokenScopeWriteRepository) |
| 241 | +deferdoAPIDeleteRepository(httpContext)(t) |
| 242 | + |
| 243 | +wfTreePath:=".gitea/workflows/actions-schedule.yml" |
| 244 | +wfFileContent:=`name: actions-schedule |
| 245 | +on: |
| 246 | + schedule: |
| 247 | + - cron: '@every 1m' |
| 248 | +jobs: |
| 249 | + job: |
| 250 | + runs-on: ubuntu-latest |
| 251 | + steps: |
| 252 | + - run: echo 'schedule workflow' |
| 253 | +` |
| 254 | + |
| 255 | +opts1:=getWorkflowCreateFileOptions(user2,repo.DefaultBranch,"create "+wfTreePath,wfFileContent) |
| 256 | +apiFileResp:=createWorkflowFile(t,token,user2.Name,repo.Name,wfTreePath,opts1) |
| 257 | + |
| 258 | +actionSchedule:=unittest.AssertExistsAndLoadBean(t,&actions_model.ActionSchedule{RepoID:repo.ID,CommitSHA:apiFileResp.Commit.SHA}) |
| 259 | +scheduleSpec:=unittest.AssertExistsAndLoadBean(t,&actions_model.ActionScheduleSpec{RepoID:repo.ID,ScheduleID:actionSchedule.ID}) |
| 260 | +assert.Equal(t,"@every 1m",scheduleSpec.Spec) |
| 261 | + |
| 262 | +commitID,expectedSpec:=updateTrigger(t,u,httpContext,user2,repo) |
| 263 | + |
| 264 | +actionSchedule=unittest.AssertExistsAndLoadBean(t,&actions_model.ActionSchedule{RepoID:repo.ID,CommitSHA:commitID}) |
| 265 | +scheduleSpec=unittest.AssertExistsAndLoadBean(t,&actions_model.ActionScheduleSpec{RepoID:repo.ID,ScheduleID:actionSchedule.ID}) |
| 266 | +assert.Equal(t,expectedSpec,scheduleSpec.Spec) |
| 267 | +}) |
| 268 | +} |
| 269 | + |
| 270 | +funcpushScheduleChange(t*testing.T,u*url.URL,repo*repo_model.Repository,newCronstring) { |
| 271 | +workflowTreePath:=".gitea/workflows/actions-schedule.yml" |
| 272 | +workflowContent:=`name: actions-schedule |
| 273 | +on: |
| 274 | + schedule: |
| 275 | + - cron: '`+newCron+`' |
| 276 | +jobs: |
| 277 | + job: |
| 278 | + runs-on: ubuntu-latest |
| 279 | + steps: |
| 280 | + - run: echo 'schedule workflow' |
| 281 | +` |
| 282 | + |
| 283 | +dstPath:=t.TempDir() |
| 284 | +u.Path=repo.FullName()+".git" |
| 285 | +u.User=url.UserPassword(repo.OwnerName,userPassword) |
| 286 | +doGitClone(dstPath,u)(t) |
| 287 | +doGitCheckoutWriteFileCommit(localGitAddCommitOptions{ |
| 288 | +LocalRepoPath:dstPath, |
| 289 | +CheckoutBranch:repo.DefaultBranch, |
| 290 | +TreeFilePath:workflowTreePath, |
| 291 | +TreeFileContent:workflowContent, |
| 292 | +})(t) |
| 293 | +doGitPushTestRepository(dstPath,"origin",repo.DefaultBranch)(t) |
| 294 | +} |