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