1 package modconfig
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io/fs"
8 "net/http"
9 "net/http/httptest"
10 "net/url"
11 "os"
12 "path"
13 "path/filepath"
14 "slices"
15 "strings"
16 "sync/atomic"
17 "testing"
18
19 "github.com/go-quicktest/qt"
20 "golang.org/x/tools/txtar"
21
22 "cuelang.org/go/internal/cueversion"
23 "cuelang.org/go/internal/registrytest"
24 "cuelang.org/go/internal/txtarfs"
25 "cuelang.org/go/mod/modcache"
26 "cuelang.org/go/mod/module"
27 )
28
29
30
31
32
33 func TestNewRegistry(t *testing.T) {
34 modules := txtar.Parse([]byte(`
35 -- r1/foo.example_v0.0.1/cue.mod/module.cue --
36 module: "foo.example@v0"
37 deps: "bar.example@v0": v: "v0.0.1"
38 -- r1/foo.example_v0.0.1/bar/bar.cue --
39 package bar
40 -- r1/bar.example_v0.0.1/cue.mod/module.cue --
41 module: "bar.example@v0"
42 -- r1/bar.example_v0.0.1/y/y.cue --
43 package y
44
45 -- r2/auth.json --
46 {
47 "username": "bob",
48 "password": "somePassword"
49 }
50 -- r2/bar.example_v0.0.1/cue.mod/module.cue --
51 module: "bar.example@v0"
52 -- r2/bar.example_v0.0.1/x/x.cue --
53 package x
54 `))
55 fsys := txtarfs.FS(modules)
56 r1fs, err := fs.Sub(fsys, "r1")
57 qt.Assert(t, qt.IsNil(err))
58 r1, err := registrytest.New(r1fs, "")
59 qt.Assert(t, qt.IsNil(err))
60 r2fs, err := fs.Sub(fsys, "r2")
61 qt.Assert(t, qt.IsNil(err))
62 r2, err := registrytest.New(r2fs, "")
63 qt.Assert(t, qt.IsNil(err))
64
65 dir := t.TempDir()
66 t.Setenv("DOCKER_CONFIG", dir)
67 dockerCfg, err := json.Marshal(dockerConfig{
68 Auths: map[string]authConfig{
69 r2.Host(): {
70 Username: "bob",
71 Password: "somePassword",
72 },
73 },
74 })
75 qt.Assert(t, qt.IsNil(err))
76 err = os.WriteFile(filepath.Join(dir, "config.json"), dockerCfg, 0o666)
77 qt.Assert(t, qt.IsNil(err))
78
79 t.Setenv("CUE_REGISTRY",
80 fmt.Sprintf("foo.example=%s+insecure,%s+insecure",
81 r1.Host(),
82 r2.Host(),
83 ))
84 cacheDir := filepath.Join(dir, "cache")
85 t.Setenv("CUE_CACHE_DIR", cacheDir)
86 t.Cleanup(func() {
87 modcache.RemoveAll(cacheDir)
88 })
89
90 var transportInvoked atomic.Bool
91 r, err := NewRegistry(&Config{
92 Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) {
93 transportInvoked.Store(true)
94 return http.DefaultTransport.RoundTrip(req)
95 }),
96 })
97 qt.Assert(t, qt.IsNil(err))
98 ctx := context.Background()
99 gotRequirements, err := r.Requirements(ctx, module.MustNewVersion("foo.example@v0", "v0.0.1"))
100 qt.Assert(t, qt.IsNil(err))
101 qt.Assert(t, qt.DeepEquals(gotRequirements, []module.Version{
102 module.MustNewVersion("bar.example@v0", "v0.0.1"),
103 }))
104
105 loc, err := r.Fetch(ctx, module.MustNewVersion("bar.example@v0", "v0.0.1"))
106 qt.Assert(t, qt.IsNil(err))
107 data, err := fs.ReadFile(loc.FS, path.Join(loc.Dir, "x/x.cue"))
108 qt.Assert(t, qt.IsNil(err))
109 qt.Assert(t, qt.Equals(string(data), "package x\n"))
110
111
112 resolver, err := NewResolver(nil)
113 qt.Assert(t, qt.IsNil(err))
114 gotAllHosts := resolver.AllHosts()
115 wantAllHosts := []Host{{Name: r1.Host(), Insecure: true}, {Name: r2.Host(), Insecure: true}}
116
117 byHostname := func(a, b Host) int { return strings.Compare(a.Name, b.Name) }
118 slices.SortFunc(gotAllHosts, byHostname)
119 slices.SortFunc(wantAllHosts, byHostname)
120
121 qt.Assert(t, qt.DeepEquals(gotAllHosts, wantAllHosts))
122
123
124 qt.Assert(t, qt.IsTrue(transportInvoked.Load()))
125 }
126
127 func TestDefaultTransportSetsUserAgent(t *testing.T) {
128
129
130
131 modules := txtar.Parse([]byte(`
132 -- bar.example_v0.0.1/cue.mod/module.cue --
133 module: "bar.example@v0"
134 -- bar.example_v0.0.1/x/x.cue --
135 package x
136 `))
137 rh, err := registrytest.NewHandler(txtarfs.FS(modules), "")
138 qt.Assert(t, qt.IsNil(err))
139 agent := cueversion.UserAgent("cuelang.org/go")
140 checked := false
141 checkUserAgentHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
142 qt.Check(t, qt.Equals(req.UserAgent(), agent))
143 checked = true
144 rh.ServeHTTP(w, req)
145 })
146 srv := httptest.NewServer(checkUserAgentHandler)
147 u, err := url.Parse(srv.URL)
148 qt.Assert(t, qt.IsNil(err))
149
150 dir := t.TempDir()
151 t.Setenv("DOCKER_CONFIG", dir)
152 t.Setenv("CUE_REGISTRY", u.Host+"+insecure")
153 cacheDir := filepath.Join(dir, "cache")
154 t.Setenv("CUE_CACHE_DIR", cacheDir)
155 t.Cleanup(func() {
156 modcache.RemoveAll(cacheDir)
157 })
158
159 r, err := NewRegistry(nil)
160 qt.Assert(t, qt.IsNil(err))
161 ctx := context.Background()
162 gotRequirements, err := r.Requirements(ctx, module.MustNewVersion("bar.example@v0", "v0.0.1"))
163 qt.Assert(t, qt.IsNil(err))
164 qt.Assert(t, qt.HasLen(gotRequirements, 0))
165
166 qt.Assert(t, qt.IsTrue(checked))
167 }
168
169
170
171
172 type dockerConfig struct {
173 Auths map[string]authConfig `json:"auths"`
174 }
175
176
177 type authConfig struct {
178 Username string `json:"username,omitempty"`
179 Password string `json:"password,omitempty"`
180 }
181
182 type roundTripperFunc func(*http.Request) (*http.Response, error)
183
184 func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
185 return f(req)
186 }
187
View as plain text