cuelang.org/go@v0.10.1/internal/mod/modresolve/resolve_test.go (about)

     1  package modresolve
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"cuelang.org/go/cue"
    10  	"cuelang.org/go/cue/cuecontext"
    11  	"github.com/go-quicktest/qt"
    12  )
    13  
    14  func TestRegistryConfigSchema(t *testing.T) {
    15  	schema := RegistryConfigSchema()
    16  	// Sanity check that it parses OK as CUE and can
    17  	// validate a legitimate schema.
    18  	ctx := cuecontext.New()
    19  	v := ctx.CompileString(schema)
    20  	fileSchema := v.LookupPath(cue.MakePath(cue.Def("#file")))
    21  	qt.Assert(t, qt.IsNil(fileSchema.Err()))
    22  	cfgVal := ctx.CompileString(`defaultRegistry: registry: "something.example"`)
    23  	qt.Assert(t, qt.IsNil(cfgVal.Err()))
    24  	cfgVal = cfgVal.Unify(fileSchema)
    25  	qt.Assert(t, qt.IsNil(cfgVal.Err()))
    26  }
    27  
    28  func TestParseCUERegistry(t *testing.T) {
    29  	testCases := []struct {
    30  		testName        string
    31  		in              string
    32  		catchAllDefault string
    33  		err             string
    34  		wantAllHosts    []Host
    35  		lookups         map[string]*Location
    36  	}{{
    37  		testName: "MultipleFallbacks",
    38  		in:       "registry.somewhere,registry.other",
    39  		err:      "duplicate catch-all registry",
    40  	}, {
    41  		testName:        "NoRegistryOrDefault",
    42  		catchAllDefault: "",
    43  		err:             "no catch-all registry or default",
    44  	}, {
    45  		testName: "InvalidRegistry",
    46  		in:       "$#foo",
    47  		err:      `invalid registry "\$#foo": invalid host name "\$#foo" in registry`,
    48  	}, {
    49  		testName: "InvalidSecuritySuffix",
    50  		in:       "foo.com+bogus",
    51  		err:      `invalid registry "foo.com\+bogus": unknown suffix \("\+bogus"\), need \+insecure, \+secure or no suffix\)`,
    52  	}, {
    53  		testName: "IPV6AddrWithoutBrackets",
    54  		in:       "::1",
    55  		err:      `invalid registry "::1": invalid host name "::1" in registry`,
    56  	}, {
    57  		testName: "EmptyElement",
    58  		in:       "foo.com,",
    59  		err:      `empty registry part`,
    60  	}, {
    61  		testName: "MissingPrefix",
    62  		in:       "=foo.com",
    63  		err:      `empty module prefix`,
    64  	}, {
    65  		testName: "MissingRegistry",
    66  		in:       "x.com=",
    67  		err:      `empty registry reference`,
    68  	}, {
    69  		testName: "InvalidModulePrefix",
    70  		in:       "foo#=foo.com",
    71  		err:      `invalid module path "foo#": invalid char '#'`,
    72  	}, {
    73  		testName: "DuplicateModulePrefix",
    74  		in:       "x.com=r.org,x.com=q.org",
    75  		err:      `duplicate module prefix "x.com"`,
    76  	}, {
    77  		testName: "NoDefaultCatchAll",
    78  		in:       "x.com=r.org",
    79  		err:      `no default catch-all registry provided`,
    80  	}, {
    81  		testName:        "InvalidCatchAll",
    82  		in:              "x.com=r.org",
    83  		catchAllDefault: "bogus",
    84  		err:             `invalid catch-all registry "bogus": invalid host name "bogus" in registry`,
    85  	}, {
    86  		testName: "InvalidRegistryRef",
    87  		in:       "foo.com//bar",
    88  		err:      `invalid registry "foo.com//bar": invalid reference syntax \("foo.com//bar"\)`,
    89  	}, {
    90  		testName: "RegistryRefWithDigest",
    91  		in:       "foo.com/bar@sha256:f3c16f525a1b7c204fc953d6d7db7168d84ebf4902f83c3a37d113b18c28981f",
    92  		err:      `invalid registry "foo.com/bar@sha256:f3c16f525a1b7c204fc953d6d7db7168d84ebf4902f83c3a37d113b18c28981f": cannot have an associated tag or digest`,
    93  	}, {
    94  		testName: "RegistryRefWithTag",
    95  		in:       "foo.com/bar:sometag",
    96  		err:      `invalid registry "foo.com/bar:sometag": cannot have an associated tag or digest`,
    97  	}, {
    98  		testName: "MismatchedSecurity",
    99  		in:       "foo.com/bar+secure,other.example=foo.com/bar+insecure",
   100  		err:      `registry host "foo.com" is specified both as secure and insecure`,
   101  	}, {
   102  		testName:        "SingleCatchAll",
   103  		catchAllDefault: "registry.somewhere",
   104  		wantAllHosts:    []Host{{"registry.somewhere", false}},
   105  		lookups: map[string]*Location{
   106  			"fruit.com/apple": {
   107  				Host:       "registry.somewhere",
   108  				Repository: "fruit.com/apple",
   109  			},
   110  		},
   111  	}, {
   112  		testName:     "CatchAllWithNoDefault",
   113  		in:           "registry.somewhere",
   114  		wantAllHosts: []Host{{"registry.somewhere", false}},
   115  		lookups: map[string]*Location{
   116  			"fruit.com/apple": {
   117  				Host:       "registry.somewhere",
   118  				Repository: "fruit.com/apple",
   119  			},
   120  		},
   121  	}, {
   122  		testName:        "CatchAllWithDefault",
   123  		in:              "registry.somewhere",
   124  		catchAllDefault: "other.cue.somewhere",
   125  		wantAllHosts:    []Host{{"registry.somewhere", false}},
   126  		lookups: map[string]*Location{
   127  			"fruit.com/apple": {
   128  				Host:       "registry.somewhere",
   129  				Repository: "fruit.com/apple",
   130  			},
   131  			"": nil,
   132  		},
   133  	}, {
   134  		testName:     "PrefixWithCatchAllNoDefault",
   135  		in:           "example.com=registry.example.com/offset,registry.somewhere",
   136  		wantAllHosts: []Host{{"registry.example.com", false}, {"registry.somewhere", false}},
   137  		lookups: map[string]*Location{
   138  			"fruit.com/apple": {
   139  				Host:       "registry.somewhere",
   140  				Repository: "fruit.com/apple",
   141  			},
   142  			"example.com/blah": {
   143  				Host:       "registry.example.com",
   144  				Repository: "offset/example.com/blah",
   145  			},
   146  			"example.com": {
   147  				Host:       "registry.example.com",
   148  				Repository: "offset/example.com",
   149  			},
   150  		},
   151  	}, {
   152  		testName:        "PrefixWithCatchAllDefault",
   153  		in:              "example.com=registry.example.com/offset",
   154  		catchAllDefault: "registry.somewhere",
   155  		wantAllHosts:    []Host{{"registry.example.com", false}, {"registry.somewhere", false}},
   156  		lookups: map[string]*Location{
   157  			"fruit.com/apple": {
   158  				Host:       "registry.somewhere",
   159  				Repository: "fruit.com/apple",
   160  			},
   161  			"example.com/blah": {
   162  				Host:       "registry.example.com",
   163  				Repository: "offset/example.com/blah",
   164  			},
   165  		},
   166  	}, {
   167  		testName:        "PrefixWithCatchAllDefaultAndExplicitNoneFallback",
   168  		in:              "example.com=registry.example.com/offset,none",
   169  		catchAllDefault: "registry.somewhere",
   170  		wantAllHosts:    []Host{{"registry.example.com", false}},
   171  		lookups: map[string]*Location{
   172  			"fruit.com/apple": nil,
   173  			"example.com/blah": {
   174  				Host:       "registry.example.com",
   175  				Repository: "offset/example.com/blah",
   176  			},
   177  		},
   178  	}, {
   179  		testName:        "PrefixWithExplicitNone",
   180  		in:              "example.com=none",
   181  		catchAllDefault: "registry.somewhere",
   182  		wantAllHosts:    []Host{{"registry.somewhere", false}},
   183  		lookups: map[string]*Location{
   184  			"fruit.com/apple": {
   185  				Host:       "registry.somewhere",
   186  				Repository: "fruit.com/apple",
   187  			},
   188  			"example.com/blah": nil,
   189  		},
   190  	}, {
   191  		testName:     "LocalhostIsInsecure",
   192  		in:           "localhost:5000",
   193  		wantAllHosts: []Host{{"localhost:5000", true}},
   194  		lookups: map[string]*Location{
   195  			"fruit.com/apple": {
   196  				Host:       "localhost:5000",
   197  				Insecure:   true,
   198  				Repository: "fruit.com/apple",
   199  			},
   200  		},
   201  	}, {
   202  		testName:     "SecureLocalhost",
   203  		in:           "localhost:1234+secure",
   204  		wantAllHosts: []Host{{"localhost:1234", false}},
   205  		lookups: map[string]*Location{
   206  			"fruit.com/apple": {
   207  				Host:       "localhost:1234",
   208  				Repository: "fruit.com/apple",
   209  			},
   210  		},
   211  	}, {
   212  		testName:     "127.0.0.1IsInsecure",
   213  		in:           "127.0.0.1",
   214  		wantAllHosts: []Host{{"127.0.0.1", true}},
   215  		lookups: map[string]*Location{
   216  			"fruit.com/apple": {
   217  				Host:       "127.0.0.1",
   218  				Insecure:   true,
   219  				Repository: "fruit.com/apple",
   220  			},
   221  		},
   222  	}, {
   223  		testName:     "[::1]IsInsecure",
   224  		in:           "[::1]",
   225  		wantAllHosts: []Host{{"[::1]", true}},
   226  		lookups: map[string]*Location{
   227  			"fruit.com/apple": {
   228  				Host:       "[::1]",
   229  				Insecure:   true,
   230  				Repository: "fruit.com/apple",
   231  			},
   232  		},
   233  	}, {
   234  		testName:     "[0:0::1]IsInsecure",
   235  		in:           "[0:0::1]",
   236  		wantAllHosts: []Host{{"[0:0::1]", true}},
   237  		lookups: map[string]*Location{
   238  			"fruit.com/apple": {
   239  				Host:       "[0:0::1]",
   240  				Insecure:   true,
   241  				Repository: "fruit.com/apple",
   242  			},
   243  		},
   244  	}}
   245  
   246  	for _, tc := range testCases {
   247  		t.Run(tc.testName, func(t *testing.T) {
   248  			r, err := ParseCUERegistry(tc.in, tc.catchAllDefault)
   249  			if tc.err != "" {
   250  				qt.Assert(t, qt.ErrorMatches(err, tc.err))
   251  				return
   252  			}
   253  			qt.Assert(t, qt.IsNil(err))
   254  			qt.Check(t, qt.DeepEquals(r.AllHosts(), tc.wantAllHosts))
   255  			testLookups(t, r, tc.lookups)
   256  		})
   257  	}
   258  }
   259  
   260  func TestParseConfig(t *testing.T) {
   261  	testCases := []struct {
   262  		testName        string
   263  		in              string
   264  		catchAllDefault string
   265  		err             string
   266  		wantAllHosts    []Host
   267  		lookups         map[string]*Location
   268  	}{{
   269  		testName:        "NoRegistryOrDefault",
   270  		catchAllDefault: "",
   271  		err:             "no default catch-all registry provided",
   272  	}, {
   273  		testName: "InvalidRegistry",
   274  		in: `
   275  defaultRegistry: registry: "$#foo"
   276  `,
   277  		err: `invalid default registry configuration: invalid host name "\$#foo" in registry`,
   278  	}, {
   279  		testName: "EncHashAsRepo",
   280  		in: `
   281  defaultRegistry: {
   282  	registry: "registry.somewhere/hello"
   283  	pathEncoding: "hashAsRepo"
   284  	prefixForTags: "mod-"
   285  }
   286  `,
   287  		wantAllHosts: []Host{{"registry.somewhere", false}},
   288  		lookups: map[string]*Location{
   289  			"foo.com/bar v1.2.3": {
   290  				Host:       "registry.somewhere",
   291  				Repository: "hello/" + hashOf("foo.com/bar"),
   292  				Tag:        "mod-v1.2.3",
   293  			},
   294  		},
   295  	}, {
   296  		testName: "EncHashAsTag",
   297  		in: `
   298  defaultRegistry: {
   299  	registry: "registry.somewhere/hello"
   300  	pathEncoding: "hashAsTag"
   301  	prefixForTags: "mod-"
   302  }
   303  `,
   304  		wantAllHosts: []Host{{"registry.somewhere", false}},
   305  		lookups: map[string]*Location{
   306  			"foo.com/bar v1.2.3": {
   307  				Host:       "registry.somewhere",
   308  				Repository: "hello",
   309  				Tag:        "mod-" + hashOf("foo.com/bar") + "-v1.2.3",
   310  			},
   311  		},
   312  	}, {
   313  		testName: "DefaultRegistryWithModuleRegistries",
   314  		in: `
   315  defaultRegistry: {
   316  	registry: "registry.somewhere"
   317  }
   318  moduleRegistries: {
   319  	"a.com": {
   320  		registry: "registry.otherwhere"
   321  	}
   322  }
   323  `,
   324  		wantAllHosts: []Host{{"registry.otherwhere", false}, {"registry.somewhere", false}},
   325  		lookups: map[string]*Location{
   326  			"a.com v0.0.1": {
   327  				Host:       "registry.otherwhere",
   328  				Repository: "a.com",
   329  				Tag:        "v0.0.1",
   330  			},
   331  			"b.com v0.0.1": {
   332  				Host:       "registry.somewhere",
   333  				Repository: "b.com",
   334  				Tag:        "v0.0.1",
   335  			},
   336  		},
   337  	}, {
   338  		testName:        "DiverseRegistries",
   339  		catchAllDefault: "default.example/foo",
   340  		in: `
   341  moduleRegistries: {
   342  	"a.com": {
   343  		registry: "r1.example/a/b+insecure"
   344  	}
   345  	"a.com/foo/bar": {
   346  		registry: "r2.example/xxx"
   347  		pathEncoding: "hashAsRepo"
   348  		prefixForTags: "cue-"
   349  	}
   350  	"a.com/foo": {
   351  		registry: "r1.example/hello+insecure"
   352  	}
   353  	"stripped.org/bar": {
   354  		registry: "r3.example/repo"
   355  		stripPrefix: true
   356  	}
   357  	"badmodules.org": {
   358  		registry: "none"
   359  	}
   360  }
   361  `,
   362  		wantAllHosts: []Host{{
   363  			Name: "default.example",
   364  		}, {
   365  			Name:     "r1.example",
   366  			Insecure: true,
   367  		}, {
   368  			Name: "r2.example",
   369  		}, {
   370  			Name: "r3.example",
   371  		}},
   372  		lookups: map[string]*Location{
   373  			"a.com/other/bar/baz v0.0.1": {
   374  				Host:       "r1.example",
   375  				Insecure:   true,
   376  				Repository: "a/b/a.com/other/bar/baz",
   377  				Tag:        "v0.0.1",
   378  			},
   379  			"a.com/foo/bar v0.0.1": {
   380  				Host:       "r2.example",
   381  				Repository: "xxx/" + hashOf("a.com/foo/bar"),
   382  				Tag:        "cue-v0.0.1",
   383  			},
   384  			"a.com/foo/bar": {
   385  				Host:       "r2.example",
   386  				Repository: "xxx/" + hashOf("a.com/foo/bar"),
   387  				Tag:        "cue-",
   388  			},
   389  			"a.com/foo/baz v0.0.1": {
   390  				Host:       "r1.example",
   391  				Insecure:   true,
   392  				Repository: "hello/a.com/foo/baz",
   393  				Tag:        "v0.0.1",
   394  			},
   395  			"a.com/food v0.0.1": {
   396  				Host:       "r1.example",
   397  				Insecure:   true,
   398  				Repository: "a/b/a.com/food",
   399  				Tag:        "v0.0.1",
   400  			},
   401  			"b.com v0.0.1": {
   402  				Host:       "default.example",
   403  				Repository: "foo/b.com",
   404  				Tag:        "v0.0.1",
   405  			},
   406  			"stripped.org/bar/one/two/three v0.0.1": {
   407  				Host:       "r3.example",
   408  				Repository: "repo/one/two/three",
   409  				Tag:        "v0.0.1",
   410  			},
   411  			"stripped.org/bar v0.0.1": {
   412  				Host:       "r3.example",
   413  				Repository: "repo",
   414  				Tag:        "v0.0.1",
   415  			},
   416  			"badmodules.org/something v1.2.3": nil,
   417  			"badmodules.org v1.2.3":           nil,
   418  		},
   419  	}, {
   420  		testName: "InvalidModulePath",
   421  		in: `
   422  moduleRegistries: "bad+module": {
   423  	registry: "foo.com"
   424  }
   425  `,
   426  		err: `invalid module path "bad\+module": invalid char '\+'`,
   427  	}, {
   428  		testName: "InvalidHost",
   429  		in: `
   430  moduleRegistries: "foo.example": {
   431  		registry: "badhost:"
   432  }
   433  `,
   434  		err: `invalid registry configuration in "foo.example": invalid host name "badhost:" in registry`,
   435  	}, {
   436  		testName: "InvalidRepository",
   437  		in: `
   438  moduleRegistries: "foo.example": {
   439  		registry: "ok.com/A"
   440  }
   441  `,
   442  		err: `invalid registry configuration in "foo.example": invalid reference syntax \("ok.com/A"\)`,
   443  	}, {
   444  		testName: "UnknownField",
   445  		in: `
   446  registiries: "foo.example": {
   447  		registry: "ok.com/A",
   448  }
   449  `,
   450  		err: `invalid configuration file: registiries: field not allowed`,
   451  	}, {
   452  		testName:        "MismatchedSecurity",
   453  		catchAllDefault: "c.example",
   454  		in: `
   455  moduleRegistries: {
   456  	"a.example": {
   457  		registry: "ok.com+insecure"
   458  	}
   459  	"b.example": {
   460  		registry: "ok.com"
   461  	}
   462  }
   463  `,
   464  		err: `registry host "ok.com" is specified both as secure and insecure`,
   465  	}, {
   466  		testName:        "StripPrefixWithNoRepo",
   467  		catchAllDefault: "c.example",
   468  		in: `
   469  moduleRegistries: {
   470  	"a.example/foo": {
   471  		registry: "foo.example"
   472  		stripPrefix: true
   473  	}
   474  }
   475  `,
   476  		err: `invalid registry configuration in "a.example/foo": use of stripPrefix requires a non-empty repository within the registry`,
   477  	}}
   478  
   479  	for _, tc := range testCases {
   480  		t.Run(tc.testName, func(t *testing.T) {
   481  			r, err := ParseConfig([]byte(tc.in), "somefile.cue", tc.catchAllDefault)
   482  			if tc.err != "" {
   483  				qt.Assert(t, qt.ErrorMatches(err, tc.err))
   484  				return
   485  			}
   486  			qt.Assert(t, qt.IsNil(err))
   487  			qt.Check(t, qt.DeepEquals(r.AllHosts(), tc.wantAllHosts))
   488  			testLookups(t, r, tc.lookups)
   489  		})
   490  	}
   491  }
   492  
   493  func testLookups(t *testing.T, r LocationResolver, lookups map[string]*Location) {
   494  	for key, want := range lookups {
   495  		t.Run(key, func(t *testing.T) {
   496  			m, v, _ := strings.Cut(key, " ")
   497  			got, ok := r.ResolveToLocation(m, v)
   498  			if want == nil {
   499  				qt.Assert(t, qt.IsFalse(ok))
   500  			} else {
   501  				qt.Assert(t, qt.DeepEquals(&got, want))
   502  			}
   503  		})
   504  	}
   505  }
   506  
   507  func hashOf(s string) string {
   508  	return fmt.Sprintf("%x", sha256.Sum256([]byte(s)))
   509  }