github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/config/branch_protection_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"reflect"
    21  	"sort"
    22  	"testing"
    23  
    24  	"k8s.io/apimachinery/pkg/util/diff"
    25  )
    26  
    27  var (
    28  	y   = true
    29  	n   = false
    30  	yes = &y
    31  	no  = &n
    32  )
    33  
    34  func normalize(policy *Policy) {
    35  	if policy == nil || policy.RequiredStatusChecks == nil {
    36  		return
    37  	}
    38  	sort.Strings(policy.RequiredStatusChecks.Contexts)
    39  }
    40  
    41  func TestSelectBool(t *testing.T) {
    42  	cases := []struct {
    43  		name     string
    44  		parent   *bool
    45  		child    *bool
    46  		expected *bool
    47  	}{
    48  		{
    49  			name: "default is nil",
    50  		},
    51  		{
    52  			name:     "use child if set",
    53  			child:    yes,
    54  			expected: yes,
    55  		},
    56  		{
    57  			name:     "child overrides parent",
    58  			child:    yes,
    59  			parent:   no,
    60  			expected: yes,
    61  		},
    62  		{
    63  			name:     "use parent if child unset",
    64  			parent:   no,
    65  			expected: no,
    66  		},
    67  	}
    68  	for _, tc := range cases {
    69  		t.Run(tc.name, func(t *testing.T) {
    70  			actual := selectBool(tc.parent, tc.child)
    71  			if !reflect.DeepEqual(actual, tc.expected) {
    72  				t.Errorf("actual %v != expected %v", actual, tc.expected)
    73  			}
    74  		})
    75  	}
    76  }
    77  
    78  func TestSelectInt(t *testing.T) {
    79  	one := 1
    80  	two := 2
    81  	cases := []struct {
    82  		name     string
    83  		parent   *int
    84  		child    *int
    85  		expected *int
    86  	}{
    87  		{
    88  			name: "default is nil",
    89  		},
    90  		{
    91  			name:     "use child if set",
    92  			child:    &one,
    93  			expected: &one,
    94  		},
    95  		{
    96  			name:     "child overrides parent",
    97  			child:    &one,
    98  			parent:   &two,
    99  			expected: &one,
   100  		},
   101  		{
   102  			name:     "use parent if child unset",
   103  			parent:   &two,
   104  			expected: &two,
   105  		},
   106  	}
   107  	for _, tc := range cases {
   108  		t.Run(tc.name, func(t *testing.T) {
   109  			actual := selectInt(tc.parent, tc.child)
   110  			if !reflect.DeepEqual(actual, tc.expected) {
   111  				t.Errorf("actual %v != expected %v", actual, tc.expected)
   112  			}
   113  		})
   114  	}
   115  }
   116  
   117  func TestUnionStrings(t *testing.T) {
   118  	cases := []struct {
   119  		name     string
   120  		parent   []string
   121  		child    []string
   122  		expected []string
   123  	}{
   124  		{
   125  			name: "empty list",
   126  		},
   127  		{
   128  			name:     "all parent items",
   129  			parent:   []string{"hi", "there"},
   130  			expected: []string{"hi", "there"},
   131  		},
   132  		{
   133  			name:     "all child items",
   134  			child:    []string{"hi", "there"},
   135  			expected: []string{"hi", "there"},
   136  		},
   137  		{
   138  			name:     "both child and parent items, no duplicates",
   139  			child:    []string{"hi", "world"},
   140  			parent:   []string{"hi", "there"},
   141  			expected: []string{"hi", "there", "world"},
   142  		},
   143  	}
   144  	for _, tc := range cases {
   145  		t.Run(tc.name, func(t *testing.T) {
   146  			actual := unionStrings(tc.parent, tc.child)
   147  			sort.Strings(actual)
   148  			sort.Strings(tc.expected)
   149  			if !reflect.DeepEqual(actual, tc.expected) {
   150  				t.Errorf("actual %v != expected %v", actual, tc.expected)
   151  			}
   152  		})
   153  	}
   154  }
   155  
   156  func TestApply(test *testing.T) {
   157  	t := true
   158  	f := false
   159  	basic := Policy{
   160  		Protect: &t,
   161  	}
   162  	ebasic := Policy{
   163  		Protect: &t,
   164  	}
   165  	cases := []struct {
   166  		name     string
   167  		parent   Policy
   168  		child    Policy
   169  		expected Policy
   170  	}{
   171  		{
   172  			name:     "nil child",
   173  			parent:   basic,
   174  			expected: ebasic,
   175  		},
   176  		{
   177  			name: "merge parent and child",
   178  			parent: Policy{
   179  				Protect: &t,
   180  			},
   181  			child: Policy{
   182  				Admins: &f,
   183  			},
   184  			expected: Policy{
   185  				Protect: &t,
   186  				Admins:  &f,
   187  			},
   188  		},
   189  		{
   190  			name: "child overrides parent",
   191  			parent: Policy{
   192  				Protect: &t,
   193  			},
   194  			child: Policy{
   195  				Protect: &f,
   196  			},
   197  			expected: Policy{
   198  				Protect: &f,
   199  			},
   200  		},
   201  		{
   202  			name: "append strings",
   203  			parent: Policy{
   204  				RequiredStatusChecks: &ContextPolicy{
   205  					Contexts: []string{"hello", "world"},
   206  				},
   207  			},
   208  			child: Policy{
   209  				RequiredStatusChecks: &ContextPolicy{
   210  					Contexts: []string{"world", "of", "thrones"},
   211  				},
   212  			},
   213  			expected: Policy{
   214  				RequiredStatusChecks: &ContextPolicy{
   215  					Contexts: []string{"hello", "of", "thrones", "world"},
   216  				},
   217  			},
   218  		},
   219  		{
   220  			name: "merge struct",
   221  			parent: Policy{
   222  				RequiredStatusChecks: &ContextPolicy{
   223  					Contexts: []string{"hi"},
   224  				},
   225  			},
   226  			child: Policy{
   227  				RequiredStatusChecks: &ContextPolicy{
   228  					Strict: &t,
   229  				},
   230  			},
   231  			expected: Policy{
   232  				RequiredStatusChecks: &ContextPolicy{
   233  					Contexts: []string{"hi"},
   234  					Strict:   &t,
   235  				},
   236  			},
   237  		},
   238  		{
   239  			name: "nil child struct",
   240  			parent: Policy{
   241  				RequiredStatusChecks: &ContextPolicy{
   242  					Strict: &f,
   243  				},
   244  			},
   245  			child: Policy{
   246  				Protect: &t,
   247  			},
   248  			expected: Policy{
   249  				RequiredStatusChecks: &ContextPolicy{
   250  					Strict: &f,
   251  				},
   252  				Protect: &t,
   253  			},
   254  		},
   255  		{
   256  			name: "nil parent struct",
   257  			child: Policy{
   258  				RequiredStatusChecks: &ContextPolicy{
   259  					Strict: &f,
   260  				},
   261  			},
   262  			parent: Policy{
   263  				Protect: &t,
   264  			},
   265  			expected: Policy{
   266  				RequiredStatusChecks: &ContextPolicy{
   267  					Strict: &f,
   268  				},
   269  				Protect: &t,
   270  			},
   271  		},
   272  	}
   273  
   274  	for _, tc := range cases {
   275  		test.Run(tc.name, func(test *testing.T) {
   276  			defer func() {
   277  				if r := recover(); r != nil {
   278  					test.Errorf("unexpected panic: %s", r)
   279  				}
   280  			}()
   281  			actual, err := tc.parent.Apply(tc.child)
   282  			if err != nil {
   283  				test.Fatalf("unexpected error: %v", err)
   284  			}
   285  			normalize(&actual)
   286  			normalize(&tc.expected)
   287  			if !reflect.DeepEqual(actual, tc.expected) {
   288  				test.Errorf("bad merged policy:\n%s", diff.ObjectReflectDiff(tc.expected, actual))
   289  			}
   290  		})
   291  	}
   292  }
   293  
   294  func TestJobRequirements(t *testing.T) {
   295  	cases := []struct {
   296  		name                          string
   297  		config                        []Presubmit
   298  		masterExpected, otherExpected []string
   299  		masterOptional, otherOptional []string
   300  	}{
   301  		{
   302  			name: "basic",
   303  			config: []Presubmit{
   304  				{
   305  					Context:    "always-run",
   306  					AlwaysRun:  true,
   307  					SkipReport: false,
   308  				},
   309  				{
   310  					Context:      "run-if-changed",
   311  					RunIfChanged: "foo",
   312  					AlwaysRun:    false,
   313  					SkipReport:   false,
   314  				},
   315  				{
   316  					Context:    "not-always",
   317  					AlwaysRun:  false,
   318  					SkipReport: false,
   319  				},
   320  				{
   321  					Context:    "skip-report",
   322  					AlwaysRun:  true,
   323  					SkipReport: true,
   324  					Brancher: Brancher{
   325  						SkipBranches: []string{"master"},
   326  					},
   327  				},
   328  				{
   329  					Context:    "optional",
   330  					AlwaysRun:  true,
   331  					SkipReport: false,
   332  					Optional:   true,
   333  				},
   334  			},
   335  			masterExpected: []string{"always-run", "run-if-changed"},
   336  			masterOptional: []string{"optional"},
   337  			otherExpected:  []string{"always-run", "run-if-changed"},
   338  			otherOptional:  []string{"skip-report", "optional"},
   339  		},
   340  		{
   341  			name: "children",
   342  			config: []Presubmit{
   343  				{
   344  					Context:    "always-run",
   345  					AlwaysRun:  true,
   346  					SkipReport: false,
   347  					RunAfterSuccess: []Presubmit{
   348  						{
   349  							Context: "include-me",
   350  						},
   351  					},
   352  				},
   353  				{
   354  					Context:      "run-if-changed",
   355  					RunIfChanged: "foo",
   356  					SkipReport:   true,
   357  					AlwaysRun:    false,
   358  					RunAfterSuccess: []Presubmit{
   359  						{
   360  							Context: "me2",
   361  						},
   362  					},
   363  				},
   364  				{
   365  					Context:    "run-and-skip",
   366  					AlwaysRun:  true,
   367  					SkipReport: true,
   368  					RunAfterSuccess: []Presubmit{
   369  						{
   370  							Context: "also-me-3",
   371  						},
   372  					},
   373  				},
   374  				{
   375  					Context:    "optional",
   376  					AlwaysRun:  false,
   377  					SkipReport: false,
   378  					RunAfterSuccess: []Presubmit{
   379  						{
   380  							Context: "no thanks",
   381  						},
   382  					},
   383  				},
   384  				{
   385  					Context:    "hidden-grandpa",
   386  					AlwaysRun:  true,
   387  					SkipReport: true,
   388  					RunAfterSuccess: []Presubmit{
   389  						{
   390  							Context:   "hidden-parent",
   391  							Optional:  true,
   392  							AlwaysRun: false,
   393  							Brancher: Brancher{
   394  								Branches: []string{"master"},
   395  							},
   396  							RunAfterSuccess: []Presubmit{
   397  								{
   398  									Context: "visible-kid",
   399  									Brancher: Brancher{
   400  										Branches: []string{"master"},
   401  									},
   402  								},
   403  							},
   404  						},
   405  					},
   406  				},
   407  			},
   408  			masterExpected: []string{
   409  				"always-run", "include-me",
   410  				"me2",
   411  				"also-me-3",
   412  				"visible-kid",
   413  			},
   414  			masterOptional: []string{
   415  				"run-if-changed",
   416  				"run-and-skip",
   417  				"hidden-grandpa",
   418  				"hidden-parent"},
   419  			otherExpected: []string{
   420  				"always-run", "include-me",
   421  				"me2",
   422  				"also-me-3",
   423  			},
   424  			otherOptional: []string{
   425  				"run-if-changed",
   426  				"run-and-skip",
   427  				"hidden-grandpa"},
   428  		},
   429  	}
   430  
   431  	for _, tc := range cases {
   432  		if err := SetPresubmitRegexes(tc.config); err != nil {
   433  			t.Fatalf("could not set regexes: %v", err)
   434  		}
   435  		masterActual, masterOptional := jobRequirements(tc.config, "master", false)
   436  		if !reflect.DeepEqual(masterActual, tc.masterExpected) {
   437  			t.Errorf("branch: master - %s: actual %v != expected %v", tc.name, masterActual, tc.masterExpected)
   438  		}
   439  		if !reflect.DeepEqual(masterOptional, tc.masterOptional) {
   440  			t.Errorf("branch: master - optional - %s: actual %v != expected %v", tc.name, masterOptional, tc.masterOptional)
   441  		}
   442  		otherActual, otherOptional := jobRequirements(tc.config, "other", false)
   443  		if !reflect.DeepEqual(masterActual, tc.masterExpected) {
   444  			t.Errorf("branch: other - %s: actual %v != expected %v", tc.name, otherActual, tc.otherExpected)
   445  		}
   446  		if !reflect.DeepEqual(otherOptional, tc.otherOptional) {
   447  			t.Errorf("branch: other - optional - %s: actual %v != expected %v", tc.name, otherOptional, tc.otherOptional)
   448  		}
   449  	}
   450  }
   451  
   452  func TestConfig_GetBranchProtection(t *testing.T) {
   453  	testCases := []struct {
   454  		name              string
   455  		config            Config
   456  		org, repo, branch string
   457  		err               bool
   458  		expected          *Policy
   459  	}{
   460  		{
   461  			name: "unprotected by default",
   462  		},
   463  		{
   464  			name: "undefined org not protected",
   465  			config: Config{
   466  				ProwConfig: ProwConfig{
   467  					BranchProtection: BranchProtection{
   468  						Policy: Policy{
   469  							Protect: yes,
   470  						},
   471  						Orgs: map[string]Org{
   472  							"unknown": {},
   473  						},
   474  					},
   475  				},
   476  			},
   477  		},
   478  		{
   479  			name: "protect via config default",
   480  			config: Config{
   481  				ProwConfig: ProwConfig{
   482  					BranchProtection: BranchProtection{
   483  						Policy: Policy{
   484  							Protect: yes,
   485  						},
   486  						Orgs: map[string]Org{
   487  							"org": {},
   488  						},
   489  					},
   490  				},
   491  			},
   492  			expected: &Policy{Protect: yes},
   493  		},
   494  		{
   495  			name: "protect via org default",
   496  			config: Config{
   497  				ProwConfig: ProwConfig{
   498  					BranchProtection: BranchProtection{
   499  						Orgs: map[string]Org{
   500  							"org": {
   501  								Policy: Policy{
   502  									Protect: yes,
   503  								},
   504  							},
   505  						},
   506  					},
   507  				},
   508  			},
   509  			expected: &Policy{Protect: yes},
   510  		},
   511  		{
   512  			name: "protect via repo default",
   513  			config: Config{
   514  				ProwConfig: ProwConfig{
   515  					BranchProtection: BranchProtection{
   516  						Orgs: map[string]Org{
   517  							"org": {
   518  								Repos: map[string]Repo{
   519  									"repo": {
   520  										Policy: Policy{
   521  											Protect: yes,
   522  										},
   523  									},
   524  								},
   525  							},
   526  						},
   527  					},
   528  				},
   529  			},
   530  			expected: &Policy{Protect: yes},
   531  		},
   532  		{
   533  			name: "protect specific branch",
   534  			config: Config{
   535  				ProwConfig: ProwConfig{
   536  					BranchProtection: BranchProtection{
   537  						Orgs: map[string]Org{
   538  							"org": {
   539  								Repos: map[string]Repo{
   540  									"repo": {
   541  										Branches: map[string]Branch{
   542  											"branch": {
   543  												Policy: Policy{
   544  													Protect: yes,
   545  												},
   546  											},
   547  										},
   548  									},
   549  								},
   550  							},
   551  						},
   552  					},
   553  				},
   554  			},
   555  			expected: &Policy{Protect: yes},
   556  		},
   557  		{
   558  			name: "ignore other org settings",
   559  			config: Config{
   560  				ProwConfig: ProwConfig{
   561  					BranchProtection: BranchProtection{
   562  						Policy: Policy{
   563  							Protect: no,
   564  						},
   565  						Orgs: map[string]Org{
   566  							"other": {
   567  								Policy: Policy{Protect: yes},
   568  							},
   569  							"org": {},
   570  						},
   571  					},
   572  				},
   573  			},
   574  			expected: &Policy{Protect: no},
   575  		},
   576  		{
   577  			name: "defined branches must make a protection decision",
   578  			config: Config{
   579  				ProwConfig: ProwConfig{
   580  					BranchProtection: BranchProtection{
   581  						Orgs: map[string]Org{
   582  							"org": {
   583  								Repos: map[string]Repo{
   584  									"repo": {
   585  										Branches: map[string]Branch{
   586  											"branch": {},
   587  										},
   588  									},
   589  								},
   590  							},
   591  						},
   592  					},
   593  				},
   594  			},
   595  			err: true,
   596  		},
   597  		{
   598  			name: "pushers require protection",
   599  			config: Config{
   600  				ProwConfig: ProwConfig{
   601  					BranchProtection: BranchProtection{
   602  						Policy: Policy{
   603  							Protect: no,
   604  							Restrictions: &Restrictions{
   605  								Teams: []string{"oncall"},
   606  							},
   607  						},
   608  						Orgs: map[string]Org{
   609  							"org": {},
   610  						},
   611  					},
   612  				},
   613  			},
   614  			err: true,
   615  		},
   616  		{
   617  			name: "required contexts require protection",
   618  			config: Config{
   619  				ProwConfig: ProwConfig{
   620  					BranchProtection: BranchProtection{
   621  						Policy: Policy{
   622  							Protect: no,
   623  							RequiredStatusChecks: &ContextPolicy{
   624  								Contexts: []string{"test-foo"},
   625  							},
   626  						},
   627  						Orgs: map[string]Org{
   628  							"org": {},
   629  						},
   630  					},
   631  				},
   632  			},
   633  			err: true,
   634  		},
   635  		{
   636  			name: "child policy with defined parent can disable protection",
   637  			config: Config{
   638  				ProwConfig: ProwConfig{
   639  					BranchProtection: BranchProtection{
   640  						AllowDisabledPolicies: true,
   641  						Policy: Policy{
   642  							Protect: yes,
   643  							Restrictions: &Restrictions{
   644  								Teams: []string{"oncall"},
   645  							},
   646  						},
   647  						Orgs: map[string]Org{
   648  							"org": {
   649  								Policy: Policy{
   650  									Protect: no,
   651  								},
   652  							},
   653  						},
   654  					},
   655  				},
   656  			},
   657  			expected: &Policy{
   658  				Protect: no,
   659  			},
   660  		},
   661  		{
   662  			name: "Make required presubmits required",
   663  			config: Config{
   664  				ProwConfig: ProwConfig{
   665  					BranchProtection: BranchProtection{
   666  						Policy: Policy{
   667  							Protect: yes,
   668  							RequiredStatusChecks: &ContextPolicy{
   669  								Contexts: []string{"cla"},
   670  							},
   671  						},
   672  						Orgs: map[string]Org{
   673  							"org": {},
   674  						},
   675  					},
   676  				},
   677  				JobConfig: JobConfig{
   678  					Presubmits: map[string][]Presubmit{
   679  						"org/repo": {
   680  							{
   681  								Name:      "required presubmit",
   682  								Context:   "required presubmit",
   683  								AlwaysRun: true,
   684  							},
   685  						},
   686  					},
   687  				},
   688  			},
   689  			expected: &Policy{
   690  				Protect: yes,
   691  				RequiredStatusChecks: &ContextPolicy{
   692  					Contexts: []string{"required presubmit", "cla"},
   693  				},
   694  			},
   695  		},
   696  		{
   697  			name: "ProtectTested opts into protection",
   698  			config: Config{
   699  				ProwConfig: ProwConfig{
   700  					BranchProtection: BranchProtection{
   701  						ProtectTested: true,
   702  						Orgs: map[string]Org{
   703  							"org": {},
   704  						},
   705  					},
   706  				},
   707  				JobConfig: JobConfig{
   708  					Presubmits: map[string][]Presubmit{
   709  						"org/repo": {
   710  							{
   711  								Name:      "required presubmit",
   712  								Context:   "required presubmit",
   713  								AlwaysRun: true,
   714  							},
   715  						},
   716  					},
   717  				},
   718  			},
   719  			expected: &Policy{
   720  				Protect: yes,
   721  				RequiredStatusChecks: &ContextPolicy{
   722  					Contexts: []string{"required presubmit"},
   723  				},
   724  			},
   725  		},
   726  		{
   727  			name: "required presubmits require protection",
   728  			config: Config{
   729  				ProwConfig: ProwConfig{
   730  					BranchProtection: BranchProtection{
   731  						Policy: Policy{
   732  							Protect: no,
   733  						},
   734  						Orgs: map[string]Org{
   735  							"org": {},
   736  						},
   737  					},
   738  				},
   739  				JobConfig: JobConfig{
   740  					Presubmits: map[string][]Presubmit{
   741  						"org/repo": {
   742  							{
   743  								Name:      "required presubmit",
   744  								Context:   "required presubmit",
   745  								AlwaysRun: true,
   746  							},
   747  						},
   748  					},
   749  				},
   750  			},
   751  			err: true,
   752  		},
   753  		{
   754  			name: "Optional presubmits do not force protection",
   755  			config: Config{
   756  				ProwConfig: ProwConfig{
   757  					BranchProtection: BranchProtection{
   758  						ProtectTested: true,
   759  						Orgs: map[string]Org{
   760  							"org": {},
   761  						},
   762  					},
   763  				},
   764  				JobConfig: JobConfig{
   765  					Presubmits: map[string][]Presubmit{
   766  						"org/repo": {
   767  							{
   768  								Name:      "optional presubmit",
   769  								Context:   "optional presubmit",
   770  								AlwaysRun: true,
   771  								Optional:  true,
   772  							},
   773  						},
   774  					},
   775  				},
   776  			},
   777  		},
   778  		{
   779  			name: "Explicit configuration takes precedence over ProtectTested",
   780  			config: Config{
   781  				ProwConfig: ProwConfig{
   782  					BranchProtection: BranchProtection{
   783  						ProtectTested: true,
   784  						Orgs: map[string]Org{
   785  							"org": {
   786  								Policy: Policy{
   787  									Protect: yes,
   788  								},
   789  							},
   790  						},
   791  					},
   792  				},
   793  				JobConfig: JobConfig{
   794  					Presubmits: map[string][]Presubmit{
   795  						"org/repo": {
   796  							{
   797  								Name:      "optional presubmit",
   798  								Context:   "optional presubmit",
   799  								AlwaysRun: true,
   800  								Optional:  true,
   801  							},
   802  						},
   803  					},
   804  				},
   805  			},
   806  			expected: &Policy{Protect: yes},
   807  		},
   808  	}
   809  
   810  	for _, tc := range testCases {
   811  		t.Run(tc.name, func(t *testing.T) {
   812  			actual, err := tc.config.GetBranchProtection("org", "repo", "branch")
   813  			switch {
   814  			case err != nil:
   815  				if !tc.err {
   816  					t.Errorf("unexpected error: %v", err)
   817  				}
   818  			case err == nil && tc.err:
   819  				t.Errorf("failed to receive an error")
   820  			default:
   821  				normalize(actual)
   822  				normalize(tc.expected)
   823  				if !reflect.DeepEqual(actual, tc.expected) {
   824  					t.Errorf("actual %+v != expected %+v", actual, tc.expected)
   825  				}
   826  			}
   827  		})
   828  	}
   829  }