sigs.k8s.io/cluster-api@v1.6.3/cmd/clusterctl/client/repository/repository_github_test.go (about) 1 /* 2 Copyright 2019 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 repository 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "net/http/httptest" 24 "net/url" 25 "testing" 26 "time" 27 28 "github.com/google/go-github/v53/github" 29 . "github.com/onsi/gomega" 30 "k8s.io/utils/pointer" 31 32 clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" 33 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" 34 "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" 35 "sigs.k8s.io/cluster-api/internal/goproxy" 36 ) 37 38 func Test_gitHubRepository_GetVersions(t *testing.T) { 39 retryableOperationInterval = 200 * time.Millisecond 40 retryableOperationTimeout = 1 * time.Second 41 42 client, mux, teardown := test.NewFakeGitHub() 43 defer teardown() 44 45 // Setup an handler for returning 5 fake releases. 46 mux.HandleFunc("/repos/o/r1/releases", func(w http.ResponseWriter, r *http.Request) { 47 testMethod(t, r, "GET") 48 fmt.Fprint(w, `[`) 49 fmt.Fprint(w, `{"id":1, "tag_name": "v0.4.0"},`) 50 fmt.Fprint(w, `{"id":2, "tag_name": "v0.4.1"},`) 51 fmt.Fprint(w, `{"id":3, "tag_name": "v0.4.2"},`) 52 fmt.Fprint(w, `{"id":4, "tag_name": "v0.4.3-alpha"}`) // Pre-release 53 fmt.Fprint(w, `]`) 54 }) 55 56 clientGoproxy, muxGoproxy, teardownGoproxy := newFakeGoproxy() 57 defer teardownGoproxy() 58 59 // Setup a handler for returning 4 fake releases. 60 muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) { 61 testMethod(t, r, "GET") 62 fmt.Fprint(w, "v0.5.0\n") 63 fmt.Fprint(w, "v0.4.0\n") 64 fmt.Fprint(w, "v0.3.2\n") 65 fmt.Fprint(w, "v0.3.1\n") 66 }) 67 68 // Setup a handler for returning 3 different major fake releases. 69 muxGoproxy.HandleFunc("/github.com/o/r3/@v/list", func(w http.ResponseWriter, r *http.Request) { 70 testMethod(t, r, "GET") 71 fmt.Fprint(w, "v1.0.0\n") 72 fmt.Fprint(w, "v0.1.0\n") 73 }) 74 muxGoproxy.HandleFunc("/github.com/o/r3/v2/@v/list", func(w http.ResponseWriter, r *http.Request) { 75 testMethod(t, r, "GET") 76 fmt.Fprint(w, "v2.0.0\n") 77 }) 78 muxGoproxy.HandleFunc("/github.com/o/r3/v3/@v/list", func(w http.ResponseWriter, r *http.Request) { 79 testMethod(t, r, "GET") 80 fmt.Fprint(w, "v3.0.0\n") 81 }) 82 83 configVariablesClient := test.NewFakeVariableClient() 84 85 tests := []struct { 86 name string 87 providerConfig config.Provider 88 want []string 89 wantErr bool 90 }{ 91 { 92 name: "fallback to github", 93 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.0/path", clusterctlv1.CoreProviderType), 94 want: []string{"v0.4.0", "v0.4.1", "v0.4.2", "v0.4.3-alpha"}, 95 wantErr: false, 96 }, 97 { 98 name: "use goproxy", 99 providerConfig: config.NewProvider("test", "https://github.com/o/r2/releases/v0.4.0/path", clusterctlv1.CoreProviderType), 100 want: []string{"v0.3.1", "v0.3.2", "v0.4.0", "v0.5.0"}, 101 wantErr: false, 102 }, 103 { 104 name: "use goproxy having multiple majors", 105 providerConfig: config.NewProvider("test", "https://github.com/o/r3/releases/v3.0.0/path", clusterctlv1.CoreProviderType), 106 want: []string{"v0.1.0", "v1.0.0", "v2.0.0", "v3.0.0"}, 107 wantErr: false, 108 }, 109 { 110 name: "failure", 111 providerConfig: config.NewProvider("test", "https://github.com/o/unknown/releases/v0.4.0/path", clusterctlv1.CoreProviderType), 112 wantErr: true, 113 }, 114 } 115 for _, tt := range tests { 116 t.Run(tt.name, func(t *testing.T) { 117 g := NewWithT(t) 118 119 ctx := context.Background() 120 121 resetCaches() 122 123 gRepo, err := NewGitHubRepository(ctx, tt.providerConfig, configVariablesClient, injectGithubClient(client), injectGoproxyClient(clientGoproxy)) 124 g.Expect(err).ToNot(HaveOccurred()) 125 126 got, err := gRepo.GetVersions(ctx) 127 if tt.wantErr { 128 g.Expect(err).To(HaveOccurred()) 129 return 130 } 131 g.Expect(err).ToNot(HaveOccurred()) 132 g.Expect(got).To(Equal(tt.want)) 133 }) 134 } 135 } 136 137 func Test_githubRepository_newGitHubRepository(t *testing.T) { 138 retryableOperationInterval = 200 * time.Millisecond 139 retryableOperationTimeout = 1 * time.Second 140 type field struct { 141 providerConfig config.Provider 142 variableClient config.VariablesClient 143 } 144 tests := []struct { 145 name string 146 field field 147 want *gitHubRepository 148 wantErr bool 149 }{ 150 { 151 name: "can create a new GitHub repo", 152 field: field{ 153 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.1/path", clusterctlv1.CoreProviderType), 154 variableClient: test.NewFakeVariableClient(), 155 }, 156 want: &gitHubRepository{ 157 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.1/path", clusterctlv1.CoreProviderType), 158 configVariablesClient: test.NewFakeVariableClient(), 159 authenticatingHTTPClient: nil, 160 owner: "o", 161 repository: "r1", 162 defaultVersion: "v0.4.1", 163 rootPath: ".", 164 componentsPath: "path", 165 injectClient: nil, 166 }, 167 wantErr: false, 168 }, 169 { 170 name: "missing variableClient", 171 field: field{ 172 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.1/path", clusterctlv1.CoreProviderType), 173 variableClient: nil, 174 }, 175 want: nil, 176 wantErr: true, 177 }, 178 { 179 name: "provider url is not valid", 180 field: field{ 181 providerConfig: config.NewProvider("test", "%gh&%ij", clusterctlv1.CoreProviderType), 182 variableClient: test.NewFakeVariableClient(), 183 }, 184 want: nil, 185 wantErr: true, 186 }, 187 { 188 name: "provider url should be in https", 189 field: field{ 190 providerConfig: config.NewProvider("test", "http://github.com/blabla", clusterctlv1.CoreProviderType), 191 variableClient: test.NewFakeVariableClient(), 192 }, 193 want: nil, 194 wantErr: true, 195 }, 196 { 197 name: "provider url should be in github", 198 field: field{ 199 providerConfig: config.NewProvider("test", "http://gitlab.com/blabla", clusterctlv1.CoreProviderType), 200 variableClient: test.NewFakeVariableClient(), 201 }, 202 want: &gitHubRepository{}, 203 wantErr: true, 204 }, 205 { 206 name: "provider url should be in https://github.com/{owner}/{Repository}/%s/{latest|version-tag}/{componentsClient.yaml} format", 207 field: field{ 208 providerConfig: config.NewProvider("test", "https://github.com/dd/", clusterctlv1.CoreProviderType), 209 variableClient: test.NewFakeVariableClient(), 210 }, 211 want: nil, 212 wantErr: true, 213 }, 214 } 215 216 for _, tt := range tests { 217 t.Run(tt.name, func(t *testing.T) { 218 g := NewWithT(t) 219 resetCaches() 220 221 gitHub, err := NewGitHubRepository(context.Background(), tt.field.providerConfig, tt.field.variableClient) 222 if tt.wantErr { 223 g.Expect(err).To(HaveOccurred()) 224 return 225 } 226 227 g.Expect(err).ToNot(HaveOccurred()) 228 g.Expect(gitHub).To(Equal(tt.want)) 229 }) 230 } 231 } 232 233 func Test_githubRepository_getComponentsPath(t *testing.T) { 234 tests := []struct { 235 name string 236 path string 237 rootPath string 238 want string 239 }{ 240 { 241 name: "get the file name", 242 path: "github.com/o/r/releases/v0.4.1/file.yaml", 243 rootPath: "github.com/o/r/releases/v0.4.1/", 244 want: "file.yaml", 245 }, 246 { 247 name: "trim github.com", 248 path: "github.com/o/r/releases/v0.4.1/file.yaml", 249 rootPath: "github.com", 250 want: "o/r/releases/v0.4.1/file.yaml", 251 }, 252 } 253 254 for _, tt := range tests { 255 t.Run(tt.name, func(t *testing.T) { 256 g := NewWithT(t) 257 resetCaches() 258 259 g.Expect(getComponentsPath(tt.path, tt.rootPath)).To(Equal(tt.want)) 260 }) 261 } 262 } 263 264 func Test_githubRepository_getFile(t *testing.T) { 265 retryableOperationInterval = 200 * time.Millisecond 266 retryableOperationTimeout = 1 * time.Second 267 client, mux, teardown := test.NewFakeGitHub() 268 defer teardown() 269 270 providerConfig := config.NewProvider("test", "https://github.com/o/r/releases/v0.4.1/file.yaml", clusterctlv1.CoreProviderType) 271 272 // Setup a handler for returning a fake release. 273 mux.HandleFunc("/repos/o/r/releases/tags/v0.4.1", func(w http.ResponseWriter, r *http.Request) { 274 testMethod(t, r, "GET") 275 fmt.Fprint(w, `{"id":13, "tag_name": "v0.4.1", "assets": [{"id": 1, "name": "file.yaml"}] }`) 276 }) 277 278 // Setup a handler for returning a fake release asset. 279 mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { 280 testMethod(t, r, "GET") 281 w.Header().Set("Content-Type", "application/octet-stream") 282 w.Header().Set("Content-Disposition", "attachment; filename=file.yaml") 283 fmt.Fprint(w, "content") 284 }) 285 286 configVariablesClient := test.NewFakeVariableClient() 287 288 tests := []struct { 289 name string 290 release string 291 fileName string 292 want []byte 293 wantErr bool 294 }{ 295 { 296 name: "Release and file exist", 297 release: "v0.4.1", 298 fileName: "file.yaml", 299 want: []byte("content"), 300 wantErr: false, 301 }, 302 { 303 name: "Release does not exist", 304 release: "not-a-release", 305 fileName: "file.yaml", 306 want: nil, 307 wantErr: true, 308 }, 309 { 310 name: "File does not exist", 311 release: "v0.4.1", 312 fileName: "404.file", 313 want: nil, 314 wantErr: true, 315 }, 316 } 317 318 for _, tt := range tests { 319 t.Run(tt.name, func(t *testing.T) { 320 g := NewWithT(t) 321 resetCaches() 322 323 gitHub, err := NewGitHubRepository(context.Background(), providerConfig, configVariablesClient, injectGithubClient(client)) 324 g.Expect(err).ToNot(HaveOccurred()) 325 326 got, err := gitHub.GetFile(context.Background(), tt.release, tt.fileName) 327 if tt.wantErr { 328 g.Expect(err).To(HaveOccurred()) 329 return 330 } 331 332 g.Expect(err).ToNot(HaveOccurred()) 333 g.Expect(got).To(Equal(tt.want)) 334 }) 335 } 336 } 337 338 func Test_gitHubRepository_getVersions(t *testing.T) { 339 retryableOperationInterval = 200 * time.Millisecond 340 retryableOperationTimeout = 1 * time.Second 341 client, mux, teardown := test.NewFakeGitHub() 342 defer teardown() 343 344 // Setup a handler for returning fake releases in a paginated manner 345 // Each response contains a link to the next page (if available) which 346 // is parsed by the handler to navigate through all pages 347 mux.HandleFunc("/repos/o/r1/releases", func(w http.ResponseWriter, r *http.Request) { 348 testMethod(t, r, "GET") 349 page := r.URL.Query().Get("page") 350 switch page { 351 case "", "1": 352 // Page 1 353 w.Header().Set("Link", `<https://api.github.com/repositories/12345/releases?page=2>; rel="next"`) // Link to page 2 354 fmt.Fprint(w, `[`) 355 fmt.Fprint(w, `{"id":1, "tag_name": "v0.4.0"},`) 356 fmt.Fprint(w, `{"id":2, "tag_name": "v0.4.1"}`) 357 fmt.Fprint(w, `]`) 358 case "2": 359 // Page 2 360 w.Header().Set("Link", `<https://api.github.com/repositories/12345/releases?page=3>; rel="next"`) // Link to page 3 361 fmt.Fprint(w, `[`) 362 fmt.Fprint(w, `{"id":3, "tag_name": "v0.4.2"},`) 363 fmt.Fprint(w, `{"id":4, "tag_name": "v0.4.3-alpha"}`) // Pre-release 364 fmt.Fprint(w, `]`) 365 case "3": 366 // Page 3 (last page) 367 fmt.Fprint(w, `[`) 368 fmt.Fprint(w, `{"id":4, "tag_name": "v0.4.4-beta"},`) // Pre-release 369 fmt.Fprint(w, `{"id":5, "tag_name": "foo"}`) // No semantic version tag 370 fmt.Fprint(w, `]`) 371 default: 372 t.Fatalf("unexpected page requested") 373 } 374 }) 375 376 configVariablesClient := test.NewFakeVariableClient() 377 378 type field struct { 379 providerConfig config.Provider 380 } 381 tests := []struct { 382 name string 383 field field 384 want []string 385 wantErr bool 386 }{ 387 { 388 name: "Get versions with all releases", 389 field: field{ 390 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.1/path", clusterctlv1.CoreProviderType), 391 }, 392 want: []string{"v0.4.0", "v0.4.1", "v0.4.2", "v0.4.3-alpha", "v0.4.4-beta"}, 393 wantErr: false, 394 }, 395 } 396 for _, tt := range tests { 397 t.Run(tt.name, func(t *testing.T) { 398 g := NewWithT(t) 399 400 ctx := context.Background() 401 402 resetCaches() 403 404 gitHub, err := NewGitHubRepository(ctx, tt.field.providerConfig, configVariablesClient, injectGithubClient(client)) 405 g.Expect(err).ToNot(HaveOccurred()) 406 407 got, err := gitHub.(*gitHubRepository).getVersions(ctx) 408 if tt.wantErr { 409 g.Expect(err).To(HaveOccurred()) 410 return 411 } 412 g.Expect(err).ToNot(HaveOccurred()) 413 414 g.Expect(got).To(ConsistOf(tt.want)) 415 }) 416 } 417 } 418 419 func Test_gitHubRepository_getLatestContractRelease(t *testing.T) { 420 retryableOperationInterval = 200 * time.Millisecond 421 retryableOperationTimeout = 1 * time.Second 422 client, mux, teardown := test.NewFakeGitHub() 423 defer teardown() 424 425 // Setup a handler for returning a fake release. 426 mux.HandleFunc("/repos/o/r1/releases/tags/v0.5.0", func(w http.ResponseWriter, r *http.Request) { 427 testMethod(t, r, "GET") 428 fmt.Fprint(w, `{"id":13, "tag_name": "v0.5.0", "assets": [{"id": 1, "name": "metadata.yaml"}] }`) 429 }) 430 431 // Setup a handler for returning a fake release metadata file. 432 mux.HandleFunc("/repos/o/r1/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { 433 testMethod(t, r, "GET") 434 w.Header().Set("Content-Type", "application/octet-stream") 435 w.Header().Set("Content-Disposition", "attachment; filename=metadata.yaml") 436 fmt.Fprint(w, "apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3\nreleaseSeries:\n - major: 0\n minor: 4\n contract: v1alpha4\n - major: 0\n minor: 5\n contract: v1alpha4\n - major: 0\n minor: 3\n contract: v1alpha3\n") 437 }) 438 439 clientGoproxy, muxGoproxy, teardownGoproxy := newFakeGoproxy() 440 defer teardownGoproxy() 441 442 // Setup a handler for returning 4 fake releases. 443 muxGoproxy.HandleFunc("/github.com/o/r1/@v/list", func(w http.ResponseWriter, r *http.Request) { 444 testMethod(t, r, "GET") 445 fmt.Fprint(w, "v0.5.0\n") 446 fmt.Fprint(w, "v0.4.0\n") 447 fmt.Fprint(w, "v0.3.2\n") 448 fmt.Fprint(w, "v0.3.1\n") 449 }) 450 451 // setup an handler for returning 4 fake releases but no actual tagged release 452 muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) { 453 testMethod(t, r, "GET") 454 fmt.Fprint(w, "v0.5.0\n") 455 fmt.Fprint(w, "v0.4.0\n") 456 fmt.Fprint(w, "v0.3.2\n") 457 fmt.Fprint(w, "v0.3.1\n") 458 }) 459 460 configVariablesClient := test.NewFakeVariableClient() 461 462 type field struct { 463 providerConfig config.Provider 464 } 465 tests := []struct { 466 name string 467 field field 468 contract string 469 want string 470 wantErr bool 471 }{ 472 { 473 name: "Get latest release if it matches the contract", 474 field: field{ 475 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/latest/path", clusterctlv1.CoreProviderType), 476 }, 477 contract: "v1alpha4", 478 want: "v0.5.0", 479 wantErr: false, 480 }, 481 { 482 name: "Get previous release if the latest doesn't match the contract", 483 field: field{ 484 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/latest/path", clusterctlv1.CoreProviderType), 485 }, 486 contract: "v1alpha3", 487 want: "v0.3.2", 488 wantErr: false, 489 }, 490 { 491 name: "Return the latest release if the contract doesn't exist", 492 field: field{ 493 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/latest/path", clusterctlv1.CoreProviderType), 494 }, 495 want: "v0.5.0", 496 contract: "foo", 497 wantErr: false, 498 }, 499 { 500 name: "Return 404 if there is no release for the tag", 501 field: field{ 502 providerConfig: config.NewProvider("test", "https://github.com/o/r2/releases/v0.99.0/path", clusterctlv1.CoreProviderType), 503 }, 504 want: "0.99.0", 505 contract: "v1alpha4", 506 wantErr: true, 507 }, 508 } 509 for _, tt := range tests { 510 t.Run(tt.name, func(t *testing.T) { 511 g := NewWithT(t) 512 resetCaches() 513 514 gRepo, err := NewGitHubRepository(context.Background(), tt.field.providerConfig, configVariablesClient, injectGithubClient(client), injectGoproxyClient(clientGoproxy)) 515 g.Expect(err).ToNot(HaveOccurred()) 516 517 got, err := latestContractRelease(context.Background(), gRepo, tt.contract) 518 if tt.wantErr { 519 g.Expect(err).To(HaveOccurred()) 520 return 521 } 522 g.Expect(err).ToNot(HaveOccurred()) 523 g.Expect(got).To(Equal(tt.want)) 524 }) 525 } 526 } 527 528 func Test_gitHubRepository_getLatestRelease(t *testing.T) { 529 retryableOperationInterval = 200 * time.Millisecond 530 retryableOperationTimeout = 1 * time.Second 531 clientGoproxy, muxGoproxy, teardownGoproxy := newFakeGoproxy() 532 defer teardownGoproxy() 533 534 // Setup a handler for returning 4 fake releases. 535 muxGoproxy.HandleFunc("/github.com/o/r1/@v/list", func(w http.ResponseWriter, r *http.Request) { 536 testMethod(t, r, "GET") 537 fmt.Fprint(w, "v0.4.1\n") 538 fmt.Fprint(w, "v0.4.2\n") 539 fmt.Fprint(w, "v0.4.3-alpha\n") // prerelease 540 fmt.Fprint(w, "foo\n") // no semantic version tag 541 }) 542 // And also expose a release for them 543 muxGoproxy.HandleFunc("/api.github.com/repos/o/r1/releases/tags/v0.4.2", func(w http.ResponseWriter, r *http.Request) { 544 testMethod(t, r, "GET") 545 fmt.Fprint(w, "{}\n") 546 }) 547 548 // Setup a handler for returning no releases. 549 muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) { 550 testMethod(t, r, "GET") 551 // no releases 552 }) 553 554 // Setup a handler for returning fake prereleases only. 555 muxGoproxy.HandleFunc("/github.com/o/r3/@v/list", func(w http.ResponseWriter, r *http.Request) { 556 testMethod(t, r, "GET") 557 fmt.Fprint(w, "v0.1.0-alpha.0\n") 558 fmt.Fprint(w, "v0.1.0-alpha.1\n") 559 fmt.Fprint(w, "v0.1.0-alpha.2\n") 560 }) 561 562 configVariablesClient := test.NewFakeVariableClient() 563 564 type field struct { 565 providerConfig config.Provider 566 } 567 tests := []struct { 568 name string 569 field field 570 want string 571 wantErr bool 572 }{ 573 { 574 name: "Get latest release, ignores pre-release version", 575 field: field{ 576 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.2/path", clusterctlv1.CoreProviderType), 577 }, 578 want: "v0.4.2", 579 wantErr: false, 580 }, 581 { 582 name: "Fails, when no release found", 583 field: field{ 584 providerConfig: config.NewProvider("test", "https://github.com/o/r2/releases/v0.4.1/path", clusterctlv1.CoreProviderType), 585 }, 586 want: "", 587 wantErr: true, 588 }, 589 { 590 name: "Falls back to latest prerelease when no official release present", 591 field: field{ 592 providerConfig: config.NewProvider("test", "https://github.com/o/r3/releases/v0.1.0-alpha.2/path", clusterctlv1.CoreProviderType), 593 }, 594 want: "v0.1.0-alpha.2", 595 wantErr: false, 596 }, 597 } 598 for _, tt := range tests { 599 t.Run(tt.name, func(t *testing.T) { 600 g := NewWithT(t) 601 602 ctx := context.Background() 603 604 resetCaches() 605 606 gRepo, err := NewGitHubRepository(ctx, tt.field.providerConfig, configVariablesClient, injectGoproxyClient(clientGoproxy)) 607 g.Expect(err).ToNot(HaveOccurred()) 608 609 got, err := latestRelease(ctx, gRepo) 610 if tt.wantErr { 611 g.Expect(err).To(HaveOccurred()) 612 return 613 } 614 g.Expect(err).ToNot(HaveOccurred()) 615 g.Expect(got).To(Equal(tt.want)) 616 g.Expect(gRepo.(*gitHubRepository).defaultVersion).To(Equal(tt.want)) 617 }) 618 } 619 } 620 621 func Test_gitHubRepository_getLatestPatchRelease(t *testing.T) { 622 retryableOperationInterval = 200 * time.Millisecond 623 retryableOperationTimeout = 1 * time.Second 624 clientGoproxy, muxGoproxy, teardownGoproxy := newFakeGoproxy() 625 defer teardownGoproxy() 626 627 // Setup a handler for returning 4 fake releases. 628 muxGoproxy.HandleFunc("/github.com/o/r1/@v/list", func(w http.ResponseWriter, r *http.Request) { 629 testMethod(t, r, "GET") 630 fmt.Fprint(w, "v0.4.0\n") 631 fmt.Fprint(w, "v0.3.2\n") 632 fmt.Fprint(w, "v1.3.2\n") 633 }) 634 635 major0 := uint(0) 636 minor3 := uint(3) 637 minor4 := uint(4) 638 639 configVariablesClient := test.NewFakeVariableClient() 640 641 type field struct { 642 providerConfig config.Provider 643 } 644 tests := []struct { 645 name string 646 field field 647 major *uint 648 minor *uint 649 want string 650 wantErr bool 651 }{ 652 { 653 name: "Get latest patch release, no Major/Minor specified", 654 field: field{ 655 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v1.3.2/path", clusterctlv1.CoreProviderType), 656 }, 657 minor: nil, 658 major: nil, 659 want: "v1.3.2", 660 wantErr: false, 661 }, 662 { 663 name: "Get latest patch release, for Major 0 and Minor 3", 664 field: field{ 665 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.3.2/path", clusterctlv1.CoreProviderType), 666 }, 667 major: &major0, 668 minor: &minor3, 669 want: "v0.3.2", 670 wantErr: false, 671 }, 672 { 673 name: "Get latest patch release, for Major 0 and Minor 4", 674 field: field{ 675 providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.0/path", clusterctlv1.CoreProviderType), 676 }, 677 major: &major0, 678 minor: &minor4, 679 want: "v0.4.0", 680 wantErr: false, 681 }, 682 } 683 for _, tt := range tests { 684 t.Run(tt.name, func(t *testing.T) { 685 g := NewWithT(t) 686 687 ctx := context.Background() 688 689 resetCaches() 690 691 gRepo, err := NewGitHubRepository(ctx, tt.field.providerConfig, configVariablesClient, injectGoproxyClient(clientGoproxy)) 692 g.Expect(err).ToNot(HaveOccurred()) 693 694 got, err := latestPatchRelease(ctx, gRepo, tt.major, tt.minor) 695 if tt.wantErr { 696 g.Expect(err).To(HaveOccurred()) 697 return 698 } 699 g.Expect(err).ToNot(HaveOccurred()) 700 g.Expect(got).To(Equal(tt.want)) 701 }) 702 } 703 } 704 705 func Test_gitHubRepository_getReleaseByTag(t *testing.T) { 706 retryableOperationInterval = 200 * time.Millisecond 707 retryableOperationTimeout = 1 * time.Second 708 client, mux, teardown := test.NewFakeGitHub() 709 defer teardown() 710 711 providerConfig := config.NewProvider("test", "https://github.com/o/r/releases/v0.4.1/path", clusterctlv1.CoreProviderType) 712 713 // Setup a handler for returning a fake release. 714 mux.HandleFunc("/repos/o/r/releases/tags/foo", func(w http.ResponseWriter, r *http.Request) { 715 testMethod(t, r, "GET") 716 fmt.Fprint(w, `{"id":13, "tag_name": "v0.4.1"}`) 717 }) 718 719 configVariablesClient := test.NewFakeVariableClient() 720 721 type args struct { 722 tag string 723 } 724 tests := []struct { 725 name string 726 args args 727 wantTagName *string 728 wantErr bool 729 }{ 730 { 731 name: "Return existing version", 732 args: args{ 733 tag: "foo", 734 }, 735 wantTagName: pointer.String("v0.4.1"), 736 wantErr: false, 737 }, 738 { 739 name: "Fails if version does not exists", 740 args: args{ 741 tag: "bar", 742 }, 743 wantTagName: nil, 744 wantErr: true, 745 }, 746 } 747 for _, tt := range tests { 748 t.Run(tt.name, func(t *testing.T) { 749 g := NewWithT(t) 750 751 ctx := context.Background() 752 753 resetCaches() 754 755 gRepo, err := NewGitHubRepository(ctx, providerConfig, configVariablesClient, injectGithubClient(client)) 756 g.Expect(err).ToNot(HaveOccurred()) 757 758 got, err := gRepo.(*gitHubRepository).getReleaseByTag(ctx, tt.args.tag) 759 if tt.wantErr { 760 g.Expect(err).To(HaveOccurred()) 761 return 762 } 763 g.Expect(err).ToNot(HaveOccurred()) 764 765 if tt.wantTagName == nil { 766 g.Expect(got).To(BeNil()) 767 return 768 } 769 770 g.Expect(got.TagName).To(Equal(tt.wantTagName)) 771 }) 772 } 773 } 774 775 func Test_gitHubRepository_downloadFilesFromRelease(t *testing.T) { 776 retryableOperationInterval = 200 * time.Millisecond 777 retryableOperationTimeout = 1 * time.Second 778 client, mux, teardown := test.NewFakeGitHub() 779 defer teardown() 780 781 providerConfig := config.NewProvider("test", "https://github.com/o/r/releases/v0.4.1/file.yaml", clusterctlv1.CoreProviderType) // tree/main/path not relevant for the test 782 providerConfigWithRedirect := config.NewProvider("test", "https://github.com/o/r-with-redirect/releases/v0.4.1/file.yaml", clusterctlv1.CoreProviderType) // tree/main/path not relevant for the test 783 784 // Setup a handler for returning a fake release asset. 785 mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { 786 testMethod(t, r, "GET") 787 w.Header().Set("Content-Type", "application/octet-stream") 788 w.Header().Set("Content-Disposition", "attachment; filename=file.yaml") 789 fmt.Fprint(w, "content") 790 }) 791 // Setup a handler which redirects to a different location. 792 mux.HandleFunc("/repos/o/r-with-redirect/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { 793 testMethod(t, r, "GET") 794 http.Redirect(w, r, "/api-v3/repos/o/r/releases/assets/1", http.StatusFound) 795 }) 796 797 configVariablesClient := test.NewFakeVariableClient() 798 799 var id1 int64 = 1 800 var id2 int64 = 2 801 tagName := "vO.3.3" 802 file := "file.yaml" 803 804 type args struct { 805 release *github.RepositoryRelease 806 fileName string 807 } 808 tests := []struct { 809 name string 810 args args 811 providerConfig config.Provider 812 want []byte 813 wantErr bool 814 }{ 815 { 816 name: "Pass if file exists", 817 args: args{ 818 release: &github.RepositoryRelease{ 819 TagName: &tagName, 820 Assets: []*github.ReleaseAsset{ 821 { 822 ID: &id1, 823 Name: &file, 824 }, 825 }, 826 }, 827 fileName: file, 828 }, 829 providerConfig: providerConfig, 830 want: []byte("content"), 831 wantErr: false, 832 }, 833 { 834 name: "Pass if file exists with redirect", 835 args: args{ 836 release: &github.RepositoryRelease{ 837 TagName: &tagName, 838 Assets: []*github.ReleaseAsset{ 839 { 840 ID: &id1, 841 Name: &file, 842 }, 843 }, 844 }, 845 fileName: file, 846 }, 847 providerConfig: providerConfigWithRedirect, 848 want: []byte("content"), 849 wantErr: false, 850 }, 851 { 852 name: "Fails if file does not exists", 853 args: args{ 854 release: &github.RepositoryRelease{ 855 TagName: &tagName, 856 Assets: []*github.ReleaseAsset{ 857 { 858 ID: &id1, 859 Name: &file, 860 }, 861 }, 862 }, 863 fileName: "another file", 864 }, 865 providerConfig: providerConfig, 866 wantErr: true, 867 }, 868 { 869 name: "Fails if file does not exists", 870 args: args{ 871 release: &github.RepositoryRelease{ 872 TagName: &tagName, 873 Assets: []*github.ReleaseAsset{ 874 { 875 ID: &id2, // id does not match any file (this should not happen) 876 Name: &file, 877 }, 878 }, 879 }, 880 fileName: "another file", 881 }, 882 providerConfig: providerConfig, 883 wantErr: true, 884 }, 885 } 886 for _, tt := range tests { 887 t.Run(tt.name, func(t *testing.T) { 888 g := NewWithT(t) 889 resetCaches() 890 891 gRepo, err := NewGitHubRepository(context.Background(), tt.providerConfig, configVariablesClient, injectGithubClient(client)) 892 g.Expect(err).ToNot(HaveOccurred()) 893 894 got, err := gRepo.(*gitHubRepository).downloadFilesFromRelease(context.Background(), tt.args.release, tt.args.fileName) 895 if tt.wantErr { 896 g.Expect(err).To(HaveOccurred()) 897 return 898 } 899 900 g.Expect(err).ToNot(HaveOccurred()) 901 g.Expect(got).To(Equal(tt.want)) 902 }) 903 } 904 } 905 906 func testMethod(t *testing.T, r *http.Request, want string) { 907 t.Helper() 908 909 if got := r.Method; got != want { 910 t.Errorf("Request method: %v, want %v", got, want) 911 } 912 } 913 914 // resetCaches is called repeatedly throughout tests to help avoid cross-test pollution. 915 func resetCaches() { 916 cacheVersions = map[string][]string{} 917 cacheReleases = map[string]*github.RepositoryRelease{} 918 cacheFiles = map[string][]byte{} 919 } 920 921 // newFakeGoproxy sets up a test HTTP server along with a github.Client that is 922 // configured to talk to that test server. Tests should register handlers on 923 // mux which provide mock responses for the API method being tested. 924 func newFakeGoproxy() (client *goproxy.Client, mux *http.ServeMux, teardown func()) { 925 // mux is the HTTP request multiplexer used with the test server. 926 mux = http.NewServeMux() 927 928 apiHandler := http.NewServeMux() 929 apiHandler.Handle("/", mux) 930 931 // server is a test HTTP server used to provide mock API responses. 932 server := httptest.NewServer(apiHandler) 933 934 // client is the GitHub client being tested and is configured to use test server. 935 url, _ := url.Parse(server.URL + "/") 936 return goproxy.NewClient(url.Scheme, url.Host), mux, server.Close 937 }