1
2
3
4
5
6
7
8
9
10
11
12
13
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
157
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
250
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