mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 04:14:01 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			297 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			297 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package integration
 | 
						|
 | 
						|
import (
 | 
						|
	"net/url"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	actions_model "code.gitea.io/gitea/models/actions"
 | 
						|
	auth_model "code.gitea.io/gitea/models/auth"
 | 
						|
	git_model "code.gitea.io/gitea/models/git"
 | 
						|
	issues_model "code.gitea.io/gitea/models/issues"
 | 
						|
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						|
	unit_model "code.gitea.io/gitea/models/unit"
 | 
						|
	"code.gitea.io/gitea/models/unittest"
 | 
						|
	user_model "code.gitea.io/gitea/models/user"
 | 
						|
	"code.gitea.io/gitea/modules/migration"
 | 
						|
	api "code.gitea.io/gitea/modules/structs"
 | 
						|
	"code.gitea.io/gitea/modules/util"
 | 
						|
	mirror_service "code.gitea.io/gitea/services/mirror"
 | 
						|
	repo_service "code.gitea.io/gitea/services/repository"
 | 
						|
	files_service "code.gitea.io/gitea/services/repository/files"
 | 
						|
 | 
						|
	"github.com/stretchr/testify/assert"
 | 
						|
)
 | 
						|
 | 
						|
func TestScheduleUpdate(t *testing.T) {
 | 
						|
	t.Run("Push", testScheduleUpdatePush)
 | 
						|
	t.Run("PullMerge", testScheduleUpdatePullMerge)
 | 
						|
	t.Run("DisableAndEnableActionsUnit", testScheduleUpdateDisableAndEnableActionsUnit)
 | 
						|
	t.Run("ArchiveAndUnarchive", testScheduleUpdateArchiveAndUnarchive)
 | 
						|
	t.Run("MirrorSync", testScheduleUpdateMirrorSync)
 | 
						|
}
 | 
						|
 | 
						|
