sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/flagutil/github_test.go (about)

     1  /*
     2  Copyright 2020 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 flagutil
    18  
    19  import (
    20  	"flag"
    21  	"fmt"
    22  	"reflect"
    23  	"strconv"
    24  	"testing"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  
    28  	"sigs.k8s.io/prow/pkg/github"
    29  )
    30  
    31  func TestGitHubOptions_Validate(t *testing.T) {
    32  	t.Parallel()
    33  	var testCases = []struct {
    34  		name                    string
    35  		in                      *GitHubOptions
    36  		expectedGraphqlEndpoint string
    37  		expectedErr             bool
    38  	}{
    39  		{
    40  			name:                    "when no endpoints, sets graphql endpoint",
    41  			in:                      &GitHubOptions{},
    42  			expectedGraphqlEndpoint: github.DefaultGraphQLEndpoint,
    43  			expectedErr:             false,
    44  		},
    45  		{
    46  			name: "when empty endpoint, sets graphql endpoint",
    47  			in: &GitHubOptions{
    48  				endpoint: NewStrings(""),
    49  			},
    50  			expectedGraphqlEndpoint: github.DefaultGraphQLEndpoint,
    51  			expectedErr:             false,
    52  		},
    53  		{
    54  			name: "when invalid github endpoint, returns error",
    55  			in: &GitHubOptions{
    56  				endpoint: NewStrings("not a github url"),
    57  			},
    58  			expectedErr: true,
    59  		},
    60  		{
    61  			name: "both --github-hourly-tokens and --github-allowed-burst are zero: no error",
    62  			in: &GitHubOptions{
    63  				ThrottleHourlyTokens: 0,
    64  				ThrottleAllowBurst:   0,
    65  			},
    66  			expectedGraphqlEndpoint: github.DefaultGraphQLEndpoint,
    67  		},
    68  		{
    69  			name: "both --github-hourly-tokens and --github-allowed-burst are nonzero and hourly is higher or equal: no error",
    70  			in: &GitHubOptions{
    71  				ThrottleHourlyTokens: 100,
    72  				ThrottleAllowBurst:   100,
    73  			},
    74  			expectedGraphqlEndpoint: github.DefaultGraphQLEndpoint,
    75  		},
    76  		{
    77  			name: "both --github-hourly-tokens and --github-allowed-burst are nonzero and hourly is lower: error",
    78  			in: &GitHubOptions{
    79  				ThrottleHourlyTokens: 10,
    80  				ThrottleAllowBurst:   100,
    81  			},
    82  			expectedGraphqlEndpoint: github.DefaultGraphQLEndpoint,
    83  			expectedErr:             true,
    84  		},
    85  		{
    86  			name: "only --github-hourly-tokens is nonzero: error",
    87  			in: &GitHubOptions{
    88  				ThrottleHourlyTokens: 10,
    89  			},
    90  			expectedGraphqlEndpoint: github.DefaultGraphQLEndpoint,
    91  			expectedErr:             true,
    92  		},
    93  		{
    94  			name: "only --github-hourly-tokens is zero: no error, allows easier throttling disable",
    95  			in: &GitHubOptions{
    96  				ThrottleAllowBurst: 10,
    97  			},
    98  			expectedGraphqlEndpoint: github.DefaultGraphQLEndpoint,
    99  			expectedErr:             false,
   100  		},
   101  	}
   102  
   103  	for _, testCase := range testCases {
   104  		t.Run(testCase.name, func(s *testing.T) {
   105  			err := testCase.in.Validate(false)
   106  			if testCase.expectedErr && err == nil {
   107  				t.Errorf("%s: expected an error but got none", testCase.name)
   108  			}
   109  			if !testCase.expectedErr && err != nil {
   110  				t.Errorf("%s: expected no error but got one: %v", testCase.name, err)
   111  			}
   112  			if testCase.expectedGraphqlEndpoint != testCase.in.graphqlEndpoint {
   113  				t.Errorf("%s: unexpected graphql endpoint", testCase.name)
   114  			}
   115  		})
   116  	}
   117  }
   118  
   119  // TestGitHubOptionsConstructsANewClientOnEachInvocation verifies that multiple invocations do not
   120  // return the same client. This is important for components that use multiple clients with different
   121  // settings, like for example for the throttling.
   122  func TestGitHubOptionsConstructsANewClientOnEachInvocation(t *testing.T) {
   123  	o := &GitHubOptions{}
   124  
   125  	firstClient, err := o.githubClient(false)
   126  	if err != nil {
   127  		t.Fatalf("failed to construct first client: %v", err)
   128  	}
   129  	secondClient, err := o.githubClient(false)
   130  	if err != nil {
   131  		t.Fatalf("failed to construct second client: %v", err)
   132  	}
   133  
   134  	firstClientAddr, secondClientAddr := fmt.Sprintf("%p", firstClient), fmt.Sprintf("%p", secondClient)
   135  	if firstClientAddr == secondClientAddr {
   136  		t.Error("got the same client twice on subsequent invocation")
   137  	}
   138  }
   139  
   140  func TestCustomThrottlerOptions(t *testing.T) {
   141  	t.Parallel()
   142  	testCases := []struct {
   143  		name   string
   144  		params []FlagParameter
   145  
   146  		expectPresent map[string]bool
   147  		expectDefault map[string]int
   148  	}{
   149  		{
   150  			name:          "no customizations",
   151  			expectPresent: map[string]bool{"github-hourly-tokens": true, "github-allowed-burst": true},
   152  			expectDefault: map[string]int{"github-hourly-tokens": 0, "github-allowed-burst": 0},
   153  		},
   154  		{
   155  			name:          "suppress presence",
   156  			params:        []FlagParameter{DisableThrottlerOptions()},
   157  			expectPresent: map[string]bool{"github-hourly-tokens": false, "github-allowed-burst": false},
   158  		},
   159  		{
   160  			name:          "custom defaults",
   161  			params:        []FlagParameter{ThrottlerDefaults(100, 20)},
   162  			expectPresent: map[string]bool{"github-hourly-tokens": true, "github-allowed-burst": true},
   163  			expectDefault: map[string]int{"github-hourly-tokens": 100, "github-allowed-burst": 20},
   164  		},
   165  	}
   166  
   167  	for _, tc := range testCases {
   168  		t.Run(tc.name, func(t *testing.T) {
   169  			fs := flag.NewFlagSet(tc.name, flag.ExitOnError)
   170  			opts := &GitHubOptions{}
   171  			opts.AddCustomizedFlags(fs, tc.params...)
   172  			for _, name := range []string{"github-hourly-tokens", "github-allowed-burst"} {
   173  				flg := fs.Lookup(name)
   174  				if (flg != nil) != (tc.expectPresent[name]) {
   175  					t.Errorf("Flag --%s presence differs: expected %t got %t", name, tc.expectPresent[name], flg != nil)
   176  					continue
   177  				}
   178  				expected := strconv.Itoa(tc.expectDefault[name])
   179  				if flg != nil && flg.DefValue != expected {
   180  					t.Errorf("Flag --%s default value differs: expected %#v got '%#v'", name, expected, flg.DefValue)
   181  				}
   182  			}
   183  		})
   184  	}
   185  }
   186  
   187  func TestOrgThottlerOptions(t *testing.T) {
   188  	t.Parallel()
   189  	testCases := []struct {
   190  		name       string
   191  		parameters []string
   192  
   193  		expectedErrorMsg            string
   194  		expectedParsedOrgThrottlers map[string]throttlerSettings
   195  	}{
   196  		{
   197  			name: "No org throttler, success",
   198  		},
   199  		{
   200  			name:             "Invalid format, a colon too much",
   201  			parameters:       []string{"--github-throttle-org=kubernetes:10:10:10"},
   202  			expectedErrorMsg: "-github-throttle-org=kubernetes:10:10:10 is not in org:hourlyTokens:burst format",
   203  		},
   204  		{
   205  			name:             "Invalid format, a colon too little",
   206  			parameters:       []string{"--github-throttle-org=kubernetes:10"},
   207  			expectedErrorMsg: "-github-throttle-org=kubernetes:10 is not in org:hourlyTokens:burst format",
   208  		},
   209  		{
   210  			name:             "Invalid format, hourly tokens not an int",
   211  			parameters:       []string{"--github-throttle-org=kubernetes:a:10"},
   212  			expectedErrorMsg: "-github-throttle-org=kubernetes:a:10 is not in org:hourlyTokens:burst format: hourlyTokens is not an int",
   213  		},
   214  		{
   215  			name:             "Invalid format, burst not an int",
   216  			parameters:       []string{"--github-throttle-org=kubernetes:10:a"},
   217  			expectedErrorMsg: "-github-throttle-org=kubernetes:10:a is not in org:hourlyTokens:burst format: burst is not an int",
   218  		},
   219  		{
   220  			name:             "Invalid, burst > hourly tokens",
   221  			parameters:       []string{"--github-throttle-org=kubernetes:10:11"},
   222  			expectedErrorMsg: "-github-throttle-org=kubernetes:10:11: burst must not be greater than hourlyTokens",
   223  		},
   224  		{
   225  			name:             "Invalid, burst < 1",
   226  			parameters:       []string{"--github-throttle-org=kubernetes:10:0"},
   227  			expectedErrorMsg: "-github-throttle-org=kubernetes:10:0: burst must be > 0",
   228  		},
   229  		{
   230  			name:             "Invalid, hourly tokens < 1",
   231  			parameters:       []string{"--github-throttle-org=kubernetes:0:10"},
   232  			expectedErrorMsg: "-github-throttle-org=kubernetes:0:10: hourlyTokens must be > 0",
   233  		},
   234  		{
   235  			name: "Invalid, multiple settings for same org",
   236  			parameters: []string{
   237  				"--github-throttle-org=kubernetes:10:10",
   238  				"--github-throttle-org=kubernetes:10:10",
   239  			},
   240  			expectedErrorMsg: "got multiple -github-throttle-org for the kubernetes org",
   241  		},
   242  		{
   243  			name:                        "Valid single org setting, success",
   244  			parameters:                  []string{"--github-throttle-org=kubernetes:10:10"},
   245  			expectedParsedOrgThrottlers: map[string]throttlerSettings{"kubernetes": {hourlyTokens: 10, burst: 10}},
   246  		},
   247  		{
   248  			name: "Valid settings for multiple orgs, success",
   249  			parameters: []string{
   250  				"--github-throttle-org=kubernetes:10:10",
   251  				"--github-throttle-org=kubernetes-sigs:10:10",
   252  			},
   253  			expectedParsedOrgThrottlers: map[string]throttlerSettings{
   254  				"kubernetes":      {hourlyTokens: 10, burst: 10},
   255  				"kubernetes-sigs": {hourlyTokens: 10, burst: 10},
   256  			},
   257  		},
   258  	}
   259  
   260  	exportThrottlerSettings := cmp.Exporter(func(t reflect.Type) bool {
   261  		return t == reflect.TypeOf(throttlerSettings{})
   262  	})
   263  
   264  	for _, tc := range testCases {
   265  		t.Run(tc.name, func(t *testing.T) {
   266  			fs := flag.NewFlagSet(tc.name, flag.ContinueOnError)
   267  			opts := &GitHubOptions{}
   268  			opts.AddFlags(fs)
   269  			if err := fs.Parse(tc.parameters); err != nil {
   270  				t.Fatalf("flag parsing failed: %v", err)
   271  			}
   272  			opts.AppID = "10"
   273  			opts.AppPrivateKeyPath = "/test/path"
   274  
   275  			var actualErrMsg string
   276  			if actualErr := opts.Validate(false); actualErr != nil {
   277  				actualErrMsg = actualErr.Error()
   278  			}
   279  			if actualErrMsg != tc.expectedErrorMsg {
   280  				t.Fatalf("actual error %s does not match expected error %s", actualErrMsg, tc.expectedErrorMsg)
   281  			}
   282  			if actualErrMsg != "" {
   283  				return
   284  			}
   285  
   286  			if diff := cmp.Diff(tc.expectedParsedOrgThrottlers, opts.parsedOrgThrottlers, exportThrottlerSettings); diff != "" {
   287  				t.Errorf("expected org throttlers differ from actual: %s", diff)
   288  			}
   289  		})
   290  	}
   291  }