Files
forge/services/pull/commit_status_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

166 lines
7.1 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package pull
import (
"testing"
actions_model "github.com/hanzoai/git/models/actions"
"github.com/hanzoai/git/models/db"
git_model "github.com/hanzoai/git/models/git"
repo_model "github.com/hanzoai/git/models/repo"
"github.com/hanzoai/git/models/unittest"
"github.com/hanzoai/git/modules/commitstatus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMergeRequiredContextsCommitStatus(t *testing.T) {
cases := []struct {
commitStatuses []*git_model.CommitStatus
requiredContexts []string
expected commitstatus.CommitStatusState
}{
{
commitStatuses: []*git_model.CommitStatus{},
requiredContexts: []string{},
expected: commitstatus.CommitStatusPending,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build xxx", State: commitstatus.CommitStatusSkipped},
},
requiredContexts: []string{"Build*"},
expected: commitstatus.CommitStatusSuccess,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build 1", State: commitstatus.CommitStatusSkipped},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 3", State: commitstatus.CommitStatusSuccess},
},
requiredContexts: []string{"Build*"},
expected: commitstatus.CommitStatusSuccess,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusPending},
},
requiredContexts: []string{"Build*", "Build 2t*"},
expected: commitstatus.CommitStatusPending,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusFailure},
},
requiredContexts: []string{"Build*", "Build 2t*"},
expected: commitstatus.CommitStatusFailure,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusFailure},
},
requiredContexts: []string{"Build*"},
expected: commitstatus.CommitStatusFailure,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusSuccess},
},
requiredContexts: []string{"Build*", "Build 2t*", "Build 3*"},
expected: commitstatus.CommitStatusPending,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build 1", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2", State: commitstatus.CommitStatusSuccess},
{Context: "Build 2t", State: commitstatus.CommitStatusSuccess},
},
requiredContexts: []string{"Build*", "Build *", "Build 2t*", "Build 1*"},
expected: commitstatus.CommitStatusSuccess,
},
}
for i, c := range cases {
assert.Equal(t, c.expected, MergeRequiredContextsCommitStatus(c.commitStatuses, c.requiredContexts), "case %d", i)
}
}
// TestEffectiveRequiredContexts: every required scoped workflow's stored status-check patterns are appended to the
// branch protection's configured contexts unconditionally (must-present; the matching is done downstream).
func TestEffectiveRequiredContexts(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
consumer := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) // owned by user5
pbOn := &git_model.ProtectedBranch{EnableStatusCheck: true, StatusCheckContexts: []string{"configured/check"}}
t.Run("nil protected branch: nil", func(t *testing.T) {
got, err := EffectiveRequiredContexts(t.Context(), consumer, nil)
require.NoError(t, err)
assert.Nil(t, got)
})
t.Run("status checks disabled, no required scoped: nothing required", func(t *testing.T) {
pbOff := &git_model.ProtectedBranch{EnableStatusCheck: false, StatusCheckContexts: []string{"configured/check"}}
got, err := EffectiveRequiredContexts(t.Context(), consumer, pbOff)
require.NoError(t, err)
assert.Empty(t, got) // the rule's own status check is off and no required scoped workflow applies -> nothing gates
})
t.Run("owner with no scoped sources: configured contexts unchanged", func(t *testing.T) {
noSourceRepo := &repo_model.Repository{ID: consumer.ID, OwnerID: 99999}
got, err := EffectiveRequiredContexts(t.Context(), noSourceRepo, pbOn)
require.NoError(t, err)
assert.Equal(t, []string{"configured/check"}, got)
})
t.Run("required workflow patterns appended", func(t *testing.T) {
require.NoError(t, db.Insert(t.Context(), &actions_model.ActionScopedWorkflowSource{
OwnerID: consumer.OwnerID,
SourceRepoID: 1,
WorkflowConfigs: map[string]*actions_model.ScopedWorkflowConfig{
"ci.yaml": {Required: true, Patterns: []string{"org/src: ci.yaml / build (pull_request)", "org/src: ci.yaml / lint (pull_request)"}},
"old.yaml": {Required: false, Patterns: []string{"org/src: old.yaml / *"}}, // kept as history, must NOT be enforced
},
}))
// No status is passed/needed: required patterns are enforced even though nothing has posted them yet (must-present).
got, err := EffectiveRequiredContexts(t.Context(), consumer, pbOn)
require.NoError(t, err)
assert.ElementsMatch(t, []string{
"configured/check",
"org/src: ci.yaml / build (pull_request)",
"org/src: ci.yaml / lint (pull_request)",
}, got)
assert.NotContains(t, got, "org/src: old.yaml / *", "a non-required (history) config must not be enforced")
})
t.Run("status checks disabled, with required scoped: only the scoped patterns gate", func(t *testing.T) {
pbOff := &git_model.ProtectedBranch{EnableStatusCheck: false, StatusCheckContexts: []string{"configured/check"}}
got, err := EffectiveRequiredContexts(t.Context(), consumer, pbOff)
require.NoError(t, err)
// "configured/check" is dropped (the rule's own status check is off); only the required scoped patterns remain.
assert.ElementsMatch(t, []string{
"org/src: ci.yaml / build (pull_request)",
"org/src: ci.yaml / lint (pull_request)",
}, got)
})
t.Run("several rules union their enabled contexts, deduplicated; a disabled rule contributes none", func(t *testing.T) {
noSourceRepo := &repo_model.Repository{ID: consumer.ID, OwnerID: 99999} // no scoped sources, so only the rules' contexts gate
a := &git_model.ProtectedBranch{EnableStatusCheck: true, StatusCheckContexts: []string{"a/check", "shared/check"}}
b := &git_model.ProtectedBranch{EnableStatusCheck: true, StatusCheckContexts: []string{"b/check", "shared/check"}}
off := &git_model.ProtectedBranch{EnableStatusCheck: false, StatusCheckContexts: []string{"off/check"}}
got, err := EffectiveRequiredContexts(t.Context(), noSourceRepo, a, b, off)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"a/check", "b/check", "shared/check"}, got)
})
}