mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
305 lines
12 KiB
Go
305 lines
12 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
actions_model "github.com/hanzoai/git/models/actions"
|
|
auth_model "github.com/hanzoai/git/models/auth"
|
|
git_model "github.com/hanzoai/git/models/git"
|
|
issues_model "github.com/hanzoai/git/models/issues"
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
unit_model "github.com/hanzoai/git/models/unit"
|
|
"github.com/hanzoai/git/models/unittest"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
"github.com/hanzoai/git/modules/migration"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
api "github.com/hanzoai/git/modules/structs"
|
|
"github.com/hanzoai/git/modules/test"
|
|
migrations_service "github.com/hanzoai/git/services/migrations"
|
|
mirror_service "github.com/hanzoai/git/services/mirror"
|
|
repo_service "github.com/hanzoai/git/services/repository"
|
|
files_service "github.com/hanzoai/git/services/repository/files"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
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 := ".hanzo/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: new(true),
|
|
AllowManualMerge: new(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) {
|
|
// the mirror sync re-validates the remote URL, which rejects the local test server unless local
|
|
// networks are allowed; migrations.Init rebuilds the host allow-list from the setting
|
|
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
|
|
require.NoError(t, migrations_service.Init())
|
|
|
|
// 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: new(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: new(true),
|
|
})(t)
|
|
assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
|
|
doAPIEditRepository(testContext, &api.EditRepoOption{
|
|
Archived: new(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: new(false),
|
|
})(t)
|
|
assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
|
|
doAPIEditRepository(testContext, &api.EditRepoOption{
|
|
HasActions: new(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) {
|
|
onGitRun(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 := ".hanzo/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 := ".hanzo/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)
|
|
}
|