Files
forge/tests/integration/repo_commits_test.go
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

246 lines
10 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"path"
"strings"
"sync"
"testing"
auth_model "github.com/hanzoai/git/models/auth"
"github.com/hanzoai/git/models/db"
git_model "github.com/hanzoai/git/models/git"
"github.com/hanzoai/git/modules/commitstatus"
"github.com/hanzoai/git/modules/json"
"github.com/hanzoai/git/modules/setting"
api "github.com/hanzoai/git/modules/structs"
"github.com/hanzoai/git/tests"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepoCommits(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
t.Run("CommitList", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo16/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK)
var commits, userHrefs []string
doc := NewHTMLParser(t, resp.Body)
doc.doc.Find("#commits-table .commit-id-short").Each(func(i int, s *goquery.Selection) {
commits = append(commits, path.Base(s.AttrOr("href", "")))
})
doc.doc.Find("#commits-table .avatar-stack-names a.muted").Each(func(i int, s *goquery.Selection) {
userHrefs = append(userHrefs, s.AttrOr("href", ""))
})
assert.Equal(t, []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, commits)
assert.Equal(t, []string{
"/user2/repo16/commits/branch/master/search?q=author%3Auser2%40example.com",
"/user2/repo16/commits/branch/master/search?q=author%3Auser21%40example.com",
"/user2/repo16/commits/branch/master/search?q=author%3Auser2%40example.com",
}, userHrefs)
})
t.Run("LastCommit", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo16")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
commitHref := doc.doc.Find(".latest-commit .commit-id-short").AttrOr("href", "")
authorHref := doc.doc.Find(".latest-commit .avatar-stack-names a").AttrOr("href", "")
assert.Equal(t, "/user2/repo16/commit/69554a64c1e6030f051e5c3f94bfbd773cd6a324", commitHref)
assert.Equal(t, "/user2/repo16/commits/branch/master/search?q=author%3Auser2%40example.com", authorHref)
})
t.Run("CommitListNonExistingCommiter", func(t *testing.T) {
// check the commit list for a repository whose commit author has no account here
// * commit 985f0301dba5e7b34be866819cd15ad3d8f508ee (branch2)
// * Author: 6543 <[email protected]>
req := NewRequest(t, "GET", "/user2/repo1/commits/branch/branch2")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
commitHref := doc.doc.Find("#commits-table tr:first-child .commit-id-short").AttrOr("href", "")
assert.Equal(t, "/user2/repo1/commit/985f0301dba5e7b34be866819cd15ad3d8f508ee", commitHref)
authorElem := doc.doc.Find("#commits-table tr:first-child .avatar-stack-names")
assert.Equal(t, "6543", strings.TrimSpace(authorElem.Text()))
})
t.Run("CommitPageUsesCommitterDate", func(t *testing.T) {
const (
commitID = "5099b81332712fe655e34e8dd63574f503f61811"
expectedCommitterTime = "2017-08-06T19:56:13+02:00"
authorTime = "2017-08-06T19:55:01+02:00"
)
req := NewRequest(t, "GET", "/user2/repo16/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
var commitListTime string
doc.doc.Find("#commits-table tbody tr").EachWithBreak(func(_ int, row *goquery.Selection) bool {
if path.Base(row.Find(".commit-id-short").AttrOr("href", "")) != commitID {
return true
}
commitListTime = row.Find("td").Eq(3).Find("relative-time").AttrOr("datetime", "")
return false
})
require.Equal(t, expectedCommitterTime, commitListTime)
})
t.Run("LastCommitNonExistingCommiter", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/src/branch/branch2")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
commitHref := doc.doc.Find(".latest-commit .commit-id-short").AttrOr("href", "")
assert.Equal(t, "/user2/repo1/commit/985f0301dba5e7b34be866819cd15ad3d8f508ee", commitHref)
authorElem := doc.doc.Find(".latest-commit .avatar-stack-names")
assert.Equal(t, "6543", strings.TrimSpace(authorElem.Text()))
})
}
func TestRepoCommitsWithStatus(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository)
requestCommitStatuses := func(t *testing.T, linkList, linkCombined string) (statuses []*api.CommitStatus, status api.CombinedStatus) {
assert.NoError(t, json.Unmarshal(session.MakeRequest(t, NewRequest(t, "GET", linkList), http.StatusOK).Body.Bytes(), &statuses))
assert.NoError(t, json.Unmarshal(session.MakeRequest(t, NewRequest(t, "GET", linkCombined), http.StatusOK).Body.Bytes(), &status))
return statuses, status
}
testRefMaster := func(t *testing.T, state commitstatus.CommitStatusState, classes ...string) {
_ = db.TruncateBeans(t.Context(), &git_model.CommitStatus{})
// Request repository commits page
req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, _ := doc.doc.Find("#commits-table .commit-id-short").Attr("href")
require.NotEmpty(t, commitURL)
commitID := path.Base(commitURL)
// Call API to add status for commit
doAPICreateCommitStatusTest(ctx, path.Base(commitURL), state, "testci")(t)
req = NewRequest(t, "GET", "/user2/repo1/commits/branch/master")
resp = session.MakeRequest(t, req, http.StatusOK)
doc = NewHTMLParser(t, resp.Body)
// Check if commit status is displayed in message column (.tippy-target to ignore the tippy trigger)
sel := doc.doc.Find("#commits-table .message .tippy-target .commit-status")
assert.Equal(t, 1, sel.Length())
for _, class := range classes {
assert.True(t, sel.HasClass(class))
}
testRepoCommitsWithStatus := func(t *testing.T, linkList, linkCombined string, state commitstatus.CommitStatusState) {
statuses, status := requestCommitStatuses(t, linkList, linkCombined)
require.Len(t, statuses, 1)
require.NotNil(t, status)
assert.Equal(t, state, statuses[0].State)
assert.Equal(t, setting.AppURL+"v1/repos/user2/repo1/statuses/"+commitID, statuses[0].URL)
assert.Equal(t, "http://test.ci/", statuses[0].TargetURL)
assert.Empty(t, statuses[0].Description)
assert.Equal(t, "testci", statuses[0].Context)
assert.Len(t, status.Statuses, 1)
assert.Equal(t, statuses[0], status.Statuses[0])
assert.Equal(t, commitID, status.SHA)
}
// By SHA
testRepoCommitsWithStatus(t, "/v1/repos/user2/repo1/commits/"+commitID+"/statuses", "/v1/repos/user2/repo1/commits/"+commitID+"/status", state)
// By short SHA
testRepoCommitsWithStatus(t, "/v1/repos/user2/repo1/commits/"+commitID[:7]+"/statuses", "/v1/repos/user2/repo1/commits/"+commitID[:7]+"/status", state)
// By Ref
testRepoCommitsWithStatus(t, "/v1/repos/user2/repo1/commits/master/statuses", "/v1/repos/user2/repo1/commits/master/status", state)
// Tag "v1.1" points to master
testRepoCommitsWithStatus(t, "/v1/repos/user2/repo1/commits/v1.1/statuses", "/v1/repos/user2/repo1/commits/v1.1/status", state)
}
t.Run("pending", func(t *testing.T) { testRefMaster(t, "pending", "octicon-dot-fill", "tw-text-yellow") })
t.Run("success", func(t *testing.T) { testRefMaster(t, "success", "octicon-check", "tw-text-green") })
t.Run("error", func(t *testing.T) { testRefMaster(t, "error", "git-exclamation", "tw-text-red") })
t.Run("failure", func(t *testing.T) { testRefMaster(t, "failure", "octicon-x", "tw-text-red") })
t.Run("warning", func(t *testing.T) { testRefMaster(t, "warning", "git-exclamation", "tw-text-yellow") })
t.Run("BranchWithSlash", func(t *testing.T) {
_ = db.TruncateBeans(t.Context(), &git_model.CommitStatus{})
linkList, linkCombined := "/v1/repos/user2/repo1/commits/feature%2F1/statuses", "/v1/repos/user2/repo1/commits/feature/1/status"
statuses, status := requestCommitStatuses(t, linkList, linkCombined)
assert.Empty(t, statuses)
assert.Empty(t, status.Statuses)
doAPICreateCommitStatusTest(ctx, "feature/1", commitstatus.CommitStatusSuccess, "testci")(t)
statuses, status = requestCommitStatuses(t, linkList, linkCombined)
assert.NotEmpty(t, statuses)
assert.NotEmpty(t, status.Statuses)
})
}
func TestRepoCommitsStatusParallel(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
// Request repository commits page
req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
var wg sync.WaitGroup
for i := range 10 {
wg.Go(func() {
t.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) {
ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository)
doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusPending, "testci")(t)
})
})
}
wg.Wait()
}
func TestRepoCommitsStatusMultiple(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
// Request repository commits page
req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
// Call API to add status for commit
ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository)
t.Run("CreateStatus", doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusSuccess, "testci"))
t.Run("CreateStatus", doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusSuccess, "other_context"))
req = NewRequest(t, "GET", "/user2/repo1/commits/branch/master")
resp = session.MakeRequest(t, req, http.StatusOK)
doc = NewHTMLParser(t, resp.Body)
// Check that the data-global-init="initCommitStatuses" (for trigger) and commit-status (svg) are present
sel := doc.doc.Find(`#commits-table .message [data-global-init="initCommitStatuses"] .commit-status`)
assert.Equal(t, 1, sel.Length())
}