...

Source file src/cuelang.org/go/mod/modregistry/client_test.go

Documentation: cuelang.org/go/mod/modregistry

     1  // Copyright 2023 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package modregistry
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/go-quicktest/qt"
    28  
    29  	"golang.org/x/tools/txtar"
    30  
    31  	"cuelabs.dev/go/oci/ociregistry/ocimem"
    32  
    33  	"cuelang.org/go/internal/mod/semver"
    34  	"cuelang.org/go/mod/module"
    35  	"cuelang.org/go/mod/modzip"
    36  )
    37  
    38  func newTestClient(t *testing.T) *Client {
    39  	return NewClient(ocimem.New())
    40  }
    41  
    42  func TestPutGetModule(t *testing.T) {
    43  	const testMod = `
    44  -- cue.mod/module.cue --
    45  module: "example.com/module@v1"
    46  
    47  -- x.cue --
    48  x: 42
    49  `
    50  	ctx := context.Background()
    51  	mv := module.MustParseVersion("example.com/module@v1.2.3")
    52  	c := newTestClient(t)
    53  	zipData := putModule(t, c, mv, testMod)
    54  
    55  	m, err := c.GetModule(ctx, mv)
    56  	qt.Assert(t, qt.IsNil(err))
    57  
    58  	r, err := m.GetZip(ctx)
    59  	qt.Assert(t, qt.IsNil(err))
    60  	data, err := io.ReadAll(r)
    61  	qt.Assert(t, qt.IsNil(err))
    62  	qt.Assert(t, qt.DeepEquals(data, zipData))
    63  
    64  	tags, err := c.ModuleVersions(ctx, mv.Path())
    65  	qt.Assert(t, qt.IsNil(err))
    66  	qt.Assert(t, qt.DeepEquals(tags, []string{"v1.2.3"}))
    67  }
    68  
    69  func TestModuleVersions(t *testing.T) {
    70  	ctx := context.Background()
    71  	c := newTestClient(t)
    72  	for _, v := range []string{"v1.0.0", "v2.3.3-alpha", "v1.2.3", "v0.23.676", "v3.2.1"} {
    73  		mpath := "example.com/module@" + semver.Major(v)
    74  		modContents := fmt.Sprintf(`
    75  -- cue.mod/module.cue --
    76  module: %q
    77  
    78  -- x.cue --
    79  x: 42
    80  `, mpath)
    81  		putModule(t, c, module.MustParseVersion("example.com/module@"+v), modContents)
    82  	}
    83  	tags, err := c.ModuleVersions(ctx, "example.com/module")
    84  	qt.Assert(t, qt.IsNil(err))
    85  	qt.Assert(t, qt.DeepEquals(tags, []string{"v0.23.676", "v1.0.0", "v1.2.3", "v2.3.3-alpha", "v3.2.1"}))
    86  
    87  	tags, err = c.ModuleVersions(ctx, "example.com/module@v1")
    88  	qt.Assert(t, qt.IsNil(err))
    89  	qt.Assert(t, qt.DeepEquals(tags, []string{"v1.0.0", "v1.2.3"}))
    90  }
    91  
    92  func TestPutGetWithDependencies(t *testing.T) {
    93  	const testMod = `
    94  -- cue.mod/module.cue --
    95  module: "foo.com/bar@v0"
    96  deps: "example.com@v1": v: "v1.2.3"
    97  deps: "other.com/something@v0": v: "v0.2.3"
    98  
    99  -- x.cue --
   100  package bar
   101  
   102  import (
   103  	a "example.com"
   104  	"other.com/something"
   105  )
   106  x: a.foo + something.bar
   107  `
   108  	ctx := context.Background()
   109  	mv := module.MustParseVersion("foo.com/bar@v0.5.100")
   110  	c := newTestClient(t)
   111  	zipData := putModule(t, c, mv, testMod)
   112  
   113  	m, err := c.GetModule(ctx, mv)
   114  	qt.Assert(t, qt.IsNil(err))
   115  
   116  	r, err := m.GetZip(ctx)
   117  	qt.Assert(t, qt.IsNil(err))
   118  	data, err := io.ReadAll(r)
   119  	qt.Assert(t, qt.IsNil(err))
   120  	qt.Assert(t, qt.DeepEquals(data, zipData))
   121  
   122  	tags, err := c.ModuleVersions(ctx, mv.Path())
   123  	qt.Assert(t, qt.IsNil(err))
   124  	qt.Assert(t, qt.DeepEquals(tags, []string{"v0.5.100"}))
   125  }
   126  
   127  func TestGetModuleWithManifest(t *testing.T) {
   128  	const testMod = `
   129  -- cue.mod/module.cue --
   130  module: "foo.com/bar@v0"
   131  deps: "example.com@v1": v: "v1.2.3"
   132  deps: "other.com/something@v0": v: "v0.2.3"
   133  
   134  -- x.cue --
   135  package bar
   136  
   137  import (
   138  	a "example.com"
   139  	"other.com/something"
   140  )
   141  x: a.foo + something.bar
   142  `
   143  	ctx := context.Background()
   144  	mv := module.MustParseVersion("foo.com/bar@v0.5.100")
   145  	reg := ocimem.New()
   146  
   147  	c := NewClient(reg)
   148  	zipData := putModule(t, c, mv, testMod)
   149  
   150  	mr, err := reg.GetTag(ctx, "foo.com/bar", "v0.5.100")
   151  	qt.Assert(t, qt.IsNil(err))
   152  	defer mr.Close()
   153  	mdata, err := io.ReadAll(mr)
   154  	qt.Assert(t, qt.IsNil(err))
   155  
   156  	// Remove the tag so that we're sure it isn't
   157  	// used for the GetModuleWithManifest call.
   158  	err = reg.DeleteTag(ctx, "foo.com/bar", "v0.5.100")
   159  	qt.Assert(t, qt.IsNil(err))
   160  
   161  	m, err := c.GetModuleWithManifest(ctx, mv, mdata, "application/json")
   162  	qt.Assert(t, qt.IsNil(err))
   163  
   164  	r, err := m.GetZip(ctx)
   165  	qt.Assert(t, qt.IsNil(err))
   166  	data, err := io.ReadAll(r)
   167  	qt.Assert(t, qt.IsNil(err))
   168  	qt.Assert(t, qt.DeepEquals(data, zipData))
   169  }
   170  
   171  func TestPutWithInvalidDependencyVersion(t *testing.T) {
   172  	const testMod = `
   173  -- cue.mod/module.cue --
   174  module: "foo.com/bar@v0"
   175  deps: "example.com@v1": v: "v1.2"
   176  
   177  -- x.cue --
   178  x: 42
   179  `
   180  	mv := module.MustParseVersion("foo.com/bar@v0.5.100")
   181  	c := newTestClient(t)
   182  	zipData := createZip(t, mv, testMod)
   183  	err := c.PutModule(context.Background(), mv, bytes.NewReader(zipData), int64(len(zipData)))
   184  	qt.Assert(t, qt.ErrorMatches(err, `module.cue file check failed: invalid module.cue file cue.mod/module.cue: cannot make version from module "example.com@v1", version "v1.2": version "v1.2" \(of module "example.com@v1"\) is not canonical`))
   185  }
   186  
   187  var checkModuleTests = []struct {
   188  	testName  string
   189  	mv        module.Version
   190  	content   string
   191  	wantError string
   192  }{{
   193  	testName: "Minimal",
   194  	mv:       module.MustNewVersion("foo.com/bar", "v0.1.2"),
   195  	content: `
   196  -- cue.mod/module.cue --
   197  module: "foo.com/bar@v0"
   198  `,
   199  }, {
   200  	testName: "MismatchedMajorVersion",
   201  	mv:       module.MustNewVersion("foo.com/bar", "v0.1.2"),
   202  	content: `
   203  -- cue.mod/module.cue --
   204  module: "foo.com/bar@v1"
   205  `,
   206  	wantError: `module.cue file check failed: module path "foo.com/bar@v1" found in cue.mod/module.cue does not match module path being published "foo.com/bar@v0"`,
   207  }, {
   208  	testName: "ModuleWithMinorVersion",
   209  	mv:       module.MustNewVersion("foo.com/bar", "v1.2.3"),
   210  	content: `
   211  -- cue.mod/module.cue --
   212  module: "foo@v1.2.3"
   213  `,
   214  	wantError: `module.cue file check failed: module path foo@v1.2.3 in "cue.mod/module.cue" should contain the major version only`,
   215  }, {
   216  	testName: "DependencyWithInvalidVersion",
   217  	mv:       module.MustNewVersion("foo.com/bar", "v1.2.3"),
   218  	content: `
   219  -- cue.mod/module.cue --
   220  module: "foo@v1"
   221  deps: "foo.com/bar@v2": v: "invalid"
   222  `,
   223  	wantError: `module.cue file check failed: invalid module.cue file cue.mod/module.cue: cannot make version from module "foo.com/bar@v2", version "invalid": version "invalid" \(of module "foo.com/bar@v2"\) is not well formed`,
   224  }}
   225  
   226  func TestCheckModule(t *testing.T) {
   227  	for _, test := range checkModuleTests {
   228  		t.Run(test.testName, func(t *testing.T) {
   229  			data := createZip(t, test.mv, test.content)
   230  			m, err := checkModule(test.mv, bytes.NewReader(data), int64(len(data)))
   231  			if test.wantError != "" {
   232  				qt.Assert(t, qt.ErrorMatches(err, test.wantError))
   233  				return
   234  			}
   235  			qt.Assert(t, qt.IsNil(err))
   236  			qt.Assert(t, qt.Not(qt.IsNil(m)))
   237  			qt.Assert(t, qt.DeepEquals(m.mv, test.mv))
   238  		})
   239  	}
   240  }
   241  
   242  func TestModuleVersionsOnNonExistentModule(t *testing.T) {
   243  	c := newTestClient(t)
   244  	ctx := context.Background()
   245  	tags, err := c.ModuleVersions(ctx, "not/there@v0")
   246  	qt.Assert(t, qt.IsNil(err))
   247  	qt.Assert(t, qt.HasLen(tags, 0))
   248  
   249  	// Bad names hit a slightly different code path, so make
   250  	// sure they work OK too.
   251  	tags, err = c.ModuleVersions(ctx, "bad--NAME-@v0")
   252  	qt.Assert(t, qt.IsNil(err))
   253  	qt.Assert(t, qt.HasLen(tags, 0))
   254  }
   255  
   256  func putModule(t *testing.T, c *Client, mv module.Version, txtarData string) []byte {
   257  	zipData := createZip(t, mv, txtarData)
   258  	err := c.PutModule(context.Background(), mv, bytes.NewReader(zipData), int64(len(zipData)))
   259  	qt.Assert(t, qt.IsNil(err))
   260  	return zipData
   261  }
   262  
   263  func createZip(t *testing.T, mv module.Version, txtarData string) []byte {
   264  	ar := txtar.Parse([]byte(txtarData))
   265  	var zipContent bytes.Buffer
   266  	err := modzip.Create[txtar.File](&zipContent, mv, ar.Files, txtarFileIO{})
   267  	qt.Assert(t, qt.IsNil(err))
   268  	return zipContent.Bytes()
   269  }
   270  
   271  type txtarFileIO struct{}
   272  
   273  func (txtarFileIO) Path(f txtar.File) string {
   274  	return f.Name
   275  }
   276  
   277  func (txtarFileIO) Lstat(f txtar.File) (os.FileInfo, error) {
   278  	return txtarFileInfo{f}, nil
   279  }
   280  
   281  func (txtarFileIO) Open(f txtar.File) (io.ReadCloser, error) {
   282  	return io.NopCloser(bytes.NewReader(f.Data)), nil
   283  }
   284  
   285  func (txtarFileIO) Mode() os.FileMode {
   286  	return 0o444
   287  }
   288  
   289  type txtarFileInfo struct {
   290  	f txtar.File
   291  }
   292  
   293  func (fi txtarFileInfo) Name() string {
   294  	return path.Base(fi.f.Name)
   295  }
   296  
   297  func (fi txtarFileInfo) Size() int64 {
   298  	return int64(len(fi.f.Data))
   299  }
   300  
   301  func (fi txtarFileInfo) Mode() os.FileMode {
   302  	return 0o644
   303  }
   304  
   305  func (fi txtarFileInfo) ModTime() time.Time { return time.Time{} }
   306  func (fi txtarFileInfo) IsDir() bool        { return false }
   307  func (fi txtarFileInfo) Sys() interface{}   { return nil }
   308  

View as plain text