...

Source file src/edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/approval/evaluate_test.go

Documentation: edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/approval

     1  // Copyright 2018 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package approval
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"reflect"
    21  	"regexp"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/require"
    26  	"gopkg.in/yaml.v2"
    27  
    28  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/pull"
    29  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/pull/pulltest"
    30  
    31  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/common"
    32  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/predicate"
    33  )
    34  
    35  func TestRules(t *testing.T) {
    36  	ruleText := `
    37  - name: rule1
    38    if:
    39      changed_files:
    40        paths: ["path1"]
    41      only_changed_files:
    42        paths: ["path2"]
    43      has_author_in:
    44        teams: ["team1"]
    45        users: ["user1", "user2"]
    46        organizations: ["org1"]
    47      has_contributor_in:
    48        teams: ["team2"]
    49        users: ["user3"]
    50        organizations: ["org2"]
    51    options:
    52      allow_author: true
    53      allow_contributor: true
    54      # invalidate_on_push: true
    55      methods:
    56        comments: ["+1"]
    57        github_review: true
    58    requires:
    59      users: ["user4"]
    60      teams: ["team3", "team4"]
    61      organizations: ["org3"]
    62      count: 5
    63  `
    64  
    65  	var rules []*Rule
    66  	require.NoError(t, yaml.UnmarshalStrict([]byte(ruleText), &rules))
    67  
    68  	defaultGithubReview := true
    69  	defaultComments := []string{
    70  		":+1:",
    71  		"👍",
    72  	}
    73  	comments := []string{"+1"}
    74  	expected := []*Rule{
    75  		{
    76  			Name: "rule1",
    77  			Predicates: predicate.Predicates{
    78  				ChangedFiles: &predicate.ChangedFiles{
    79  					Paths: []common.Regexp{
    80  						common.NewCompiledRegexp(regexp.MustCompile("path1")),
    81  					},
    82  				},
    83  				OnlyChangedFiles: &predicate.OnlyChangedFiles{
    84  					Paths: []common.Regexp{
    85  						common.NewCompiledRegexp(regexp.MustCompile("path2")),
    86  					},
    87  				},
    88  				HasAuthorIn: &predicate.HasAuthorIn{
    89  					Actors: common.Actors{
    90  						Teams:         []string{"team1"},
    91  						Users:         []string{"user1", "user2"},
    92  						Organizations: []string{"org1"},
    93  					},
    94  				},
    95  				HasContributorIn: &predicate.HasContributorIn{
    96  					Actors: common.Actors{
    97  						Teams:         []string{"team2"},
    98  						Users:         []string{"user3"},
    99  						Organizations: []string{"org2"},
   100  					},
   101  				},
   102  			},
   103  			Options: Options{
   104  				AllowAuthor:      true,
   105  				AllowContributor: true,
   106  				// InvalidateOnPush: true,
   107  				Methods: &common.Methods{
   108  					Comments:     comments,
   109  					GithubReview: &defaultGithubReview,
   110  				},
   111  			},
   112  			Requires: Requires{
   113  				Count: 5,
   114  				Actors: common.Actors{
   115  					Users:         []string{"user4"},
   116  					Teams:         []string{"team3", "team4"},
   117  					Organizations: []string{"org3"},
   118  				},
   119  			},
   120  		},
   121  	}
   122  
   123  	require.True(t, reflect.DeepEqual(expected, rules))
   124  
   125  	optionsText := `
   126  allow_author: true
   127  allow_contributor: true
   128  invalidate_on_push: true
   129  methods:
   130    comments: ["+1"]
   131  `
   132  	expectedMethods := &common.Methods{
   133  		Comments:     comments,
   134  		GithubReview: &defaultGithubReview,
   135  	}
   136  	var options *Options
   137  	require.NoError(t, yaml.UnmarshalStrict([]byte(optionsText), &options))
   138  
   139  	methods := options.GetMethods()
   140  
   141  	require.True(t, reflect.DeepEqual(expectedMethods.Comments, methods.Comments))
   142  	require.True(t, reflect.DeepEqual(*expectedMethods.GithubReview, *methods.GithubReview))
   143  
   144  	optionsText = `
   145  allow_author: true
   146  allow_contributor: true
   147  invalidate_on_push: true
   148  methods:
   149    github_review: false
   150  `
   151  	falseGithubReview := false
   152  	expectedMethods = &common.Methods{
   153  		Comments:     defaultComments,
   154  		GithubReview: &falseGithubReview,
   155  	}
   156  
   157  	var optionsTwo *Options
   158  	require.NoError(t, yaml.UnmarshalStrict([]byte(optionsText), &optionsTwo))
   159  
   160  	methods = optionsTwo.GetMethods()
   161  
   162  	require.True(t, reflect.DeepEqual(expectedMethods.Comments, methods.Comments))
   163  	require.True(t, reflect.DeepEqual(*expectedMethods.GithubReview, *methods.GithubReview))
   164  
   165  	optionsText = `
   166  allow_author: true
   167  allow_contributor: true
   168  invalidate_on_push: true
   169  `
   170  	expectedMethods = &common.Methods{
   171  		Comments:     defaultComments,
   172  		GithubReview: &defaultGithubReview,
   173  	}
   174  	var optionsThree *Options
   175  	require.NoError(t, yaml.UnmarshalStrict([]byte(optionsText), &optionsThree))
   176  
   177  	methods = optionsThree.GetMethods()
   178  
   179  	require.True(t, reflect.DeepEqual(expectedMethods.Comments, methods.Comments))
   180  	require.True(t, reflect.DeepEqual(*expectedMethods.GithubReview, *methods.GithubReview))
   181  
   182  }
   183  
   184  type mockRequirement struct {
   185  	result *common.Result
   186  }
   187  
   188  func (m *mockRequirement) Trigger() common.Trigger {
   189  	return common.TriggerStatic
   190  }
   191  
   192  func (m *mockRequirement) Evaluate(ctx context.Context, prctx pull.Context) common.Result {
   193  	return *m.result
   194  }
   195  
   196  func makeRulesResultingIn(es ...common.EvaluationStatus) []common.Evaluator {
   197  	var requirements []common.Evaluator
   198  	for _, e := range es {
   199  		requirements = append(requirements, &mockRequirement{
   200  			result: &common.Result{
   201  				Status: e,
   202  			},
   203  		})
   204  	}
   205  	return requirements
   206  }
   207  
   208  func TestAndRequirement(t *testing.T) {
   209  	ctx := context.Background()
   210  	prctx := &pulltest.Context{}
   211  
   212  	// One pending is pending
   213  	and := &AndRequirement{
   214  		requirements: makeRulesResultingIn(common.StatusApproved, common.StatusPending),
   215  	}
   216  	result := and.Evaluate(ctx, prctx)
   217  	assert.NoError(t, result.Error)
   218  	assert.Equal(t, common.StatusPending, result.Status)
   219  
   220  	// All approved is approved
   221  	and = &AndRequirement{
   222  		requirements: makeRulesResultingIn(common.StatusApproved),
   223  	}
   224  	result = and.Evaluate(ctx, prctx)
   225  	assert.NoError(t, result.Error)
   226  	assert.Equal(t, common.StatusApproved, result.Status)
   227  
   228  	// Skipped counts
   229  	and = &AndRequirement{
   230  		requirements: makeRulesResultingIn(common.StatusApproved, common.StatusSkipped),
   231  	}
   232  	result = and.Evaluate(ctx, prctx)
   233  	assert.NoError(t, result.Error)
   234  	assert.Equal(t, common.StatusApproved, result.Status)
   235  
   236  	// Skipped itself is results in Skipped
   237  	and = &AndRequirement{
   238  		requirements: makeRulesResultingIn(common.StatusSkipped),
   239  	}
   240  	result = and.Evaluate(ctx, prctx)
   241  	assert.NoError(t, result.Error)
   242  	assert.Equal(t, common.StatusSkipped, result.Status)
   243  
   244  	// Error blocks approval
   245  	and = &AndRequirement{
   246  		requirements: []common.Evaluator{
   247  			&mockRequirement{
   248  				result: &common.Result{
   249  					Status: common.StatusApproved,
   250  				},
   251  			},
   252  			&mockRequirement{
   253  				result: &common.Result{
   254  					Error: errors.New("error"),
   255  				},
   256  			},
   257  		},
   258  	}
   259  	result = and.Evaluate(ctx, prctx)
   260  	assert.Error(t, result.Error)
   261  }
   262  
   263  func TestOrRequirement(t *testing.T) {
   264  	ctx := context.Background()
   265  	prctx := &pulltest.Context{}
   266  
   267  	// One approval is enough
   268  	or := &OrRequirement{
   269  		requirements: makeRulesResultingIn(common.StatusPending, common.StatusSkipped, common.StatusApproved),
   270  	}
   271  	result := or.Evaluate(ctx, prctx)
   272  	assert.NoError(t, result.Error)
   273  	assert.Equal(t, common.StatusApproved, result.Status)
   274  
   275  	// All approved is approved
   276  	or = &OrRequirement{
   277  		requirements: makeRulesResultingIn(common.StatusApproved),
   278  	}
   279  	result = or.Evaluate(ctx, prctx)
   280  	assert.NoError(t, result.Error)
   281  	assert.Equal(t, common.StatusApproved, result.Status)
   282  
   283  	// Skipped does not affect approval
   284  	or = &OrRequirement{
   285  		requirements: makeRulesResultingIn(common.StatusApproved, common.StatusSkipped),
   286  	}
   287  	result = or.Evaluate(ctx, prctx)
   288  	assert.NoError(t, result.Error)
   289  	assert.Equal(t, common.StatusApproved, result.Status)
   290  
   291  	// Skipped itself results in Skipped
   292  	or = &OrRequirement{
   293  		requirements: makeRulesResultingIn(common.StatusSkipped),
   294  	}
   295  	result = or.Evaluate(ctx, prctx)
   296  	assert.NoError(t, result.Error)
   297  	assert.Equal(t, common.StatusSkipped, result.Status)
   298  
   299  	// Error does not block approval
   300  	or = &OrRequirement{
   301  		requirements: []common.Evaluator{
   302  			&mockRequirement{
   303  				result: &common.Result{
   304  					Status: common.StatusApproved,
   305  				},
   306  			},
   307  			&mockRequirement{
   308  				result: &common.Result{
   309  					Error: errors.New("error"),
   310  				},
   311  			},
   312  		},
   313  	}
   314  	result = or.Evaluate(ctx, prctx)
   315  	assert.NoError(t, result.Error)
   316  	assert.Equal(t, common.StatusApproved, result.Status)
   317  }
   318  

View as plain text