func testScheduleUpdatePush(t *testing.T) {
 | 
						|
	doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
 | 
						|
		newCron := "30 5 * * 1,3"
 | 
						|
		pushScheduleChange(t, u, repo, newCron)
 | 
						|
		branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		return branch.CommitID, newCron
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func testScheduleUpdatePullMerge(t *testing.T) {
 | 
						|
	newBranchName := "feat1"
 | 
						|
	workflowTreePath := ".gitea/workflows/actions-schedule.yml"
 | 
						|
	workflowContent := `name: actions-schedule
 | 
						|
on:
 | 
						|
  schedule:
 | 
						|
    - cron:  '@every 2m' # update to 2m
 | 
						|
jobs:
 | 
						|
  job:
 | 
						|
    runs-on: ubuntu-latest
 | 
						|
    steps:
 | 
						|
      - run: echo 'schedule workflow'
 | 
						|
`
 | 
						|
 | 
						|
	mergeStyles := []repo_model.MergeStyle{
 | 
						|
		repo_model.MergeStyleMerge,
 | 
						|
		repo_model.MergeStyleRebase,
 | 
						|
		repo_model.MergeStyleRebaseMerge,
 | 
						|
		repo_model.MergeStyleSquash,
 | 
						|
		repo_model.MergeStyleFastForwardOnly,
 | 
						|
	}
 | 
						|
 | 
						|
	for _, mergeStyle := range mergeStyles {
 | 
						|
		t.Run(string(mergeStyle), func(t *testing.T) {
 | 
						|
			doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
 | 
						|
				// update workflow file
 | 
						|
				_, err := files_service.ChangeRepoFiles(t.Context(), repo, user, &files_service.ChangeRepoFilesOptions{
 | 
						|
					NewBranch: newBranchName,
 | 
						|
					Files: []*files_service.ChangeRepoFile{
 | 
						|
						{
 | 
						|
							Operation:     "update",
 | 
						|
							TreePath:      workflowTreePath,
 | 
						|
							ContentReader: strings.NewReader(workflowContent),
 | 
						|
						},
 | 
						|
					},
 | 
						|
					Message: "update workflow schedule",
 | 
						|
				})
 | 
						|
				assert.NoError(t, err)
 | 
						|
 | 
						|
				// create pull request
 | 
						|
				apiPull, err := doAPICreatePullRequest(testContext, repo.OwnerName, repo.Name, repo.DefaultBranch, newBranchName)(t)
 | 
						|
				assert.NoError(t, err)
 | 
						|
 | 
						|
				// merge pull request
 | 
						|
				testPullMerge(t, testContext.Session, repo.OwnerName, repo.Name, strconv.FormatInt(apiPull.Index, 10), MergeOptions{
 | 
						|
					Style: mergeStyle,
 | 
						|
				})
 | 
						|
 | 
						|
				pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
 | 
						|
				return pull.MergedCommitID, "@every 2m"
 | 
						|
			})
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	t.Run(string(repo_model.MergeStyleManuallyMerged), func(t *testing.T) {
 | 
						|
		doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
 | 
						|
			// enable manual-merge
 | 
						|
			doAPIEditRepository(testContext, &api.EditRepoOption{
 | 
						|
				HasPullRequests:  util.ToPointer(true),
 | 
						|
				AllowManualMerge: util.ToPointer(true),
 | 
						|
			})(t)
 | 
						|
 | 
						|
			// update workflow file
 | 
						|
			fileResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user, &files_service.ChangeRepoFilesOptions{
 | 
						|
				NewBranch: newBranchName,
 | 
						|
				Files: []*files_service.ChangeRepoFile{
 | 
						|
					{
 | 
						|
						Operation:     "update",
 | 
						|
						TreePath:      workflowTreePath,
 | 
						|
						ContentReader: strings.NewReader(workflowContent),
 | 
						|
					},
 | 
						|
				},
 | 
						|
				Message: "update workflow schedule",
 | 
						|
			})
 | 
						|
			assert.NoError(t, err)
 | 
						|
 | 
						|
			// merge and push
 | 
						|
			dstPath := t.TempDir()
 | 
						|
			u.Path = repo.FullName() + ".git"
 | 
						|
			u.User = url.UserPassword(repo.OwnerName, userPassword)
 | 
						|
			doGitClone(dstPath, u)(t)
 | 
						|
			doGitMerge(dstPath, "origin/"+newBranchName)(t)
 | 
						|
			doGitPushTestRepository(dstPath, "origin", repo.DefaultBranch)(t)
 | 
						|
 | 
						|
			// create pull request
 | 
						|
			apiPull, err := doAPICreatePullRequest(testContext, repo.OwnerName, repo.Name, repo.DefaultBranch, newBranchName)(t)
 | 
						|
			assert.NoError(t, err)
 | 
						|
 | 
						|
			// merge pull request manually
 | 
						|
			doAPIManuallyMergePullRequest(testContext, repo.OwnerName, repo.Name, fileResp.Commit.SHA, apiPull.Index)(t)
 | 
						|
 | 
						|
			pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
 | 
						|
			assert.Equal(t, issues_model.PullRequestStatusManuallyMerged, pull.Status)
 | 
						|
			return pull.MergedCommitID, "@every 2m"
 | 
						|
		})
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func testScheduleUpdateMirrorSync(t *testing.T) {
 | 
						|
	doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
 | 
						|
		// create mirror repo
 | 
						|
		opts := migration.MigrateOptions{
 | 
						|
			RepoName:    "actions-schedule-mirror",
 | 
						|
			Description: "Test mirror for actions-schedule",
 | 
						|
			Private:     false,
 | 
						|
			Mirror:      true,
 | 
						|
			CloneAddr:   repo.CloneLinkGeneral(t.Context()).HTTPS,
 | 
						|
		}
 | 
						|
		mirrorRepo, err := repo_service.CreateRepositoryDirectly(t.Context(), user, user, repo_service.CreateRepoOptions{
 | 
						|
			Name:          opts.RepoName,
 | 
						|
			Description:   opts.Description,
 | 
						|
			IsPrivate:     opts.Private,
 | 
						|
			IsMirror:      opts.Mirror,
 | 
						|
			DefaultBranch: repo.DefaultBranch,
 | 
						|
			Status:        repo_model.RepositoryBeingMigrated,
 | 
						|
		}, false)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		assert.True(t, mirrorRepo.IsMirror)
 | 
						|
		mirrorRepo, err = repo_service.MigrateRepositoryGitData(t.Context(), user, mirrorRepo, opts, nil)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		mirrorContext := NewAPITestContext(t, user.Name, mirrorRepo.Name, auth_model.AccessTokenScopeWriteRepository)
 | 
						|
 | 
						|
		// enable actions unit for mirror repo
 | 
						|
		assert.False(t, mirrorRepo.UnitEnabled(t.Context(), unit_model.TypeActions))
 | 
						|
		doAPIEditRepository(mirrorContext, &api.EditRepoOption{
 | 
						|
			HasActions: util.ToPointer(true),
 | 
						|
		})(t)
 | 
						|
		actionSchedule := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: mirrorRepo.ID})
 | 
						|
		scheduleSpec := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: mirrorRepo.ID, ScheduleID: actionSchedule.ID})
 | 
						|
		assert.Equal(t, "@every 1m", scheduleSpec.Spec)
 | 
						|
 | 
						|
		// update remote repo
 | 
						|
		newCron := "30 5,17 * * 2,4"
 | 
						|
		pushScheduleChange(t, u, repo, newCron)
 | 
						|
		repoDefaultBranch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
 | 
						|
		assert.NoError(t, err)
 | 
						|
 | 
						|
		// sync
 | 
						|
		ok := mirror_service.SyncPullMirror(t.Context(), mirrorRepo.ID)
 | 
						|
		assert.True(t, ok)
 | 
						|
		mirrorRepoDefaultBranch, err := git_model.GetBranch(t.Context(), mirrorRepo.ID, mirrorRepo.DefaultBranch)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		assert.Equal(t, repoDefaultBranch.CommitID, mirrorRepoDefaultBranch.CommitID)
 | 
						|
 | 
						|
		// check updated schedule
 | 
						|
		actionSchedule = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: mirrorRepo.ID})
 | 
						|
		scheduleSpec = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: mirrorRepo.ID, ScheduleID: actionSchedule.ID})
 | 
						|
		assert.Equal(t, newCron, scheduleSpec.Spec)
 | 
						|
 | 
						|
		return repoDefaultBranch.CommitID, newCron
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func testScheduleUpdateArchiveAndUnarchive(t *testing.T) {
 | 
						|
	doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
 | 
						|
		doAPIEditRepository(testContext, &api.EditRepoOption{
 | 
						|
			Archived: util.ToPointer(true),
 | 
						|
		})(t)
 | 
						|
		assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
 | 
						|
		doAPIEditRepository(testContext, &api.EditRepoOption{
 | 
						|
			Archived: util.ToPointer(false),
 | 
						|
		})(t)
 | 
						|
		branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		return branch.CommitID, "@every 1m"
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func testScheduleUpdateDisableAndEnableActionsUnit(t *testing.T) {
 | 
						|
	doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
 | 
						|
		doAPIEditRepository(testContext, &api.EditRepoOption{
 | 
						|
			HasActions: util.ToPointer(false),
 | 
						|
		})(t)
 | 
						|
		assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
 | 
						|
		doAPIEditRepository(testContext, &api.EditRepoOption{
 | 
						|
			HasActions: util.ToPointer(true),
 | 
						|
		})(t)
 | 
						|
		branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		return branch.CommitID, "@every 1m"
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
type scheduleUpdateTrigger func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string)
 | 
						|
 | 
						|
func doTestScheduleUpdate(t *testing.T, updateTrigger scheduleUpdateTrigger) {
 | 
						|
	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-schedule", false)
 | 
						|
		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
 | 
						|
		assert.NoError(t, repo.LoadAttributes(t.Context()))
 | 
						|
		httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
 | 
						|
		defer doAPIDeleteRepository(httpContext)(t)
 | 
						|
 | 
						|
		wfTreePath := ".gitea/workflows/actions-schedule.yml"
 | 
						|
		wfFileContent := `name: actions-schedule
 | 
						|
on:
 | 
						|
  schedule:
 | 
						|
    - cron:  '@every 1m'
 | 
						|
jobs:
 | 
						|
  job:
 | 
						|
    runs-on: ubuntu-latest
 | 
						|
    steps:
 | 
						|
      - run: echo 'schedule workflow'
 | 
						|
`
 | 
						|
 | 
						|
		opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wfTreePath, wfFileContent)
 | 
						|
		apiFileResp := createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts1)
 | 
						|
 | 
						|
		actionSchedule := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: repo.ID, CommitSHA: apiFileResp.Commit.SHA})
 | 
						|
		scheduleSpec := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: repo.ID, ScheduleID: actionSchedule.ID})
 | 
						|
		assert.Equal(t, "@every 1m", scheduleSpec.Spec)
 | 
						|
 | 
						|
		commitID, expectedSpec := updateTrigger(t, u, httpContext, user2, repo)
 | 
						|
 | 
						|
		actionSchedule = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: repo.ID, CommitSHA: commitID})
 | 
						|
		scheduleSpec = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: repo.ID, ScheduleID: actionSchedule.ID})
 | 
						|
		assert.Equal(t, expectedSpec, scheduleSpec.Spec)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func pushScheduleChange(t *testing.T, u *url.URL, repo *repo_model.Repository, newCron string) {
 | 
						|
	workflowTreePath := ".gitea/workflows/actions-schedule.yml"
 | 
						|
	workflowContent := `name: actions-schedule
 | 
						|
on:
 | 
						|
  schedule:
 | 
						|
    - cron:  '` + newCron + `'
 | 
						|
jobs:
 | 
						|
  job:
 | 
						|
    runs-on: ubuntu-latest
 | 
						|
    steps:
 | 
						|
      - run: echo 'schedule workflow'
 | 
						|
`
 | 
						|
 | 
						|
	dstPath := t.TempDir()
 | 
						|
	u.Path = repo.FullName() + ".git"
 | 
						|
	u.User = url.UserPassword(repo.OwnerName, userPassword)
 | 
						|
	doGitClone(dstPath, u)(t)
 | 
						|
	doGitCheckoutWriteFileCommit(localGitAddCommitOptions{
 | 
						|
		LocalRepoPath:   dstPath,
 | 
						|
		CheckoutBranch:  repo.DefaultBranch,
 | 
						|
		TreeFilePath:    workflowTreePath,
 | 
						|
		TreeFileContent: workflowContent,
 | 
						|
	})(t)
 | 
						|
	doGitPushTestRepository(dstPath, "origin", repo.DefaultBranch)(t)
 | 
						|
}
 |