Files
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
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]>
2026-09-10 17:50:35 -07:00

239 lines
9.4 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"path"
"strings"
"testing"
auth_model "github.com/hanzoai/git/models/auth"
git_model "github.com/hanzoai/git/models/git"
"github.com/hanzoai/git/models/issues"
repo_model "github.com/hanzoai/git/models/repo"
"github.com/hanzoai/git/models/unittest"
"github.com/hanzoai/git/modules/commitstatus"
"github.com/hanzoai/git/modules/setting"
api "github.com/hanzoai/git/modules/structs"
"github.com/hanzoai/git/modules/test"
"github.com/hanzoai/git/services/pull"
"github.com/stretchr/testify/assert"
)
func TestPullCreate_CommitStatus(t *testing.T) {
onGitRun(t, func(t *testing.T, u *url.URL) {
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1")
req := NewRequestWithValues(t, "POST", "/user1/repo1/compare/master...status1",
map[string]string{
"title": "pull request from status1",
},
)
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/user1/repo1/pulls")
resp := session.MakeRequest(t, req, http.StatusOK)
NewHTMLParser(t, resp.Body)
// Request repository commits page
req = NewRequest(t, "GET", "/user1/repo1/pulls/1/commits")
resp = session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
commitID := path.Base(commitURL)
statusList := []commitstatus.CommitStatusState{
commitstatus.CommitStatusPending,
commitstatus.CommitStatusError,
commitstatus.CommitStatusFailure,
commitstatus.CommitStatusWarning,
commitstatus.CommitStatusSuccess,
}
statesIcons := map[commitstatus.CommitStatusState]string{
commitstatus.CommitStatusPending: "octicon-dot-fill",
commitstatus.CommitStatusSuccess: "octicon-check",
commitstatus.CommitStatusError: "git-exclamation",
commitstatus.CommitStatusFailure: "octicon-x",
commitstatus.CommitStatusWarning: "git-exclamation",
}
testCtx := NewAPITestContext(t, "user1", "repo1", auth_model.AccessTokenScopeWriteRepository)
// Update commit status, and check if icon is updated as well
for _, status := range statusList {
// Call API to add status for commit
t.Run("CreateStatus", doAPICreateCommitStatusTest(testCtx, commitID, status, "testci"))
req = NewRequest(t, "GET", "/user1/repo1/pulls/1/commits")
resp = session.MakeRequest(t, req, http.StatusOK)
doc = NewHTMLParser(t, resp.Body)
commitURL, exists = doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
assert.Equal(t, commitID, path.Base(commitURL))
cls, ok := doc.doc.Find("#commits-table tbody tr td.message .commit-status").Last().Attr("class")
assert.True(t, ok)
assert.Contains(t, cls, statesIcons[status])
}
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
css := unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatusSummary{RepoID: repo1.ID, SHA: commitID})
assert.Equal(t, commitstatus.CommitStatusSuccess, css.State)
})
}
func doAPICreateCommitStatusTest(ctx APITestContext, ref string, state commitstatus.CommitStatusState, statusContext string) func(*testing.T) {
return func(t *testing.T) {
link := fmt.Sprintf("/v1/repos/%s/%s/statuses/%s", ctx.Username, ctx.Reponame, url.PathEscape(ref))
req := NewRequestWithJSON(t, http.MethodPost, link, api.CreateStatusOption{
State: state,
TargetURL: "http://test.ci/",
Context: statusContext,
}).AddTokenAuth(ctx.Token)
ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
}
}
func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
// Merge must continue if commits SHA are different, even if content is same
// Reason: gitflow and merging master back into develop, where is high possibility, there are no changes
// but just commit saying "Merge branch". And this meta commit can be also tagged,
// so we need to have this meta commit also in develop branch.
onGitRun(t, func(t *testing.T, u *url.URL) {
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1")
testEditFile(t, session, "user1", "repo1", "status1", "README.md", "# repo1\n\nDescription for repo1")
req := NewRequestWithValues(t, "POST", "/user1/repo1/compare/master...status1",
map[string]string{
"title": "pull request from status1",
},
)
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/user1/repo1/pulls/1")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
assert.Contains(t, text, "The changes on this branch are already on the target branch. This will be an empty commit.")
assert.Contains(t, text, "This pull request can be merged automatically.")
})
}
func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) {
onGitRun(t, func(t *testing.T, u *url.URL) {
session := loginUser(t, "user1")
testCreateBranch(t, session, "user2", "repo1", "branch/master", "empty-pr-branch", http.StatusSeeOther)
resp := testPullCreateDirectly(t, session, createPullRequestOptions{
BaseRepoOwner: "user2",
BaseRepoName: "repo1",
BaseBranch: "master",
HeadBranch: "empty-pr-branch",
Title: "empty pr test",
})
prURL := test.RedirectURL(resp)
// check the "merge box" text
req := NewRequest(t, "GET", prURL)
resp = session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.")
// check the "files" tab content
req = NewRequest(t, "GET", prURL+"/files")
resp = session.MakeRequest(t, req, http.StatusOK)
doc = NewHTMLParser(t, resp.Body)
assert.Equal(t, "Diff Content Not Available", strings.TrimSpace(doc.Find("#diff-container").Text()))
})
}
func TestPullStatusDelayCheck(t *testing.T) {
onGitRun(t, func(t *testing.T, u *url.URL) {
defer test.MockVariableValue(&setting.Repository.PullRequest.DelayCheckForInactiveDays, 1)()
defer test.MockVariableValue(&pull.AddPullRequestToCheckQueue)()
session := loginUser(t, "user2")
run := func(t *testing.T, fn func(*testing.T)) (issue3 *issues.Issue, checkedPrID int64) {
pull.AddPullRequestToCheckQueue = func(prID int64) {
checkedPrID = prID
}
fn(t)
issue3 = unittest.AssertExistsAndLoadBean(t, &issues.Issue{RepoID: 1, Index: 3})
_ = issue3.LoadPullRequest(t.Context())
return issue3, checkedPrID
}
assertReloadingInterval := func(t *testing.T, interval string) {
req := NewRequest(t, "GET", "/user2/repo1/pulls/3")
resp := session.MakeRequest(t, req, http.StatusOK)
attr := "data-pull-merge-box-reloading-interval"
if interval == "" {
assert.NotContains(t, resp.Body.String(), attr)
} else {
assert.Contains(t, resp.Body.String(), fmt.Sprintf(`%s="%v"`, attr, interval))
}
}
// PR issue3 is merageable at the beginning
issue3, checkedPrID := run(t, func(t *testing.T) {})
assert.Equal(t, issues.PullRequestStatusMergeable, issue3.PullRequest.Status)
assert.Zero(t, checkedPrID)
assertReloadingInterval(t, "") // the PR is mergeable, so no need to reload the merge box
// setting.IsProd = false // it would cause data-race because the queue handlers might be running and reading its value
// assertReloadingInterval(t, "1") // make sure dev mode always do merge box reloading, to make sure the UI logic won't break
// setting.IsProd = true
// when base branch changes, PR status should be updated, but it is inactive for long time, so no real check
issue3, checkedPrID = run(t, func(t *testing.T) {
testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 1")
})
assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status)
assert.Zero(t, checkedPrID)
assertReloadingInterval(t, "5000") // the PR status is "checking", so try to reload the merge box
// view a PR with status=checking, it starts the real check
issue3, checkedPrID = run(t, func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/pulls/3")
session.MakeRequest(t, req, http.StatusOK)
})
assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status)
assert.Equal(t, issue3.PullRequest.ID, checkedPrID)
// when base branch changes, still so no real check
issue3, checkedPrID = run(t, func(t *testing.T) {
testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 2")
})
assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status)
assert.Zero(t, checkedPrID)
// then allow to check PRs without delay, when base branch changes, the PRs will be checked
setting.Repository.PullRequest.DelayCheckForInactiveDays = -1
issue3, checkedPrID = run(t, func(t *testing.T) {
testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 3")
})
assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status)
assert.Equal(t, issue3.PullRequest.ID, checkedPrID)
})
}