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 }