...

Source file src/github.com/redhat-developer/odo/pkg/registry/registry_test.go

Documentation: github.com/redhat-developer/odo/pkg/registry

     1  package registry
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/fs"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"sort"
    14  	"strings"
    15  	"testing"
    16  
    17  	devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    18  
    19  	"github.com/golang/mock/gomock"
    20  	"github.com/google/go-cmp/cmp"
    21  	"github.com/google/go-cmp/cmp/cmpopts"
    22  
    23  	"github.com/redhat-developer/odo/pkg/api"
    24  	"github.com/redhat-developer/odo/pkg/config"
    25  	envcontext "github.com/redhat-developer/odo/pkg/config/context"
    26  	"github.com/redhat-developer/odo/pkg/kclient"
    27  	"github.com/redhat-developer/odo/pkg/preference"
    28  	"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
    29  )
    30  
    31  func TestGetDevfileRegistries(t *testing.T) {
    32  	tempConfigFile, err := os.CreateTemp("", "odoconfig")
    33  	if err != nil {
    34  		t.Fatal("Fail to create temporary config file")
    35  	}
    36  	defer os.Remove(tempConfigFile.Name())
    37  	defer tempConfigFile.Close()
    38  	_, err = tempConfigFile.Write([]byte(
    39  		`kind: Preference
    40  apiversion: odo.openshift.io/v1alpha1
    41  OdoSettings:
    42    RegistryList:
    43    - Name: DefaultDevfileRegistry
    44      URL: https://registry.devfile.io
    45    - Name: CheDevfileRegistry
    46      URL: https://che-devfile-registry.openshift.io/`,
    47  	))
    48  	if err != nil {
    49  		t.Error(err)
    50  	}
    51  	tempConfigFileName := tempConfigFile.Name()
    52  
    53  	tests := []struct {
    54  		name         string
    55  		registryName string
    56  		kclient      func(ctrl *gomock.Controller) kclient.ClientInterface
    57  		want         []api.Registry
    58  		wantErr      bool
    59  	}{
    60  		{
    61  			name:         "Case 1: Test get all devfile registries",
    62  			registryName: "",
    63  			want: []api.Registry{
    64  				{
    65  					Name:   "CheDevfileRegistry",
    66  					URL:    "https://che-devfile-registry.openshift.io/",
    67  					Secure: false,
    68  				},
    69  				{
    70  					Name:   "DefaultDevfileRegistry",
    71  					URL:    "https://registry.devfile.io",
    72  					Secure: false,
    73  				},
    74  			},
    75  		},
    76  		{
    77  			name:         "Case 2: Test get specific devfile registry",
    78  			registryName: "CheDevfileRegistry",
    79  			want: []api.Registry{
    80  				{
    81  					Name:   "CheDevfileRegistry",
    82  					URL:    "https://che-devfile-registry.openshift.io/",
    83  					Secure: false,
    84  				},
    85  			},
    86  		},
    87  		{
    88  			name: "with devfileRegistrylists in cluster",
    89  			kclient: func(ctrl *gomock.Controller) kclient.ClientInterface {
    90  				result := kclient.NewMockClientInterface(ctrl)
    91  				list := []api.Registry{
    92  					{
    93  						Name:   "secure-name",
    94  						URL:    "secure-url",
    95  						Secure: true,
    96  					},
    97  					{
    98  						Name:   "unsecure-name",
    99  						URL:    "unsecure-url",
   100  						Secure: false,
   101  					},
   102  				}
   103  				result.EXPECT().GetRegistryList().Return(list, nil)
   104  				return result
   105  			},
   106  			want: []api.Registry{
   107  				{
   108  					Name:   "secure-name",
   109  					URL:    "secure-url",
   110  					Secure: true,
   111  				},
   112  				{
   113  					Name:   "unsecure-name",
   114  					URL:    "unsecure-url",
   115  					Secure: false,
   116  				},
   117  				{
   118  					Name:   "CheDevfileRegistry",
   119  					URL:    "https://che-devfile-registry.openshift.io/",
   120  					Secure: false,
   121  				},
   122  				{
   123  					Name:   "DefaultDevfileRegistry",
   124  					URL:    "https://registry.devfile.io",
   125  					Secure: false,
   126  				},
   127  			},
   128  		},
   129  		{
   130  			name: "error getting devfileRegistrylists from cluster",
   131  			kclient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   132  				result := kclient.NewMockClientInterface(ctrl)
   133  				result.EXPECT().GetRegistryList().Return(nil, errors.New("an error"))
   134  				return result
   135  			},
   136  			want: []api.Registry{
   137  				{
   138  					Name:   "CheDevfileRegistry",
   139  					URL:    "https://che-devfile-registry.openshift.io/",
   140  					Secure: false,
   141  				},
   142  				{
   143  					Name:   "DefaultDevfileRegistry",
   144  					URL:    "https://registry.devfile.io",
   145  					Secure: false,
   146  				},
   147  			},
   148  		},
   149  	}
   150  
   151  	for _, tt := range tests {
   152  		t.Run(tt.name, func(t *testing.T) {
   153  			ctx := context.Background()
   154  			ctx = envcontext.WithEnvConfig(ctx, config.Configuration{
   155  				Globalodoconfig: &tempConfigFileName,
   156  			})
   157  
   158  			ctrl := gomock.NewController(t)
   159  
   160  			prefClient, _ := preference.NewClient(ctx)
   161  			var kc kclient.ClientInterface
   162  			if tt.kclient != nil {
   163  				kc = tt.kclient(ctrl)
   164  			}
   165  			catClient := NewRegistryClient(filesystem.NewFakeFs(), prefClient, kc)
   166  			got, err := catClient.GetDevfileRegistries(tt.registryName)
   167  
   168  			if tt.wantErr != (err != nil) {
   169  				t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
   170  			}
   171  
   172  			if diff := cmp.Diff(tt.want, got); diff != "" {
   173  				t.Errorf("RegistryClient.GetDevfileRegistries() mismatch (-want +got):\n%s", diff)
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  func TestListDevfileStacks(t *testing.T) {
   180  	// Start a local HTTP server
   181  	// to test getting multiple devfiles via ListDevfileStacks
   182  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   183  		// Send response to be tested
   184  		_, err := rw.Write([]byte(
   185  			`
   186  			[
   187  				{
   188  					"name": "nodejs",
   189  					"displayName": "NodeJS Angular Web Application",
   190  					"description": "Stack for developing NodeJS Angular Web Application",
   191  					"tags": [
   192  						"NodeJS",
   193  						"Angular",
   194  						"Alpine"
   195  					],
   196  					"language": "nodejs",
   197  					"icon": "/images/angular.svg",
   198  					"globalMemoryLimit": "2686Mi",
   199  					"links": {
   200  						"self": "/devfiles/angular/devfile.yaml"
   201  					}
   202  				},
   203  				{
   204  					"name": "python",
   205  					"displayName": "Python",
   206  					"description": "Python Stack with Python 3.7",
   207  					"tags": [
   208  						"Python",
   209  						"pip"
   210  					],
   211  					"architectures": [
   212  						"amd64",
   213  						"ppc64le",
   214  						"s390x"
   215  					],
   216  					"language": "python",
   217  					"icon": "/images/foobar.svg",
   218  					"globalMemoryLimit": "2686Mi",
   219  					"links": {
   220  						"self": "/devfiles/python/devfile.yaml"
   221  					}
   222  				}
   223  			]
   224  			`,
   225  		))
   226  		if err != nil {
   227  			t.Error(err)
   228  		}
   229  	}))
   230  	// Close the server when test finishes
   231  	defer server.Close()
   232  
   233  	const registryName = "TestRegistry"
   234  	tests := []struct {
   235  		name         string
   236  		registryName string
   237  		devfileName  string
   238  		filter       string
   239  		want         DevfileStackList
   240  	}{
   241  		{
   242  			name:         "Case 1: Test getting ALL registries and looking for nodejs",
   243  			registryName: "",
   244  			want: DevfileStackList{
   245  				DevfileRegistries: []api.Registry{
   246  					{
   247  						Name:   "TestRegistry",
   248  						URL:    server.URL,
   249  						Secure: false,
   250  					},
   251  				},
   252  				Items: []api.DevfileStack{
   253  					{
   254  						Name:        "nodejs",
   255  						DisplayName: "NodeJS Angular Web Application",
   256  						Description: "Stack for developing NodeJS Angular Web Application",
   257  						Registry: api.Registry{
   258  							Name: registryName,
   259  							URL:  server.URL,
   260  						},
   261  						Language: "nodejs",
   262  						Tags:     []string{"NodeJS", "Angular", "Alpine"},
   263  					},
   264  					{
   265  						Name:        "python",
   266  						DisplayName: "Python",
   267  						Description: "Python Stack with Python 3.7",
   268  						Registry: api.Registry{
   269  							Name: registryName,
   270  							URL:  server.URL,
   271  						},
   272  						Language:      "python",
   273  						Tags:          []string{"Python", "pip"},
   274  						Architectures: []string{"amd64", "ppc64le", "s390x"},
   275  					},
   276  				},
   277  			},
   278  		},
   279  		{
   280  			name:         "Case 2: Test getting from only one specific devfile and from a specific registry",
   281  			registryName: "TestRegistry",
   282  			devfileName:  "nodejs",
   283  			want: DevfileStackList{
   284  				DevfileRegistries: []api.Registry{
   285  					{
   286  						Name:   "TestRegistry",
   287  						URL:    server.URL,
   288  						Secure: false,
   289  					},
   290  				},
   291  				Items: []api.DevfileStack{
   292  					{
   293  						Name:        "nodejs",
   294  						DisplayName: "NodeJS Angular Web Application",
   295  						Description: "Stack for developing NodeJS Angular Web Application",
   296  						Registry: api.Registry{
   297  							Name: registryName,
   298  							URL:  server.URL,
   299  						},
   300  						Language: "nodejs",
   301  						Tags:     []string{"NodeJS", "Angular", "Alpine"},
   302  					},
   303  				},
   304  			},
   305  		},
   306  		{
   307  			name:         "Case 3: Test getting a devfile using a filter from the description",
   308  			registryName: "TestRegistry",
   309  			filter:       "Python Stack",
   310  			want: DevfileStackList{
   311  				DevfileRegistries: []api.Registry{
   312  					{
   313  						Name:   "TestRegistry",
   314  						URL:    server.URL,
   315  						Secure: false,
   316  					},
   317  				},
   318  				Items: []api.DevfileStack{
   319  					{
   320  						Name:        "python",
   321  						DisplayName: "Python",
   322  						Description: "Python Stack with Python 3.7",
   323  						Registry: api.Registry{
   324  							Name: registryName,
   325  							URL:  server.URL,
   326  						},
   327  						Language:      "python",
   328  						Tags:          []string{"Python", "pip"},
   329  						Architectures: []string{"amd64", "ppc64le", "s390x"},
   330  					},
   331  				},
   332  			},
   333  		},
   334  		{
   335  			name:         "Case 3.2: Test getting a devfile using a filter from the architectures",
   336  			registryName: "TestRegistry",
   337  			filter:       "s390",
   338  			want: DevfileStackList{
   339  				DevfileRegistries: []api.Registry{
   340  					{
   341  						Name:   "TestRegistry",
   342  						URL:    server.URL,
   343  						Secure: false,
   344  					},
   345  				},
   346  				Items: []api.DevfileStack{
   347  					// Devfiles with no architectures are supposed to be compatible with all architectures,
   348  					// so s390 will match an existing valid architecture (s390x).
   349  					{
   350  						Name:        "nodejs",
   351  						DisplayName: "NodeJS Angular Web Application",
   352  						Description: "Stack for developing NodeJS Angular Web Application",
   353  						Registry: api.Registry{
   354  							Name: registryName,
   355  							URL:  server.URL,
   356  						},
   357  						Language: "nodejs",
   358  						Tags:     []string{"NodeJS", "Angular", "Alpine"},
   359  					},
   360  					{
   361  						Name:        "python",
   362  						DisplayName: "Python",
   363  						Description: "Python Stack with Python 3.7",
   364  						Registry: api.Registry{
   365  							Name: registryName,
   366  							URL:  server.URL,
   367  						},
   368  						Language:      "python",
   369  						Tags:          []string{"Python", "pip"},
   370  						Architectures: []string{"amd64", "ppc64le", "s390x"},
   371  					},
   372  				},
   373  			},
   374  		},
   375  		{
   376  			name:         "Case 3.3: Test getting a devfile using a filter not matching name, description or architectures",
   377  			registryName: "TestRegistry",
   378  			filter:       "some-random-string",
   379  			want: DevfileStackList{
   380  				DevfileRegistries: []api.Registry{
   381  					{
   382  						Name:   "TestRegistry",
   383  						URL:    server.URL,
   384  						Secure: false,
   385  					},
   386  				},
   387  			},
   388  		},
   389  		{
   390  			name:         "Case 4: Expect nothing back if registry is not found",
   391  			registryName: "Foobar",
   392  			want:         DevfileStackList{},
   393  		},
   394  	}
   395  
   396  	for _, tt := range tests {
   397  		t.Run(tt.name, func(t *testing.T) {
   398  			ctrl := gomock.NewController(t)
   399  			prefClient := preference.NewMockClient(ctrl)
   400  			prefClient.EXPECT().RegistryList().Return([]api.Registry{
   401  				{
   402  					Name: "TestRegistry",
   403  					URL:  server.URL,
   404  				},
   405  			}).AnyTimes()
   406  			// TODO(rm3l) Test with both nil and non-nil kubeclient
   407  			catClient := NewRegistryClient(filesystem.NewFakeFs(), prefClient, nil)
   408  			ctx := context.Background()
   409  			ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
   410  			got, err := catClient.ListDevfileStacks(ctx, tt.registryName, tt.devfileName, tt.filter, false, false)
   411  			if err != nil {
   412  				t.Error(err)
   413  			}
   414  
   415  			if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
   416  				t.Errorf("RegistryClient.ListDevfileStacks() mismatch (-want +got):\n%s", diff)
   417  			}
   418  		})
   419  	}
   420  }
   421  
   422  func TestGetRegistryDevfiles(t *testing.T) {
   423  	const registryName = "some registry"
   424  	const (
   425  		v1IndexResponse = `
   426  [
   427  	{
   428  		"name": "nodejs",
   429  		"displayName": "NodeJS Angular Web Application",
   430  		"description": "Stack for developing NodeJS Angular Web Application",
   431  		"version": "1.2.3",
   432  		"tags": [
   433  			"NodeJS",
   434  			"Angular",
   435  			"Alpine"
   436  		],
   437  		"language": "nodejs",
   438  		"icon": "/images/angular.svg",
   439  		"globalMemoryLimit": "2686Mi",
   440  		"links": {
   441  			"self": "/devfiles/angular/devfile.yaml"
   442  		}
   443  	},
   444  	{
   445  		"name": "python",
   446  		"displayName": "Python Application",
   447  		"description": "Stack for developing a Python Web Application",
   448  		"version": "2.3.4",
   449  		"tags": ["Python"],
   450  		"language": "python",
   451  		"architectures": [
   452  		  "amd64",
   453  		  "ppc64le"
   454  		],
   455  		"links": {
   456  			"self": "/devfiles/python/devfile.yaml"
   457  		}
   458  	}
   459  ]
   460  `
   461  		v2IndexResponse = `
   462  [
   463  	{
   464  		"name": "go",
   465  		"displayName": "Go Runtime",
   466  		"description": "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.",
   467  		"type": "stack",
   468  		"tags": [
   469  		  "Go"
   470  		],
   471  		"icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg",
   472  		"projectType": "Go",
   473  		"language": "Go",
   474  		"provider": "Red Hat",
   475  		"versions": [
   476  		  {
   477  			"version": "2.0.0",
   478  			"schemaVersion": "2.2.0",
   479  			"description": "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.",
   480  			"tags": [
   481  			  "Go"
   482  			],
   483  			"icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg",
   484  			"links": {
   485  			  "self": "devfile-catalog/go:2.0.0"
   486  			},
   487  			"resources": [
   488  			  "devfile.yaml"
   489  			],
   490  			"starterProjects": [
   491  			  "go-starter"
   492  			]
   493  		  },
   494  		  {
   495  			"version": "1.0.2",
   496  			"schemaVersion": "2.1.0",
   497  			"default": true,
   498  			"description": "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.",
   499  			"tags": [
   500  			  "Go"
   501  			],
   502  			"icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg",
   503  			"links": {
   504  			  "self": "devfile-catalog/go:1.0.2"
   505  			},
   506  			"resources": [
   507  			  "devfile.yaml"
   508  			],
   509  			"starterProjects": [
   510  			  "go-starter"
   511  			]
   512  		  }
   513  		]
   514  	},
   515  	{
   516  		"name": "python",
   517  		"displayName": "Python Application",
   518  		"description": "Python Stack",
   519  		"type": "stack",
   520  		"tags": [
   521  		  "Python"
   522  		],
   523  		"architectures": [
   524  		  "amd64",
   525  		  "ppc64le"
   526  		],
   527  		"projectType": "Python",
   528  		"language": "Python",
   529  		"versions": [
   530  		  {
   531  			"version": "2.3.4",
   532  			"schemaVersion": "2.2.0",
   533  			"description": "Python stack.",
   534  			"tags": [
   535  			  "Python"
   536  			],
   537  			"icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/python.svg",
   538  			"links": {
   539  			  "self": "devfile-catalog/python:2.0.0"
   540  			},
   541  			"resources": [
   542  			  "devfile.yaml"
   543  			],
   544  			"starterProjects": [
   545  			  "python-starter"
   546  			]
   547  		  },
   548  		  {
   549  			"version": "1.2.3",
   550  			"schemaVersion": "2.1.0",
   551  			"default": true,
   552  			"description": "Python stack.",
   553  			"tags": [
   554  			  "Python"
   555  			],
   556  			"icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/python.svg",
   557  			"links": {
   558  			  "self": "devfile-catalog/python:2.0.0"
   559  			},
   560  			"resources": [
   561  			  "devfile.yaml"
   562  			],
   563  			"starterProjects": [
   564  			  "python-starter"
   565  			]
   566  		  }
   567  		]
   568  	}
   569  ]
   570  `
   571  	)
   572  
   573  	type test struct {
   574  		name                   string
   575  		registryServerProvider func(t *testing.T) (*httptest.Server, string)
   576  		wantErr                bool
   577  		wantProvider           func(registryUrl string) []api.DevfileStack
   578  	}
   579  	tests := []test{
   580  		{
   581  			name: "GitHub-based registry: github.com",
   582  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   583  				return nil, "https://github.com/redhat-developer/odo"
   584  			},
   585  			wantErr: true,
   586  		},
   587  		{
   588  			name: "GitHub-based registry: raw.githubusercontent.com",
   589  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   590  				return nil, "https://raw.githubusercontent.com/redhat-developer/odo"
   591  			},
   592  			wantErr: true,
   593  		},
   594  		{
   595  			name: "GitHub-based registry: *.github.com",
   596  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   597  				return nil, "https://redhat-developer.github.com/odo"
   598  			},
   599  			wantErr: true,
   600  		},
   601  		{
   602  			name: "GitHub-based registry: *.raw.githubusercontent.com",
   603  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   604  				return nil, "https://redhat-developer.raw.githubusercontent.com/odo"
   605  			},
   606  			wantErr: true,
   607  		},
   608  		{
   609  			name: "Devfile registry server: client error (4xx)",
   610  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   611  				server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   612  					rw.WriteHeader(http.StatusNotFound)
   613  				}))
   614  				return server, server.URL
   615  			},
   616  			wantErr: true,
   617  		},
   618  		{
   619  			name: "Devfile registry server: server error (5xx)",
   620  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   621  				server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   622  					rw.WriteHeader(http.StatusInternalServerError)
   623  				}))
   624  				return server, server.URL
   625  			},
   626  			wantErr: true,
   627  		},
   628  		{
   629  			name: "Devfile registry: only /index",
   630  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   631  				server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   632  					if req.URL.Path != "/index" {
   633  						rw.WriteHeader(http.StatusNotFound)
   634  						return
   635  					}
   636  					_, err := rw.Write([]byte(v1IndexResponse))
   637  					if err != nil {
   638  						t.Error(err)
   639  					}
   640  				}))
   641  				return server, server.URL
   642  			},
   643  			wantProvider: func(registryUrl string) []api.DevfileStack {
   644  				return []api.DevfileStack{
   645  					{
   646  						Name:           "nodejs",
   647  						DisplayName:    "NodeJS Angular Web Application",
   648  						Description:    "Stack for developing NodeJS Angular Web Application",
   649  						Registry:       api.Registry{Name: registryName, URL: registryUrl},
   650  						Language:       "nodejs",
   651  						Tags:           []string{"NodeJS", "Angular", "Alpine"},
   652  						DefaultVersion: "1.2.3",
   653  					},
   654  					{
   655  						Name:           "python",
   656  						DisplayName:    "Python Application",
   657  						Description:    "Stack for developing a Python Web Application",
   658  						Registry:       api.Registry{Name: registryName, URL: registryUrl},
   659  						Language:       "python",
   660  						Tags:           []string{"Python"},
   661  						Architectures:  []string{"amd64", "ppc64le"},
   662  						DefaultVersion: "2.3.4",
   663  					},
   664  				}
   665  			},
   666  		},
   667  		{
   668  			name: "Devfile registry: only /v2index",
   669  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   670  				server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   671  					if req.URL.Path != "/v2index" {
   672  						rw.WriteHeader(http.StatusNotFound)
   673  						return
   674  					}
   675  					_, err := rw.Write([]byte(v2IndexResponse))
   676  					if err != nil {
   677  						t.Error(err)
   678  					}
   679  				}))
   680  				return server, server.URL
   681  			},
   682  			wantProvider: func(registryUrl string) []api.DevfileStack {
   683  				return []api.DevfileStack{
   684  					{
   685  						Name:                   "go",
   686  						DisplayName:            "Go Runtime",
   687  						Description:            "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.",
   688  						Registry:               api.Registry{Name: registryName, URL: registryUrl},
   689  						Language:               "Go",
   690  						ProjectType:            "Go",
   691  						Tags:                   []string{"Go"},
   692  						DefaultVersion:         "1.0.2",
   693  						DefaultStarterProjects: []string{"go-starter"},
   694  						Versions: []api.DevfileStackVersion{
   695  							{Version: "1.0.2", IsDefault: true, SchemaVersion: "2.1.0", StarterProjects: []string{"go-starter"}},
   696  							{Version: "2.0.0", IsDefault: false, SchemaVersion: "2.2.0", StarterProjects: []string{"go-starter"}},
   697  						},
   698  					},
   699  					{
   700  						Name:                   "python",
   701  						DisplayName:            "Python Application",
   702  						Description:            "Python Stack",
   703  						Registry:               api.Registry{Name: registryName, URL: registryUrl},
   704  						Language:               "Python",
   705  						ProjectType:            "Python",
   706  						Tags:                   []string{"Python"},
   707  						DefaultVersion:         "1.2.3",
   708  						DefaultStarterProjects: []string{"python-starter"},
   709  						Versions: []api.DevfileStackVersion{
   710  							{Version: "1.2.3", IsDefault: true, SchemaVersion: "2.1.0", StarterProjects: []string{"python-starter"}},
   711  							{Version: "2.3.4", IsDefault: false, SchemaVersion: "2.2.0", StarterProjects: []string{"python-starter"}},
   712  						},
   713  						Architectures: []string{"amd64", "ppc64le"},
   714  					},
   715  				}
   716  			},
   717  		},
   718  		{
   719  			name: "Devfile registry: both /index and /v2index => v2index has precedence",
   720  			registryServerProvider: func(t *testing.T) (*httptest.Server, string) {
   721  				server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   722  					var resp string
   723  					switch req.URL.Path {
   724  					case "/index":
   725  						resp = v1IndexResponse
   726  					case "/v2index":
   727  						resp = v2IndexResponse
   728  					}
   729  					if resp == "" {
   730  						rw.WriteHeader(http.StatusNotFound)
   731  						return
   732  					}
   733  					_, err := rw.Write([]byte(resp))
   734  					if err != nil {
   735  						t.Error(err)
   736  					}
   737  				}))
   738  				return server, server.URL
   739  			},
   740  			wantProvider: func(registryUrl string) []api.DevfileStack {
   741  				return []api.DevfileStack{
   742  					{
   743  						Name:                   "go",
   744  						DisplayName:            "Go Runtime",
   745  						Description:            "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.",
   746  						Registry:               api.Registry{Name: registryName, URL: registryUrl},
   747  						Language:               "Go",
   748  						ProjectType:            "Go",
   749  						Tags:                   []string{"Go"},
   750  						DefaultVersion:         "1.0.2",
   751  						DefaultStarterProjects: []string{"go-starter"},
   752  						Versions: []api.DevfileStackVersion{
   753  							{Version: "1.0.2", IsDefault: true, SchemaVersion: "2.1.0", StarterProjects: []string{"go-starter"}},
   754  							{Version: "2.0.0", IsDefault: false, SchemaVersion: "2.2.0", StarterProjects: []string{"go-starter"}},
   755  						},
   756  					},
   757  					{
   758  						Name:                   "python",
   759  						DisplayName:            "Python Application",
   760  						Description:            "Python Stack",
   761  						Registry:               api.Registry{Name: registryName, URL: registryUrl},
   762  						Language:               "Python",
   763  						ProjectType:            "Python",
   764  						Tags:                   []string{"Python"},
   765  						Architectures:          []string{"amd64", "ppc64le"},
   766  						DefaultVersion:         "1.2.3",
   767  						DefaultStarterProjects: []string{"python-starter"},
   768  						Versions: []api.DevfileStackVersion{
   769  							{Version: "1.2.3", IsDefault: true, SchemaVersion: "2.1.0", StarterProjects: []string{"python-starter"}},
   770  							{Version: "2.3.4", IsDefault: false, SchemaVersion: "2.2.0", StarterProjects: []string{"python-starter"}},
   771  						},
   772  					},
   773  				}
   774  			},
   775  		},
   776  	}
   777  
   778  	for _, tt := range tests {
   779  		t.Run(tt.name, func(t *testing.T) {
   780  			ctx := envcontext.WithEnvConfig(context.Background(), config.Configuration{})
   781  			server, url := tt.registryServerProvider(t)
   782  			if server != nil {
   783  				defer server.Close()
   784  			}
   785  
   786  			got, err := getRegistryStacks(ctx, api.Registry{Name: registryName, URL: url})
   787  
   788  			if tt.wantErr != (err != nil) {
   789  				t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
   790  			}
   791  
   792  			if tt.wantProvider != nil {
   793  				want := tt.wantProvider(url)
   794  				if diff := cmp.Diff(want, got); diff != "" {
   795  					t.Errorf("getRegistryStacks() mismatch (-want +got):\n%s", diff)
   796  					t.Logf("Error message is: %v", err)
   797  				}
   798  			}
   799  		})
   800  	}
   801  }
   802  
   803  func TestRegistryClient_DownloadStarterProject(t *testing.T) {
   804  	setupFS := func(contextDir string, fs filesystem.Filesystem) {
   805  		directoriesToBeCreated := []string{"kubernetes", "docker"}
   806  		for _, dirToBeCreated := range directoriesToBeCreated {
   807  			dirName := filepath.Join(contextDir, dirToBeCreated)
   808  			err := fs.MkdirAll(dirName, os.FileMode(0750))
   809  			if err != nil {
   810  				t.Errorf("failed to create %s; cause: %s", dirName, err)
   811  			}
   812  		}
   813  
   814  		filesToBeCreated := []string{"devfile.yaml", "kubernetes/deploy.yaml", "docker/Dockerfile"}
   815  		for _, fileToBeCreated := range filesToBeCreated {
   816  			fileName := filepath.Join(contextDir, fileToBeCreated)
   817  			_, err := fs.Create(fileName)
   818  			if err != nil {
   819  				t.Errorf("failed to create %s; cause: %s", fileName, err)
   820  			}
   821  		}
   822  	}
   823  
   824  	getZipFilePath := func(name string) string {
   825  		// filename of this file
   826  		_, filename, _, _ := runtime.Caller(0)
   827  		// path to the devfile
   828  		return filepath.Join(filepath.Dir(filename), "..", "..", "tests", "examples", filepath.Join("source", "devfiles", "zips", name))
   829  	}
   830  	type fields struct {
   831  		fsys             filesystem.Filesystem
   832  		preferenceClient preference.Client
   833  		kubeClient       kclient.ClientInterface
   834  	}
   835  	type args struct {
   836  		starterProject *devfilev1.StarterProject
   837  		decryptedToken string
   838  		contextDir     string
   839  		verbose        bool
   840  	}
   841  	tests := []struct {
   842  		name                string
   843  		fields              fields
   844  		args                args
   845  		want                []string
   846  		wantContainsDevfile bool
   847  		wantErr             bool
   848  	}{
   849  		// TODO: Add test cases.
   850  		{
   851  			name: "Starter project has a Devfile",
   852  			fields: fields{
   853  				fsys: filesystem.NewFakeFs(),
   854  			},
   855  			args: args{
   856  				starterProject: &devfilev1.StarterProject{
   857  					Name: "starter-project-with-devfile",
   858  					ProjectSource: devfilev1.ProjectSource{
   859  						SourceType: "",
   860  						Zip: &devfilev1.ZipProjectSource{
   861  							CommonProjectSource: devfilev1.CommonProjectSource{},
   862  							Location:            fmt.Sprintf("file://%s", getZipFilePath("starterproject-with-devfile.zip")),
   863  						},
   864  					},
   865  				},
   866  			},
   867  			want:                []string{"devfile.yaml", "docker", filepath.Join("docker", "Dockerfile"), "README.md", "main.go", "go.mod", "someFile.txt"},
   868  			wantErr:             false,
   869  			wantContainsDevfile: true,
   870  		},
   871  		{
   872  			name: "Starter project has conflicting files",
   873  			fields: fields{
   874  				fsys: filesystem.NewFakeFs(),
   875  			},
   876  			args: args{
   877  				starterProject: &devfilev1.StarterProject{
   878  					Name: "starter-project-with-conflicting-files",
   879  					ProjectSource: devfilev1.ProjectSource{
   880  						SourceType: "",
   881  						Zip: &devfilev1.ZipProjectSource{
   882  							CommonProjectSource: devfilev1.CommonProjectSource{},
   883  							Location:            fmt.Sprintf("file://%s", getZipFilePath("starterproject-with-conflicts.zip")),
   884  						},
   885  					},
   886  				},
   887  			},
   888  			want: []string{"devfile.yaml", "docker", filepath.Join("docker", "Dockerfile"), "kubernetes", filepath.Join("kubernetes", "deploy.yaml"),
   889  				CONFLICT_DIR_NAME, filepath.Join(CONFLICT_DIR_NAME, "kubernetes"), filepath.Join(CONFLICT_DIR_NAME, "kubernetes", "deploy.yaml"),
   890  				filepath.Join(CONFLICT_DIR_NAME, "main.go"), filepath.Join(CONFLICT_DIR_NAME, "go.mod"), filepath.Join(CONFLICT_DIR_NAME, "README.md"), filepath.Join(CONFLICT_DIR_NAME, "someFile.txt")},
   891  			wantErr: false,
   892  		},
   893  		{
   894  			name: "Starter project has conflicting files and an empty dir",
   895  			fields: fields{
   896  				fsys: filesystem.NewFakeFs(),
   897  			},
   898  			args: args{
   899  				starterProject: &devfilev1.StarterProject{
   900  					Name: "starter-project-with-conflicting-files-and-empty-dir",
   901  					ProjectSource: devfilev1.ProjectSource{
   902  						SourceType: "",
   903  						Zip: &devfilev1.ZipProjectSource{
   904  							CommonProjectSource: devfilev1.CommonProjectSource{},
   905  							Location:            fmt.Sprintf("file://%s", getZipFilePath("starterproject-with-conflicts-and-empty-dir.zip")),
   906  						},
   907  					},
   908  				},
   909  			},
   910  			want: []string{"devfile.yaml", "docker", filepath.Join("docker", "Dockerfile"), "kubernetes", filepath.Join("kubernetes", "deploy.yaml"),
   911  				filepath.Join(CONFLICT_DIR_NAME, "kubernetes"), CONFLICT_DIR_NAME, filepath.Join(CONFLICT_DIR_NAME, "docker"), filepath.Join(CONFLICT_DIR_NAME, "docker", "Dockerfile"),
   912  				filepath.Join(CONFLICT_DIR_NAME, "main.go"), filepath.Join(CONFLICT_DIR_NAME, "go.mod"), filepath.Join(CONFLICT_DIR_NAME, "README.md")},
   913  			wantErr: false,
   914  		},
   915  		{
   916  			name: "Starter project does not have any conflicting files",
   917  			fields: fields{
   918  				fsys: filesystem.NewFakeFs(),
   919  			},
   920  			args: args{
   921  				starterProject: &devfilev1.StarterProject{
   922  					Name: "starter-project-with-no-conflicting-files",
   923  					ProjectSource: devfilev1.ProjectSource{
   924  						SourceType: "",
   925  						Zip: &devfilev1.ZipProjectSource{
   926  							CommonProjectSource: devfilev1.CommonProjectSource{},
   927  							Location:            fmt.Sprintf("file://%s", getZipFilePath("starterproject-with-no-conflicts.zip")),
   928  						},
   929  					},
   930  				},
   931  			},
   932  			want:    []string{"devfile.yaml", "docker", filepath.Join("docker", "Dockerfile"), "kubernetes", filepath.Join("kubernetes", "deploy.yaml"), "README.md", "main.go", "go.mod"},
   933  			wantErr: false,
   934  		},
   935  		{
   936  			name: "Starter project does not have any conflicting files but has empty dir",
   937  			fields: fields{
   938  				fsys: filesystem.NewFakeFs(),
   939  			},
   940  			args: args{
   941  				starterProject: &devfilev1.StarterProject{
   942  					Name: "starter-project-with-no-conflicting-files-and-empty-dir",
   943  					ProjectSource: devfilev1.ProjectSource{
   944  						SourceType: "",
   945  						Zip: &devfilev1.ZipProjectSource{
   946  							CommonProjectSource: devfilev1.CommonProjectSource{},
   947  							Location:            fmt.Sprintf("file://%s", getZipFilePath("starterproject-with-no-conflicts-and-empty-dir.zip")),
   948  						},
   949  					},
   950  				},
   951  			},
   952  			want:    []string{"devfile.yaml", "docker", filepath.Join("docker", "Dockerfile"), "kubernetes", filepath.Join("kubernetes", "deploy.yaml"), "README.md", "main.go", "go.mod"},
   953  			wantErr: false,
   954  		},
   955  	}
   956  	for _, tt := range tests {
   957  		t.Run(tt.name, func(t *testing.T) {
   958  			contextDir, ferr := tt.fields.fsys.TempDir("", "downloadstarterproject")
   959  			if ferr != nil {
   960  				t.Errorf("failed to create temp dir; cause: %s", ferr)
   961  			}
   962  			tt.args.contextDir = contextDir
   963  
   964  			setupFS(tt.args.contextDir, tt.fields.fsys)
   965  
   966  			o := RegistryClient{
   967  				fsys:             tt.fields.fsys,
   968  				preferenceClient: tt.fields.preferenceClient,
   969  				kubeClient:       tt.fields.kubeClient,
   970  			}
   971  			gotContainsDevfile, gotErr := o.DownloadStarterProject(tt.args.starterProject, tt.args.decryptedToken, tt.args.contextDir, tt.args.verbose)
   972  			if (gotErr != nil) != tt.wantErr {
   973  				t.Errorf("DownloadStarterProject() error = %v, wantErr %v", gotErr, tt.wantErr)
   974  				return
   975  			}
   976  			if tt.wantContainsDevfile != gotContainsDevfile {
   977  				t.Errorf("DownloadStarterProject() containsDevfile = %v, want = %v", gotContainsDevfile, tt.wantContainsDevfile)
   978  				return
   979  			}
   980  			var got []string
   981  			err := o.fsys.Walk(contextDir, func(path string, info fs.FileInfo, err error) error {
   982  				path = strings.TrimPrefix(path, contextDir)
   983  				path = strings.TrimPrefix(path, string(os.PathSeparator))
   984  				if path != "" {
   985  					got = append(got, path)
   986  				}
   987  				return nil
   988  			})
   989  			if err != nil {
   990  				t.Errorf("failed to walk %s; cause:%s", contextDir, err.Error())
   991  				return
   992  			}
   993  			sort.Strings(got)
   994  			sort.Strings(tt.want)
   995  			if diff := cmp.Diff(got, tt.want); diff != "" {
   996  				t.Errorf("DownloadStarterProject() mismatch (-want +got):\n%s", diff)
   997  			}
   998  		})
   999  	}
  1000  }
  1001  

View as plain text