github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/getproviders/multi_source_test.go (about)

     1  package getproviders
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/eliastor/durgaform/internal/addrs"
     9  )
    10  
    11  func TestMultiSourceAvailableVersions(t *testing.T) {
    12  	platform1 := Platform{OS: "amigaos", Arch: "m68k"}
    13  	platform2 := Platform{OS: "aros", Arch: "arm"}
    14  
    15  	t.Run("unfiltered merging", func(t *testing.T) {
    16  		s1 := NewMockSource([]PackageMeta{
    17  			FakePackageMeta(
    18  				addrs.NewDefaultProvider("foo"),
    19  				MustParseVersion("1.0.0"),
    20  				VersionList{MustParseVersion("5.0")},
    21  				platform1,
    22  			),
    23  			FakePackageMeta(
    24  				addrs.NewDefaultProvider("foo"),
    25  				MustParseVersion("1.0.0"),
    26  				VersionList{MustParseVersion("5.0")},
    27  				platform2,
    28  			),
    29  			FakePackageMeta(
    30  				addrs.NewDefaultProvider("bar"),
    31  				MustParseVersion("1.0.0"),
    32  				VersionList{MustParseVersion("5.0")},
    33  				platform2,
    34  			),
    35  		},
    36  			nil,
    37  		)
    38  		s2 := NewMockSource([]PackageMeta{
    39  			FakePackageMeta(
    40  				addrs.NewDefaultProvider("foo"),
    41  				MustParseVersion("1.0.0"),
    42  				VersionList{MustParseVersion("5.0")},
    43  				platform1,
    44  			),
    45  			FakePackageMeta(
    46  				addrs.NewDefaultProvider("foo"),
    47  				MustParseVersion("1.2.0"),
    48  				VersionList{MustParseVersion("5.0")},
    49  				platform1,
    50  			),
    51  			FakePackageMeta(
    52  				addrs.NewDefaultProvider("bar"),
    53  				MustParseVersion("1.0.0"),
    54  				VersionList{MustParseVersion("5.0")},
    55  				platform1,
    56  			),
    57  		},
    58  			nil,
    59  		)
    60  		multi := MultiSource{
    61  			{Source: s1},
    62  			{Source: s2},
    63  		}
    64  
    65  		// AvailableVersions produces the union of all versions available
    66  		// across all of the sources.
    67  		got, _, err := multi.AvailableVersions(context.Background(), addrs.NewDefaultProvider("foo"))
    68  		if err != nil {
    69  			t.Fatalf("unexpected error: %s", err)
    70  		}
    71  		want := VersionList{
    72  			MustParseVersion("1.0.0"),
    73  			MustParseVersion("1.2.0"),
    74  		}
    75  
    76  		if diff := cmp.Diff(want, got); diff != "" {
    77  			t.Errorf("wrong result\n%s", diff)
    78  		}
    79  
    80  		_, _, err = multi.AvailableVersions(context.Background(), addrs.NewDefaultProvider("baz"))
    81  		if want, ok := err.(ErrRegistryProviderNotKnown); !ok {
    82  			t.Fatalf("wrong error type:\ngot:  %T\nwant: %T", err, want)
    83  		}
    84  	})
    85  
    86  	t.Run("merging with filters", func(t *testing.T) {
    87  		// This is just testing that filters are being honored at all, using a
    88  		// specific pair of filters. The different filter combinations
    89  		// themselves are tested in TestMultiSourceSelector.
    90  
    91  		s1 := NewMockSource([]PackageMeta{
    92  			FakePackageMeta(
    93  				addrs.NewDefaultProvider("foo"),
    94  				MustParseVersion("1.0.0"),
    95  				VersionList{MustParseVersion("5.0")},
    96  				platform1,
    97  			),
    98  			FakePackageMeta(
    99  				addrs.NewDefaultProvider("bar"),
   100  				MustParseVersion("1.0.0"),
   101  				VersionList{MustParseVersion("5.0")},
   102  				platform1,
   103  			),
   104  		},
   105  			nil,
   106  		)
   107  		s2 := NewMockSource([]PackageMeta{
   108  			FakePackageMeta(
   109  				addrs.NewDefaultProvider("foo"),
   110  				MustParseVersion("1.2.0"),
   111  				VersionList{MustParseVersion("5.0")},
   112  				platform1,
   113  			),
   114  			FakePackageMeta(
   115  				addrs.NewDefaultProvider("bar"),
   116  				MustParseVersion("1.2.0"),
   117  				VersionList{MustParseVersion("5.0")},
   118  				platform1,
   119  			),
   120  		},
   121  			nil,
   122  		)
   123  		multi := MultiSource{
   124  			{
   125  				Source:  s1,
   126  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"),
   127  			},
   128  			{
   129  				Source:  s2,
   130  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/bar"),
   131  			},
   132  		}
   133  
   134  		got, _, err := multi.AvailableVersions(context.Background(), addrs.NewDefaultProvider("foo"))
   135  		if err != nil {
   136  			t.Fatalf("unexpected error: %s", err)
   137  		}
   138  		want := VersionList{
   139  			MustParseVersion("1.0.0"),
   140  			// 1.2.0 isn't present because s3 doesn't include "foo"
   141  		}
   142  		if diff := cmp.Diff(want, got); diff != "" {
   143  			t.Errorf("wrong result\n%s", diff)
   144  		}
   145  
   146  		got, _, err = multi.AvailableVersions(context.Background(), addrs.NewDefaultProvider("bar"))
   147  		if err != nil {
   148  			t.Fatalf("unexpected error: %s", err)
   149  		}
   150  		want = VersionList{
   151  			MustParseVersion("1.0.0"),
   152  			MustParseVersion("1.2.0"), // included because s2 matches "bar"
   153  		}
   154  		if diff := cmp.Diff(want, got); diff != "" {
   155  			t.Errorf("wrong result\n%s", diff)
   156  		}
   157  
   158  		_, _, err = multi.AvailableVersions(context.Background(), addrs.NewDefaultProvider("baz"))
   159  		if want, ok := err.(ErrRegistryProviderNotKnown); !ok {
   160  			t.Fatalf("wrong error type:\ngot:  %T\nwant: %T", err, want)
   161  		}
   162  	})
   163  
   164  	t.Run("provider not found", func(t *testing.T) {
   165  		s1 := NewMockSource(nil, nil)
   166  		s2 := NewMockSource(nil, nil)
   167  		multi := MultiSource{
   168  			{Source: s1},
   169  			{Source: s2},
   170  		}
   171  
   172  		_, _, err := multi.AvailableVersions(context.Background(), addrs.NewDefaultProvider("foo"))
   173  		if err == nil {
   174  			t.Fatal("expected error, got success")
   175  		}
   176  
   177  		wantErr := `provider registry registry.durgaform.io does not have a provider named registry.terraform.io/hashicorp/foo`
   178  
   179  		if err.Error() != wantErr {
   180  			t.Fatalf("wrong error.\ngot:  %s\nwant: %s\n", err, wantErr)
   181  		}
   182  
   183  	})
   184  
   185  	t.Run("merging with warnings", func(t *testing.T) {
   186  		platform1 := Platform{OS: "amigaos", Arch: "m68k"}
   187  		platform2 := Platform{OS: "aros", Arch: "arm"}
   188  		s1 := NewMockSource([]PackageMeta{
   189  			FakePackageMeta(
   190  				addrs.NewDefaultProvider("bar"),
   191  				MustParseVersion("1.0.0"),
   192  				VersionList{MustParseVersion("5.0")},
   193  				platform2,
   194  			),
   195  		},
   196  			map[addrs.Provider]Warnings{
   197  				addrs.NewDefaultProvider("bar"): {"WARNING!"},
   198  			},
   199  		)
   200  		s2 := NewMockSource([]PackageMeta{
   201  			FakePackageMeta(
   202  				addrs.NewDefaultProvider("bar"),
   203  				MustParseVersion("1.0.0"),
   204  				VersionList{MustParseVersion("5.0")},
   205  				platform1,
   206  			),
   207  		},
   208  			nil,
   209  		)
   210  		multi := MultiSource{
   211  			{Source: s1},
   212  			{Source: s2},
   213  		}
   214  
   215  		// AvailableVersions produces the union of all versions available
   216  		// across all of the sources.
   217  		got, warns, err := multi.AvailableVersions(context.Background(), addrs.NewDefaultProvider("bar"))
   218  		if err != nil {
   219  			t.Fatalf("unexpected error: %s", err)
   220  		}
   221  		want := VersionList{
   222  			MustParseVersion("1.0.0"),
   223  		}
   224  		if diff := cmp.Diff(want, got); diff != "" {
   225  			t.Errorf("wrong result\n%s", diff)
   226  		}
   227  
   228  		if len(warns) != 1 {
   229  			t.Fatalf("wrong number of warnings. Got %d, wanted 1", len(warns))
   230  		}
   231  		if warns[0] != "WARNING!" {
   232  			t.Fatalf("wrong warnings. Got %s, wanted \"WARNING!\"", warns[0])
   233  		}
   234  	})
   235  }
   236  
   237  func TestMultiSourcePackageMeta(t *testing.T) {
   238  	platform1 := Platform{OS: "amigaos", Arch: "m68k"}
   239  	platform2 := Platform{OS: "aros", Arch: "arm"}
   240  
   241  	// We'll use the Filename field of the fake PackageMetas we created above
   242  	// to create a difference between the packages in s1 and the ones in s2,
   243  	// so we can test where individual packages came from below.
   244  	fakeFilename := func(fn string, meta PackageMeta) PackageMeta {
   245  		meta.Filename = fn
   246  		return meta
   247  	}
   248  
   249  	onlyInS1 := fakeFilename("s1", FakePackageMeta(
   250  		addrs.NewDefaultProvider("foo"),
   251  		MustParseVersion("1.0.0"),
   252  		VersionList{MustParseVersion("5.0")},
   253  		platform2,
   254  	))
   255  	onlyInS2 := fakeFilename("s2", FakePackageMeta(
   256  		addrs.NewDefaultProvider("foo"),
   257  		MustParseVersion("1.2.0"),
   258  		VersionList{MustParseVersion("5.0")},
   259  		platform1,
   260  	))
   261  	inBothS1 := fakeFilename("s1", FakePackageMeta(
   262  		addrs.NewDefaultProvider("foo"),
   263  		MustParseVersion("1.0.0"),
   264  		VersionList{MustParseVersion("5.0")},
   265  		platform1,
   266  	))
   267  	inBothS2 := fakeFilename("s2", inBothS1)
   268  	s1 := NewMockSource([]PackageMeta{
   269  		inBothS1,
   270  		onlyInS1,
   271  		fakeFilename("s1", FakePackageMeta(
   272  			addrs.NewDefaultProvider("bar"),
   273  			MustParseVersion("1.0.0"),
   274  			VersionList{MustParseVersion("5.0")},
   275  			platform2,
   276  		)),
   277  	},
   278  		nil,
   279  	)
   280  	s2 := NewMockSource([]PackageMeta{
   281  		inBothS2,
   282  		onlyInS2,
   283  		fakeFilename("s2", FakePackageMeta(
   284  			addrs.NewDefaultProvider("bar"),
   285  			MustParseVersion("1.0.0"),
   286  			VersionList{MustParseVersion("5.0")},
   287  			platform1,
   288  		)),
   289  	}, nil)
   290  	multi := MultiSource{
   291  		{Source: s1},
   292  		{Source: s2},
   293  	}
   294  
   295  	t.Run("only in s1", func(t *testing.T) {
   296  		got, err := multi.PackageMeta(
   297  			context.Background(),
   298  			addrs.NewDefaultProvider("foo"),
   299  			MustParseVersion("1.0.0"),
   300  			platform2,
   301  		)
   302  		want := onlyInS1
   303  		if err != nil {
   304  			t.Fatalf("unexpected error: %s", err)
   305  		}
   306  		if diff := cmp.Diff(want, got); diff != "" {
   307  			t.Errorf("wrong result\n%s", diff)
   308  		}
   309  	})
   310  	t.Run("only in s2", func(t *testing.T) {
   311  		got, err := multi.PackageMeta(
   312  			context.Background(),
   313  			addrs.NewDefaultProvider("foo"),
   314  			MustParseVersion("1.2.0"),
   315  			platform1,
   316  		)
   317  		want := onlyInS2
   318  		if err != nil {
   319  			t.Fatalf("unexpected error: %s", err)
   320  		}
   321  		if diff := cmp.Diff(want, got); diff != "" {
   322  			t.Errorf("wrong result\n%s", diff)
   323  		}
   324  	})
   325  	t.Run("in both", func(t *testing.T) {
   326  		got, err := multi.PackageMeta(
   327  			context.Background(),
   328  			addrs.NewDefaultProvider("foo"),
   329  			MustParseVersion("1.0.0"),
   330  			platform1,
   331  		)
   332  		want := inBothS1 // S1 "wins" because it's earlier in the MultiSource
   333  		if err != nil {
   334  			t.Fatalf("unexpected error: %s", err)
   335  		}
   336  		if diff := cmp.Diff(want, got); diff != "" {
   337  			t.Errorf("wrong result\n%s", diff)
   338  		}
   339  
   340  		// Make sure inBothS1 and inBothS2 really are different; if not then
   341  		// that's a test bug which we'd rather catch than have this test
   342  		// accidentally passing without actually checking anything.
   343  		if diff := cmp.Diff(inBothS1, inBothS2); diff == "" {
   344  			t.Fatalf("test bug: inBothS1 and inBothS2 are indistinguishable")
   345  		}
   346  	})
   347  	t.Run("in neither", func(t *testing.T) {
   348  		_, err := multi.PackageMeta(
   349  			context.Background(),
   350  			addrs.NewDefaultProvider("nonexist"),
   351  			MustParseVersion("1.0.0"),
   352  			platform1,
   353  		)
   354  		// This case reports "platform not supported" because it assumes that
   355  		// a caller would only pass to it package versions that were returned
   356  		// by a previousc all to AvailableVersions, and therefore a missing
   357  		// object ought to be valid provider/version but an unsupported
   358  		// platform.
   359  		if want, ok := err.(ErrPlatformNotSupported); !ok {
   360  			t.Fatalf("wrong error type:\ngot:  %T\nwant: %T", err, want)
   361  		}
   362  	})
   363  }
   364  
   365  func TestMultiSourceSelector(t *testing.T) {
   366  	emptySource := NewMockSource(nil, nil)
   367  
   368  	tests := map[string]struct {
   369  		Selector  MultiSourceSelector
   370  		Provider  addrs.Provider
   371  		WantMatch bool
   372  	}{
   373  		"default provider with no constraints": {
   374  			MultiSourceSelector{
   375  				Source: emptySource,
   376  			},
   377  			addrs.NewDefaultProvider("foo"),
   378  			true,
   379  		},
   380  		"built-in provider with no constraints": {
   381  			MultiSourceSelector{
   382  				Source: emptySource,
   383  			},
   384  			addrs.NewBuiltInProvider("bar"),
   385  			true,
   386  		},
   387  
   388  		// Include constraints
   389  		"default provider with include constraint that matches it exactly": {
   390  			MultiSourceSelector{
   391  				Source:  emptySource,
   392  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/foo"),
   393  			},
   394  			addrs.NewDefaultProvider("foo"),
   395  			true,
   396  		},
   397  		"default provider with include constraint that matches it via type wildcard": {
   398  			MultiSourceSelector{
   399  				Source:  emptySource,
   400  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"),
   401  			},
   402  			addrs.NewDefaultProvider("foo"),
   403  			true,
   404  		},
   405  		"default provider with include constraint that matches it via namespace wildcard": {
   406  			MultiSourceSelector{
   407  				Source:  emptySource,
   408  				Include: mustParseMultiSourceMatchingPatterns("*/*"),
   409  			},
   410  			addrs.NewDefaultProvider("foo"),
   411  			true,
   412  		},
   413  		"default provider with non-normalized include constraint that matches it via type wildcard": {
   414  			MultiSourceSelector{
   415  				Source:  emptySource,
   416  				Include: mustParseMultiSourceMatchingPatterns("HashiCorp/*"),
   417  			},
   418  			addrs.NewDefaultProvider("foo"),
   419  			true,
   420  		},
   421  		"built-in provider with exact include constraint that does not match it": {
   422  			MultiSourceSelector{
   423  				Source:  emptySource,
   424  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/foo"),
   425  			},
   426  			addrs.NewBuiltInProvider("bar"),
   427  			false,
   428  		},
   429  		"built-in provider with type-wild include constraint that does not match it": {
   430  			MultiSourceSelector{
   431  				Source:  emptySource,
   432  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"),
   433  			},
   434  			addrs.NewBuiltInProvider("bar"),
   435  			false,
   436  		},
   437  		"built-in provider with namespace-wild include constraint that does not match it": {
   438  			MultiSourceSelector{
   439  				Source:  emptySource,
   440  				Include: mustParseMultiSourceMatchingPatterns("*/*"),
   441  			},
   442  			// Doesn't match because builtin providers are in "durgaform.io",
   443  			// but a pattern with no hostname is for registry.durgaform.io.
   444  			addrs.NewBuiltInProvider("bar"),
   445  			false,
   446  		},
   447  		"built-in provider with include constraint that matches it via type wildcard": {
   448  			MultiSourceSelector{
   449  				Source:  emptySource,
   450  				Include: mustParseMultiSourceMatchingPatterns("durgaform.io/builtin/*"),
   451  			},
   452  			addrs.NewBuiltInProvider("bar"),
   453  			true,
   454  		},
   455  
   456  		// Exclude constraints
   457  		"default provider with exclude constraint that matches it exactly": {
   458  			MultiSourceSelector{
   459  				Source:  emptySource,
   460  				Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/foo"),
   461  			},
   462  			addrs.NewDefaultProvider("foo"),
   463  			false,
   464  		},
   465  		"default provider with exclude constraint that matches it via type wildcard": {
   466  			MultiSourceSelector{
   467  				Source:  emptySource,
   468  				Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/*"),
   469  			},
   470  			addrs.NewDefaultProvider("foo"),
   471  			false,
   472  		},
   473  		"default provider with exact exclude constraint that doesn't match it": {
   474  			MultiSourceSelector{
   475  				Source:  emptySource,
   476  				Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/bar"),
   477  			},
   478  			addrs.NewDefaultProvider("foo"),
   479  			true,
   480  		},
   481  		"default provider with non-normalized exclude constraint that matches it via type wildcard": {
   482  			MultiSourceSelector{
   483  				Source:  emptySource,
   484  				Exclude: mustParseMultiSourceMatchingPatterns("HashiCorp/*"),
   485  			},
   486  			addrs.NewDefaultProvider("foo"),
   487  			false,
   488  		},
   489  
   490  		// Both include and exclude in a single selector
   491  		"default provider with exclude wildcard overriding include exact": {
   492  			MultiSourceSelector{
   493  				Source:  emptySource,
   494  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/foo"),
   495  				Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/*"),
   496  			},
   497  			addrs.NewDefaultProvider("foo"),
   498  			false,
   499  		},
   500  		"default provider with exclude wildcard overriding irrelevant include exact": {
   501  			MultiSourceSelector{
   502  				Source:  emptySource,
   503  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/bar"),
   504  				Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/*"),
   505  			},
   506  			addrs.NewDefaultProvider("foo"),
   507  			false,
   508  		},
   509  		"default provider with exclude exact overriding include wildcard": {
   510  			MultiSourceSelector{
   511  				Source:  emptySource,
   512  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"),
   513  				Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/foo"),
   514  			},
   515  			addrs.NewDefaultProvider("foo"),
   516  			false,
   517  		},
   518  		"default provider with irrelevant exclude exact overriding include wildcard": {
   519  			MultiSourceSelector{
   520  				Source:  emptySource,
   521  				Include: mustParseMultiSourceMatchingPatterns("hashicorp/*"),
   522  				Exclude: mustParseMultiSourceMatchingPatterns("hashicorp/bar"),
   523  			},
   524  			addrs.NewDefaultProvider("foo"),
   525  			true,
   526  		},
   527  	}
   528  
   529  	for name, test := range tests {
   530  		t.Run(name, func(t *testing.T) {
   531  			t.Logf("include:  %s", test.Selector.Include)
   532  			t.Logf("exclude:  %s", test.Selector.Exclude)
   533  			t.Logf("provider: %s", test.Provider)
   534  			got := test.Selector.CanHandleProvider(test.Provider)
   535  			want := test.WantMatch
   536  			if got != want {
   537  				t.Errorf("wrong result %t; want %t", got, want)
   538  			}
   539  		})
   540  	}
   541  }
   542  
   543  func mustParseMultiSourceMatchingPatterns(strs ...string) MultiSourceMatchingPatterns {
   544  	ret, err := ParseMultiSourceMatchingPatterns(strs)
   545  	if err != nil {
   546  		panic(err)
   547  	}
   548  	return ret
   549  }