Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions internal/engine/commit_evaluator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: Copyright 2026 The Minder Authors
// SPDX-License-Identifier: Apache-2.0

package engine

// Commit contains commit-level data that can be evaluated by policies.
type Commit struct {
SHA string
Message string
}

// CommitEvaluation captures the result of evaluating one policy.
type CommitEvaluation struct {
Policy string
Passed bool
Reason string
}

// CommitResult captures all evaluations for a single commit.
type CommitResult struct {
SHA string
Evaluations []CommitEvaluation
}

// CommitPolicy defines the minimal contract for demo-oriented commit-level policies.
// Concrete implementations should live outside internal/engine.
type CommitPolicy interface {
Name() string
Evaluate(commit Commit) CommitEvaluation
}

// CommitEvaluator evaluates commits against a list of commit-level policies.
type CommitEvaluator struct {
policies []CommitPolicy
}

// NewCommitEvaluator creates a commit evaluator with the provided policies.
func NewCommitEvaluator(policies ...CommitPolicy) *CommitEvaluator {
return &CommitEvaluator{policies: policies}
}

// Evaluate evaluates the commit against all configured policies.
func (e *CommitEvaluator) Evaluate(commit Commit) []CommitEvaluation {
if len(e.policies) == 0 {
return []CommitEvaluation{}
}

results := make([]CommitEvaluation, 0, len(e.policies))
for _, policy := range e.policies {
res := policy.Evaluate(commit)
if res.Policy == "" {
res.Policy = policy.Name()
}
results = append(results, res)
}

return results
}

// EvaluateAll evaluates all commits in a PR, preserving commit order.
func (e *CommitEvaluator) EvaluateAll(commits []Commit) []CommitResult {
results := make([]CommitResult, 0, len(commits))

for _, commit := range commits {
results = append(results, CommitResult{
SHA: commit.SHA,
Evaluations: e.Evaluate(commit),
})
}

return results
}

// HasPolicyFailures returns true if any policy evaluation fails.
func HasPolicyFailures(results []CommitResult) bool {
for _, commitResult := range results {
for _, eval := range commitResult.Evaluations {
if !eval.Passed {
return true
}
}
}

return false
}

// HasFailures returns true if any policy evaluation fails.
// Deprecated: use HasPolicyFailures for clearer semantics.
func HasFailures(results []CommitResult) bool {
return HasPolicyFailures(results)
}
105 changes: 105 additions & 0 deletions internal/engine/commit_evaluator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2026 The Minder Authors
// SPDX-License-Identifier: Apache-2.0

package engine

import (
"testing"

"github.com/stretchr/testify/require"
)

type alwaysPassPolicy struct{}
type alwaysFailPolicy struct{}

func (alwaysPassPolicy) Name() string {
return "always_pass"
}

func (p alwaysPassPolicy) Evaluate(_ Commit) CommitEvaluation {
return CommitEvaluation{
Policy: p.Name(),
Passed: true,
Reason: "policy passed",
}
}

func (alwaysFailPolicy) Name() string {
return "always_fail"
}

func (p alwaysFailPolicy) Evaluate(_ Commit) CommitEvaluation {
return CommitEvaluation{
Policy: p.Name(),
Passed: false,
Reason: "policy failed",
}
}

func TestCommitEvaluatorEvaluateWithoutPolicies(t *testing.T) {
t.Parallel()

evaluator := NewCommitEvaluator()

results := evaluator.Evaluate(Commit{Message: "feat: add evaluator"})
require.NotNil(t, results)
require.Empty(t, results)
}

func TestCommitEvaluatorEvaluateWithPolicy(t *testing.T) {
t.Parallel()

evaluator := NewCommitEvaluator(alwaysPassPolicy{})

results := evaluator.Evaluate(Commit{Message: "anything"})
require.Len(t, results, 1)
require.Equal(t, "always_pass", results[0].Policy)
require.True(t, results[0].Passed)
require.Equal(t, "policy passed", results[0].Reason)
}

func TestCommitEvaluatorEvaluateAll(t *testing.T) {
t.Parallel()

evaluator := NewCommitEvaluator(alwaysPassPolicy{})

results := evaluator.EvaluateAll([]Commit{
{SHA: "sha1", Message: "feat: one"},
{SHA: "sha2", Message: "fix: two"},
})

require.Len(t, results, 2)
require.Equal(t, "sha1", results[0].SHA)
require.Equal(t, "sha2", results[1].SHA)
require.Len(t, results[0].Evaluations, 1)
require.True(t, results[0].Evaluations[0].Passed)
}

func TestEvaluateAllEmpty(t *testing.T) {
t.Parallel()

evaluator := NewCommitEvaluator(alwaysPassPolicy{})
results := evaluator.EvaluateAll(nil)
require.Empty(t, results)
}

func TestHasPolicyFailures(t *testing.T) {
t.Parallel()

allPass := []CommitResult{
{SHA: "sha1", Evaluations: []CommitEvaluation{{Policy: "p1", Passed: true}}},
{SHA: "sha2", Evaluations: []CommitEvaluation{{Policy: "p1", Passed: true}}},
}
require.False(t, HasPolicyFailures(allPass))
require.False(t, HasFailures(allPass))

hasFail := []CommitResult{
{SHA: "sha1", Evaluations: []CommitEvaluation{{Policy: "p1", Passed: true}}},
{SHA: "sha2", Evaluations: []CommitEvaluation{{Policy: "p1", Passed: false}}},
}
require.True(t, HasPolicyFailures(hasFail))
require.True(t, HasFailures(hasFail))

evaluator := NewCommitEvaluator(alwaysPassPolicy{}, alwaysFailPolicy{})
require.True(t, HasPolicyFailures(evaluator.EvaluateAll([]Commit{{SHA: "sha3", Message: "any"}})))
}
Loading