github.com/jenkins-x/test-infra@v0.0.7/prow/config/tide_test.go (about)

     1  /*
     2  Copyright 2017 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  	"strings"
    22  	"testing"
    23  
    24  	"k8s.io/apimachinery/pkg/util/sets"
    25  	"k8s.io/test-infra/prow/github"
    26  	"k8s.io/test-infra/prow/labels"
    27  )
    28  
    29  var testQuery = TideQuery{
    30  	Orgs:                   []string{"org"},
    31  	Repos:                  []string{"k/k", "k/t-i"},
    32  	Labels:                 []string{labels.LGTM, labels.Approved},
    33  	MissingLabels:          []string{"foo"},
    34  	Milestone:              "milestone",
    35  	ReviewApprovedRequired: true,
    36  }
    37  
    38  func TestTideQuery(t *testing.T) {
    39  	q := " " + testQuery.Query() + " "
    40  	checkTok := func(tok string) {
    41  		if !strings.Contains(q, " "+tok+" ") {
    42  			t.Errorf("Expected query to contain \"%s\", got \"%s\"", tok, q)
    43  		}
    44  	}
    45  
    46  	checkTok("is:pr")
    47  	checkTok("state:open")
    48  	checkTok("org:\"org\"")
    49  	checkTok("repo:\"k/k\"")
    50  	checkTok("repo:\"k/t-i\"")
    51  	checkTok("label:\"lgtm\"")
    52  	checkTok("label:\"approved\"")
    53  	checkTok("-label:\"foo\"")
    54  	checkTok("milestone:\"milestone\"")
    55  	checkTok("review:approved")
    56  }
    57  
    58  func TestOrgExceptionsAndRepos(t *testing.T) {
    59  	queries := TideQueries{
    60  		{
    61  			Orgs:          []string{"k8s"},
    62  			ExcludedRepos: []string{"k8s/k8s"},
    63  		},
    64  		{
    65  			Orgs:          []string{"kuber"},
    66  			Repos:         []string{"foo/bar", "baz/bar"},
    67  			ExcludedRepos: []string{"kuber/netes"},
    68  		},
    69  		{
    70  			Orgs:          []string{"k8s"},
    71  			ExcludedRepos: []string{"k8s/k8s", "k8s/t-i"},
    72  		},
    73  		{
    74  			Orgs:          []string{"org", "org2"},
    75  			ExcludedRepos: []string{"org2/repo", "org2/repo2", "org2/repo3"},
    76  		},
    77  		{
    78  			Orgs:  []string{"foo"},
    79  			Repos: []string{"org2/repo3"},
    80  		},
    81  	}
    82  
    83  	expectedOrgs := map[string]sets.String{
    84  		"foo":   sets.NewString(),
    85  		"k8s":   sets.NewString("k8s/k8s"),
    86  		"kuber": sets.NewString("kuber/netes"),
    87  		"org":   sets.NewString(),
    88  		"org2":  sets.NewString("org2/repo", "org2/repo2"),
    89  	}
    90  	expectedRepos := sets.NewString("foo/bar", "baz/bar", "org2/repo3")
    91  
    92  	orgs, repos := queries.OrgExceptionsAndRepos()
    93  	if !reflect.DeepEqual(orgs, expectedOrgs) {
    94  		t.Errorf("Expected org map %v, but got %v.", expectedOrgs, orgs)
    95  	}
    96  	if !repos.Equal(expectedRepos) {
    97  		t.Errorf("Expected repo set %v, but got %v.", expectedRepos, repos)
    98  	}
    99  }
   100  
   101  func TestMergeMethod(t *testing.T) {
   102  	ti := &Tide{
   103  		MergeType: map[string]github.PullRequestMergeType{
   104  			"kubernetes/kops":             github.MergeRebase,
   105  			"kubernetes/charts":           github.MergeSquash,
   106  			"helm/charts":                 github.MergeSquash,
   107  			"kubernetes-helm":             github.MergeSquash,
   108  			"kubernetes-helm/chartmuseum": github.MergeMerge,
   109  		},
   110  	}
   111  
   112  	var testcases = []struct {
   113  		org      string
   114  		repo     string
   115  		expected github.PullRequestMergeType
   116  	}{
   117  		{
   118  			"kubernetes",
   119  			"kubernetes",
   120  			github.MergeMerge,
   121  		},
   122  		{
   123  			"kubernetes",
   124  			"kops",
   125  			github.MergeRebase,
   126  		},
   127  		{
   128  			"kubernetes",
   129  			"charts",
   130  			github.MergeSquash,
   131  		},
   132  		{
   133  			"kubernetes-helm",
   134  			"monocular",
   135  			github.MergeSquash,
   136  		},
   137  		{
   138  			"kubernetes-helm",
   139  			"chartmuseum",
   140  			github.MergeMerge,
   141  		},
   142  	}
   143  
   144  	for _, test := range testcases {
   145  		if ti.MergeMethod(test.org, test.repo) != test.expected {
   146  			t.Errorf("Expected merge method %q but got %q for %s/%s", test.expected, ti.MergeMethod(test.org, test.repo), test.org, test.repo)
   147  		}
   148  	}
   149  }
   150  
   151  func TestParseTideContextPolicyOptions(t *testing.T) {
   152  	yes := true
   153  	no := false
   154  	org, repo, branch := "org", "repo", "branch"
   155  	testCases := []struct {
   156  		name     string
   157  		config   TideContextPolicyOptions
   158  		expected TideContextPolicy
   159  	}{
   160  		{
   161  			name: "empty",
   162  		},
   163  		{
   164  			name: "global config",
   165  			config: TideContextPolicyOptions{
   166  				TideContextPolicy: TideContextPolicy{
   167  					FromBranchProtection: &yes,
   168  					SkipUnknownContexts:  &yes,
   169  					RequiredContexts:     []string{"r1"},
   170  					OptionalContexts:     []string{"o1"},
   171  				},
   172  			},
   173  			expected: TideContextPolicy{
   174  				SkipUnknownContexts:  &yes,
   175  				RequiredContexts:     []string{"r1"},
   176  				OptionalContexts:     []string{"o1"},
   177  				FromBranchProtection: &yes,
   178  			},
   179  		},
   180  		{
   181  			name: "org config",
   182  			config: TideContextPolicyOptions{
   183  				TideContextPolicy: TideContextPolicy{
   184  					RequiredContexts:     []string{"r1"},
   185  					OptionalContexts:     []string{"o1"},
   186  					FromBranchProtection: &no,
   187  				},
   188  				Orgs: map[string]TideOrgContextPolicy{
   189  					"org": {
   190  						TideContextPolicy: TideContextPolicy{
   191  							SkipUnknownContexts:  &yes,
   192  							RequiredContexts:     []string{"r2"},
   193  							OptionalContexts:     []string{"o2"},
   194  							FromBranchProtection: &yes,
   195  						},
   196  					},
   197  				},
   198  			},
   199  			expected: TideContextPolicy{
   200  				SkipUnknownContexts:  &yes,
   201  				RequiredContexts:     []string{"r1", "r2"},
   202  				OptionalContexts:     []string{"o1", "o2"},
   203  				FromBranchProtection: &yes,
   204  			},
   205  		},
   206  		{
   207  			name: "repo config",
   208  			config: TideContextPolicyOptions{
   209  				TideContextPolicy: TideContextPolicy{
   210  					RequiredContexts:     []string{"r1"},
   211  					OptionalContexts:     []string{"o1"},
   212  					FromBranchProtection: &no,
   213  				},
   214  				Orgs: map[string]TideOrgContextPolicy{
   215  					"org": {
   216  						TideContextPolicy: TideContextPolicy{
   217  							SkipUnknownContexts:  &no,
   218  							RequiredContexts:     []string{"r2"},
   219  							OptionalContexts:     []string{"o2"},
   220  							FromBranchProtection: &no,
   221  						},
   222  						Repos: map[string]TideRepoContextPolicy{
   223  							"repo": {
   224  								TideContextPolicy: TideContextPolicy{
   225  									SkipUnknownContexts:  &yes,
   226  									RequiredContexts:     []string{"r3"},
   227  									OptionalContexts:     []string{"o3"},
   228  									FromBranchProtection: &yes,
   229  								},
   230  							},
   231  						},
   232  					},
   233  				},
   234  			},
   235  			expected: TideContextPolicy{
   236  				SkipUnknownContexts:  &yes,
   237  				RequiredContexts:     []string{"r1", "r2", "r3"},
   238  				OptionalContexts:     []string{"o1", "o2", "o3"},
   239  				FromBranchProtection: &yes,
   240  			},
   241  		},
   242  		{
   243  			name: "branch config",
   244  			config: TideContextPolicyOptions{
   245  				TideContextPolicy: TideContextPolicy{
   246  					RequiredContexts: []string{"r1"},
   247  					OptionalContexts: []string{"o1"},
   248  				},
   249  				Orgs: map[string]TideOrgContextPolicy{
   250  					"org": {
   251  						TideContextPolicy: TideContextPolicy{
   252  							RequiredContexts: []string{"r2"},
   253  							OptionalContexts: []string{"o2"},
   254  						},
   255  						Repos: map[string]TideRepoContextPolicy{
   256  							"repo": {
   257  								TideContextPolicy: TideContextPolicy{
   258  									RequiredContexts: []string{"r3"},
   259  									OptionalContexts: []string{"o3"},
   260  								},
   261  								Branches: map[string]TideContextPolicy{
   262  									"branch": {
   263  										RequiredContexts: []string{"r4"},
   264  										OptionalContexts: []string{"o4"},
   265  									},
   266  								},
   267  							},
   268  						},
   269  					},
   270  				},
   271  			},
   272  			expected: TideContextPolicy{
   273  				RequiredContexts: []string{"r1", "r2", "r3", "r4"},
   274  				OptionalContexts: []string{"o1", "o2", "o3", "o4"},
   275  			},
   276  		},
   277  	}
   278  	for _, tc := range testCases {
   279  		policy := parseTideContextPolicyOptions(org, repo, branch, tc.config)
   280  		if !reflect.DeepEqual(policy, tc.expected) {
   281  			t.Errorf("%s - expected %v got %v", tc.name, tc.expected, policy)
   282  		}
   283  	}
   284  }
   285  
   286  func TestConfigGetTideContextPolicy(t *testing.T) {
   287  	yes := true
   288  	no := false
   289  	org, repo, branch := "org", "repo", "branch"
   290  	testCases := []struct {
   291  		name     string
   292  		config   Config
   293  		expected TideContextPolicy
   294  		error    string
   295  	}{
   296  		{
   297  			name: "no policy - use prow jobs",
   298  			config: Config{
   299  				ProwConfig: ProwConfig{
   300  					BranchProtection: BranchProtection{
   301  						Policy: Policy{
   302  							Protect: &yes,
   303  							RequiredStatusChecks: &ContextPolicy{
   304  								Contexts: []string{"r1", "r2"},
   305  							},
   306  						},
   307  					},
   308  				},
   309  				JobConfig: JobConfig{
   310  					Presubmits: map[string][]Presubmit{
   311  						"org/repo": {
   312  							Presubmit{
   313  								Context:   "pr1",
   314  								AlwaysRun: true,
   315  							},
   316  							Presubmit{
   317  								Context:   "po1",
   318  								AlwaysRun: true,
   319  								Optional:  true,
   320  							},
   321  						},
   322  					},
   323  				},
   324  			},
   325  			expected: TideContextPolicy{
   326  				RequiredContexts: []string{"pr1"},
   327  				OptionalContexts: []string{"po1"},
   328  			},
   329  		},
   330  		{
   331  			name: "no policy no prow jobs defined - empty",
   332  			config: Config{
   333  				ProwConfig: ProwConfig{
   334  					BranchProtection: BranchProtection{
   335  						Policy: Policy{
   336  							Protect: &yes,
   337  							RequiredStatusChecks: &ContextPolicy{
   338  								Contexts: []string{"r1", "r2"},
   339  							},
   340  						},
   341  					},
   342  				},
   343  			},
   344  			expected: TideContextPolicy{
   345  				RequiredContexts: []string{},
   346  				OptionalContexts: []string{},
   347  			},
   348  		},
   349  		{
   350  			name: "no branch protection",
   351  			config: Config{
   352  				ProwConfig: ProwConfig{
   353  					Tide: Tide{
   354  						ContextOptions: TideContextPolicyOptions{
   355  							TideContextPolicy: TideContextPolicy{
   356  								FromBranchProtection: &yes,
   357  							},
   358  						},
   359  					},
   360  				},
   361  			},
   362  			expected: TideContextPolicy{
   363  				RequiredContexts: []string{},
   364  				OptionalContexts: []string{},
   365  			},
   366  		},
   367  		{
   368  			name: "invalid branch protection",
   369  			config: Config{
   370  				ProwConfig: ProwConfig{
   371  					BranchProtection: BranchProtection{
   372  						Orgs: map[string]Org{
   373  							"org": {
   374  								Policy: Policy{
   375  									Protect: &no,
   376  								},
   377  							},
   378  						},
   379  					},
   380  					Tide: Tide{
   381  						ContextOptions: TideContextPolicyOptions{
   382  							TideContextPolicy: TideContextPolicy{
   383  								FromBranchProtection: &yes,
   384  							},
   385  						},
   386  					},
   387  				},
   388  			},
   389  			expected: TideContextPolicy{
   390  				RequiredContexts: []string{},
   391  				OptionalContexts: []string{},
   392  			},
   393  		},
   394  		{
   395  			name: "manually defined policy",
   396  			config: Config{
   397  				ProwConfig: ProwConfig{
   398  					Tide: Tide{
   399  						ContextOptions: TideContextPolicyOptions{
   400  							TideContextPolicy: TideContextPolicy{
   401  								RequiredContexts:    []string{"r1"},
   402  								OptionalContexts:    []string{"o1"},
   403  								SkipUnknownContexts: &yes,
   404  							},
   405  						},
   406  					},
   407  				},
   408  			},
   409  			expected: TideContextPolicy{
   410  				RequiredContexts:    []string{"r1"},
   411  				OptionalContexts:    []string{"o1"},
   412  				SkipUnknownContexts: &yes,
   413  			},
   414  		},
   415  	}
   416  
   417  	for _, tc := range testCases {
   418  		p, err := tc.config.GetTideContextPolicy(org, repo, branch)
   419  		if !reflect.DeepEqual(p, &tc.expected) {
   420  			t.Errorf("%s - expected contexts %v got %v", tc.name, &tc.expected, p)
   421  		}
   422  		if err != nil {
   423  			if err.Error() != tc.error {
   424  				t.Errorf("%s - expected error %v got %v", tc.name, tc.error, err.Error())
   425  			}
   426  		} else if tc.error != "" {
   427  			t.Errorf("%s - expected error %v got nil", tc.name, tc.error)
   428  		}
   429  	}
   430  }
   431  
   432  func TestMergeTideContextPolicyConfig(t *testing.T) {
   433  	yes := true
   434  	no := false
   435  	testCases := []struct {
   436  		name    string
   437  		a, b, c TideContextPolicy
   438  	}{
   439  		{
   440  			name: "all empty",
   441  		},
   442  		{
   443  			name: "empty a",
   444  			b: TideContextPolicy{
   445  				SkipUnknownContexts:  &yes,
   446  				FromBranchProtection: &no,
   447  				RequiredContexts:     []string{"r1"},
   448  				OptionalContexts:     []string{"o1"},
   449  			},
   450  			c: TideContextPolicy{
   451  				SkipUnknownContexts:  &yes,
   452  				FromBranchProtection: &no,
   453  				RequiredContexts:     []string{"r1"},
   454  				OptionalContexts:     []string{"o1"},
   455  			},
   456  		},
   457  		{
   458  			name: "empty b",
   459  			a: TideContextPolicy{
   460  				SkipUnknownContexts:  &yes,
   461  				FromBranchProtection: &no,
   462  				RequiredContexts:     []string{"r1"},
   463  				OptionalContexts:     []string{"o1"},
   464  			},
   465  			c: TideContextPolicy{
   466  				SkipUnknownContexts:  &yes,
   467  				FromBranchProtection: &no,
   468  				RequiredContexts:     []string{"r1"},
   469  				OptionalContexts:     []string{"o1"},
   470  			},
   471  		},
   472  		{
   473  			name: "merging unset boolean",
   474  			a: TideContextPolicy{
   475  				FromBranchProtection: &no,
   476  				RequiredContexts:     []string{"r1"},
   477  				OptionalContexts:     []string{"o1"},
   478  			},
   479  			b: TideContextPolicy{
   480  				SkipUnknownContexts: &yes,
   481  				RequiredContexts:    []string{"r2"},
   482  				OptionalContexts:    []string{"o2"},
   483  			},
   484  			c: TideContextPolicy{
   485  				SkipUnknownContexts:  &yes,
   486  				FromBranchProtection: &no,
   487  				RequiredContexts:     []string{"r1", "r2"},
   488  				OptionalContexts:     []string{"o1", "o2"},
   489  			},
   490  		},
   491  		{
   492  			name: "merging unset contexts in a",
   493  			a: TideContextPolicy{
   494  				FromBranchProtection: &no,
   495  				SkipUnknownContexts:  &yes,
   496  			},
   497  			b: TideContextPolicy{
   498  				FromBranchProtection: &yes,
   499  				SkipUnknownContexts:  &no,
   500  				RequiredContexts:     []string{"r1"},
   501  				OptionalContexts:     []string{"o1"},
   502  			},
   503  			c: TideContextPolicy{
   504  				FromBranchProtection: &yes,
   505  				SkipUnknownContexts:  &no,
   506  				RequiredContexts:     []string{"r1"},
   507  				OptionalContexts:     []string{"o1"},
   508  			},
   509  		},
   510  		{
   511  			name: "merging unset contexts in b",
   512  			a: TideContextPolicy{
   513  				FromBranchProtection: &yes,
   514  				SkipUnknownContexts:  &no,
   515  				RequiredContexts:     []string{"r1"},
   516  				OptionalContexts:     []string{"o1"},
   517  			},
   518  			b: TideContextPolicy{
   519  				FromBranchProtection: &no,
   520  				SkipUnknownContexts:  &yes,
   521  			},
   522  			c: TideContextPolicy{
   523  				FromBranchProtection: &no,
   524  				SkipUnknownContexts:  &yes,
   525  				RequiredContexts:     []string{"r1"},
   526  				OptionalContexts:     []string{"o1"},
   527  			},
   528  		},
   529  	}
   530  
   531  	for _, tc := range testCases {
   532  		c := mergeTideContextPolicy(tc.a, tc.b)
   533  		if !reflect.DeepEqual(c, tc.c) {
   534  			t.Errorf("%s - expected %v got %v", tc.name, tc.c, c)
   535  		}
   536  	}
   537  }
   538  
   539  func TestTideQuery_Validate(t *testing.T) {
   540  	testCases := []struct {
   541  		name        string
   542  		query       TideQuery
   543  		expectError bool
   544  	}{
   545  		{
   546  			name: "good query",
   547  			query: TideQuery{
   548  				Orgs:                   []string{"kuber"},
   549  				Repos:                  []string{"foo/bar", "baz/bar"},
   550  				ExcludedRepos:          []string{"kuber/netes"},
   551  				IncludedBranches:       []string{"master"},
   552  				Milestone:              "backlog-forever",
   553  				Labels:                 []string{labels.LGTM, labels.Approved},
   554  				MissingLabels:          []string{"do-not-merge/evil-code"},
   555  				ReviewApprovedRequired: true,
   556  			},
   557  			expectError: false,
   558  		},
   559  		{
   560  			name: "simple org query is valid",
   561  			query: TideQuery{
   562  				Orgs: []string{"kuber"},
   563  			},
   564  			expectError: false,
   565  		},
   566  		{
   567  			name: "org with slash is invalid",
   568  			query: TideQuery{
   569  				Orgs: []string{"kube/r"},
   570  			},
   571  			expectError: true,
   572  		},
   573  		{
   574  			name: "empty org is invalid",
   575  			query: TideQuery{
   576  				Orgs: []string{""},
   577  			},
   578  			expectError: true,
   579  		},
   580  		{
   581  			name: "duplicate org is invalid",
   582  			query: TideQuery{
   583  				Orgs: []string{"kuber", "kuber"},
   584  			},
   585  			expectError: true,
   586  		},
   587  		{
   588  			name: "simple repo query is valid",
   589  			query: TideQuery{
   590  				Repos: []string{"kuber/netes"},
   591  			},
   592  			expectError: false,
   593  		},
   594  		{
   595  			name: "repo without slash is invalid",
   596  			query: TideQuery{
   597  				Repos: []string{"foobar", "baz/bar"},
   598  			},
   599  			expectError: true,
   600  		},
   601  		{
   602  			name: "repo included with parent org is invalid",
   603  			query: TideQuery{
   604  				Orgs:  []string{"kuber"},
   605  				Repos: []string{"foo/bar", "kuber/netes"},
   606  			},
   607  			expectError: true,
   608  		},
   609  		{
   610  			name: "duplicate repo is invalid",
   611  			query: TideQuery{
   612  				Repos: []string{"baz/bar", "foo/bar", "baz/bar"},
   613  			},
   614  			expectError: true,
   615  		},
   616  		{
   617  			name: "empty orgs and repos is invalid",
   618  			query: TideQuery{
   619  				IncludedBranches:       []string{"master"},
   620  				Milestone:              "backlog-forever",
   621  				Labels:                 []string{labels.LGTM, labels.Approved},
   622  				MissingLabels:          []string{"do-not-merge/evil-code"},
   623  				ReviewApprovedRequired: true,
   624  			},
   625  			expectError: true,
   626  		},
   627  		{
   628  			name: "simple excluded repo query is valid",
   629  			query: TideQuery{
   630  				Orgs:          []string{"kuber"},
   631  				ExcludedRepos: []string{"kuber/netes"},
   632  			},
   633  			expectError: false,
   634  		},
   635  		{
   636  			name: "excluded repo without slash is invalid",
   637  			query: TideQuery{
   638  				Orgs:          []string{"kuber"},
   639  				ExcludedRepos: []string{"kubernetes"},
   640  			},
   641  			expectError: true,
   642  		},
   643  		{
   644  			name: "excluded repo included without parent org is invalid",
   645  			query: TideQuery{
   646  				Repos:         []string{"foo/bar", "baz/bar"},
   647  				ExcludedRepos: []string{"kuber/netes"},
   648  			},
   649  			expectError: true,
   650  		},
   651  		{
   652  			name: "duplicate excluded repo is invalid",
   653  			query: TideQuery{
   654  				Orgs:                   []string{"kuber"},
   655  				ExcludedRepos:          []string{"kuber/netes", "kuber/netes"},
   656  				ReviewApprovedRequired: true,
   657  			},
   658  			expectError: true,
   659  		},
   660  		{
   661  			name: "label cannot be required and forbidden",
   662  			query: TideQuery{
   663  				Orgs:          []string{"kuber"},
   664  				Labels:        []string{labels.LGTM, labels.Approved},
   665  				MissingLabels: []string{"do-not-merge/evil-code", labels.LGTM},
   666  			},
   667  			expectError: true,
   668  		},
   669  		{
   670  			name: "simple excluded branches query is valid",
   671  			query: TideQuery{
   672  				Orgs:             []string{"kuber"},
   673  				ExcludedBranches: []string{"dev"},
   674  			},
   675  			expectError: false,
   676  		},
   677  		{
   678  			name: "specifying both included and excluded branches is invalid",
   679  			query: TideQuery{
   680  				Orgs:             []string{"kuber"},
   681  				IncludedBranches: []string{"master"},
   682  				ExcludedBranches: []string{"dev"},
   683  			},
   684  			expectError: true,
   685  		},
   686  	}
   687  	for _, tc := range testCases {
   688  		t.Run(tc.name, func(t *testing.T) {
   689  			err := tc.query.Validate()
   690  			if err != nil && !tc.expectError {
   691  				t.Errorf("Unexpected error: %v.", err)
   692  			} else if err == nil && tc.expectError {
   693  				t.Error("Expected a validation error, but didn't get one.")
   694  			}
   695  		})
   696  
   697  	}
   698  }
   699  
   700  func TestTideContextPolicy_Validate(t *testing.T) {
   701  	testCases := []struct {
   702  		name   string
   703  		t      TideContextPolicy
   704  		failed bool
   705  	}{
   706  		{
   707  			name: "good policy",
   708  			t: TideContextPolicy{
   709  				OptionalContexts: []string{"o1"},
   710  				RequiredContexts: []string{"r1"},
   711  			},
   712  		},
   713  		{
   714  			name: "optional contexts must differ from required contexts",
   715  			t: TideContextPolicy{
   716  				OptionalContexts: []string{"c1"},
   717  				RequiredContexts: []string{"c1"},
   718  			},
   719  			failed: true,
   720  		},
   721  		{
   722  			name: "individual contexts cannot be both optional and required",
   723  			t: TideContextPolicy{
   724  				OptionalContexts: []string{"c1", "c2", "c3", "c4"},
   725  				RequiredContexts: []string{"c1", "c4"},
   726  			},
   727  			failed: true,
   728  		},
   729  	}
   730  	for _, tc := range testCases {
   731  		err := tc.t.Validate()
   732  		failed := err != nil
   733  		if failed != tc.failed {
   734  			t.Errorf("%s - expected %v got %v", tc.name, tc.failed, err)
   735  		}
   736  	}
   737  }
   738  
   739  func TestTideContextPolicy_IsOptional(t *testing.T) {
   740  	testCases := []struct {
   741  		name                string
   742  		skipUnknownContexts bool
   743  		required, optional  []string
   744  		contexts            []string
   745  		results             []bool
   746  	}{
   747  		{
   748  			name:     "only optional contexts registered - skipUnknownContexts false",
   749  			contexts: []string{"c1", "o1", "o2"},
   750  			optional: []string{"o1", "o2"},
   751  			results:  []bool{false, true, true},
   752  		},
   753  		{
   754  			name:     "no contexts registered - skipUnknownContexts false",
   755  			contexts: []string{"t2"},
   756  			results:  []bool{false},
   757  		},
   758  		{
   759  			name:     "only required contexts registered - skipUnknownContexts false",
   760  			required: []string{"c1", "c2", "c3"},
   761  			contexts: []string{"c1", "c2", "c3", "t1"},
   762  			results:  []bool{false, false, false, false},
   763  		},
   764  		{
   765  			name:     "optional and required contexts registered - skipUnknownContexts false",
   766  			optional: []string{"o1", "o2"},
   767  			required: []string{"c1", "c2", "c3"},
   768  			contexts: []string{"o1", "o2", "c1", "c2", "c3", "t1"},
   769  			results:  []bool{true, true, false, false, false, false},
   770  		},
   771  		{
   772  			name:                "only optional contexts registered - skipUnknownContexts true",
   773  			contexts:            []string{"c1", "o1", "o2"},
   774  			optional:            []string{"o1", "o2"},
   775  			skipUnknownContexts: true,
   776  			results:             []bool{true, true, true},
   777  		},
   778  		{
   779  			name:                "no contexts registered - skipUnknownContexts true",
   780  			contexts:            []string{"t2"},
   781  			skipUnknownContexts: true,
   782  			results:             []bool{true},
   783  		},
   784  		{
   785  			name:                "only required contexts registered - skipUnknownContexts true",
   786  			required:            []string{"c1", "c2", "c3"},
   787  			contexts:            []string{"c1", "c2", "c3", "t1"},
   788  			skipUnknownContexts: true,
   789  			results:             []bool{false, false, false, true},
   790  		},
   791  		{
   792  			name:                "optional and required contexts registered - skipUnknownContexts true",
   793  			optional:            []string{"o1", "o2"},
   794  			required:            []string{"c1", "c2", "c3"},
   795  			contexts:            []string{"o1", "o2", "c1", "c2", "c3", "t1"},
   796  			skipUnknownContexts: true,
   797  			results:             []bool{true, true, false, false, false, true},
   798  		},
   799  	}
   800  
   801  	for _, tc := range testCases {
   802  		cp := TideContextPolicy{
   803  			SkipUnknownContexts: &tc.skipUnknownContexts,
   804  			RequiredContexts:    tc.required,
   805  			OptionalContexts:    tc.optional,
   806  		}
   807  		for i, c := range tc.contexts {
   808  			if cp.IsOptional(c) != tc.results[i] {
   809  				t.Errorf("%s - IsOptional for %s should return %t", tc.name, c, tc.results[i])
   810  			}
   811  		}
   812  	}
   813  }
   814  
   815  func TestTideContextPolicy_MissingRequiredContexts(t *testing.T) {
   816  	testCases := []struct {
   817  		name                               string
   818  		skipUnknownContexts                bool
   819  		required, optional                 []string
   820  		existingContexts, expectedContexts []string
   821  	}{
   822  		{
   823  			name:             "no contexts registered",
   824  			existingContexts: []string{"c1", "c2"},
   825  		},
   826  		{
   827  			name:             "optional contexts registered / no missing contexts",
   828  			optional:         []string{"o1", "o2", "o3"},
   829  			existingContexts: []string{"c1", "c2"},
   830  		},
   831  		{
   832  			name:             "required  contexts registered / missing contexts",
   833  			required:         []string{"c1", "c2", "c3"},
   834  			existingContexts: []string{"c1", "c2"},
   835  			expectedContexts: []string{"c3"},
   836  		},
   837  		{
   838  			name:             "required contexts registered / no missing contexts",
   839  			required:         []string{"c1", "c2", "c3"},
   840  			existingContexts: []string{"c1", "c2", "c3"},
   841  		},
   842  		{
   843  			name:             "optional and required contexts registered / missing contexts",
   844  			optional:         []string{"o1", "o2", "o3"},
   845  			required:         []string{"c1", "c2", "c3"},
   846  			existingContexts: []string{"c1", "c2"},
   847  			expectedContexts: []string{"c3"},
   848  		},
   849  		{
   850  			name:             "optional and required contexts registered / no missing contexts",
   851  			optional:         []string{"o1", "o2", "o3"},
   852  			required:         []string{"c1", "c2"},
   853  			existingContexts: []string{"c1", "c2", "c4"},
   854  		},
   855  	}
   856  
   857  	for _, tc := range testCases {
   858  		cp := TideContextPolicy{
   859  			SkipUnknownContexts: &tc.skipUnknownContexts,
   860  			RequiredContexts:    tc.required,
   861  			OptionalContexts:    tc.optional,
   862  		}
   863  		missingContexts := cp.MissingRequiredContexts(tc.existingContexts)
   864  		if !sets.NewString(missingContexts...).Equal(sets.NewString(tc.expectedContexts...)) {
   865  			t.Errorf("%s - expected %v got %v", tc.name, tc.expectedContexts, missingContexts)
   866  		}
   867  	}
   868  }