|
1 | 1 | package cli |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
| 5 | + "net/http" |
| 6 | + "net/http/httptest" |
4 | 7 | "path/filepath" |
5 | 8 | "strings" |
| 9 | + "sync/atomic" |
6 | 10 | "testing" |
| 11 | + |
| 12 | + "github.com/devcontainers/cli/internal/features" |
| 13 | + "github.com/devcontainers/cli/internal/log" |
7 | 14 | ) |
8 | 15 |
|
9 | 16 | func TestReadConfigurationReportsTheFirstFeatureInDeclarationOrder(t *testing.T) { |
@@ -73,3 +80,63 @@ func TestFeaturesResolveDependenciesListsFeaturesInDeclarationOrder(t *testing.T |
73 | 80 | prev = i |
74 | 81 | } |
75 | 82 | } |
| 83 | + |
| 84 | +func TestPlainHTTPFeatureIDIsNotATarball(t *testing.T) { |
| 85 | + var requests atomic.Int32 |
| 86 | + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 87 | + requests.Add(1) |
| 88 | + w.Write(featureTgzBytes(t, map[string]string{"devcontainer-feature.json": `{"id":"x"}`})) |
| 89 | + })) |
| 90 | + t.Cleanup(srv.Close) |
| 91 | + id := srv.URL + "/devcontainer-feature-x.tgz" |
| 92 | + wantLogs := []string{ |
| 93 | + "Could not resolve Feature manifest for '" + id + "'. If necessary, provide registry credentials with 'docker login <registry>'.\n", |
| 94 | + "Could not resolve Feature '" + id + "'. Ensure the Feature is published and accessible from your current environment.\n", |
| 95 | + } |
| 96 | + |
| 97 | + for name, process := range map[string]func(log.Logger) (*features.Set, error){ |
| 98 | + "install": func(l log.Logger) (*features.Set, error) { |
| 99 | + return processInstallFeature(l, nil, &features.FNode{UserFeatureID: id}, localFeatureRoots{}, nil, stageDirs(t)) |
| 100 | + }, |
| 101 | + "metadata": func(l log.Logger) (*features.Set, error) { |
| 102 | + return newMetadataProcessFeature(nil, l, localFeatureRoots{}, nil)(&features.FNode{UserFeatureID: id}) |
| 103 | + }, |
| 104 | + } { |
| 105 | + t.Run(name, func(t *testing.T) { |
| 106 | + var logs bytes.Buffer |
| 107 | + set, err := process(log.New(log.Options{Level: log.LevelInfo, Format: "text", Writer: &logs})) |
| 108 | + if set != nil || err != nil { |
| 109 | + t.Fatalf("set, err = %+v, %v; want neither", set, err) |
| 110 | + } |
| 111 | + for _, line := range wantLogs { |
| 112 | + if !strings.Contains(logs.String(), line) { |
| 113 | + t.Errorf("logs = %q, want %q", logs.String(), line) |
| 114 | + } |
| 115 | + } |
| 116 | + }) |
| 117 | + } |
| 118 | + if n := requests.Load(); n != 0 { |
| 119 | + t.Errorf("%d requests to the http:// URL, want none", n) |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +func TestUnparseableFeatureReferences(t *testing.T) { |
| 124 | + for id, want := range map[string]string{ |
| 125 | + "owner/repo/feat@1@2": "Parse error. Use the '@' symbol only to designate a version tag.", |
| 126 | + "owner/repo/fe.at@bad": "Could not resolve Feature 'owner/repo/fe.at@bad'. Ensure the Feature is published and accessible from your current environment.", |
| 127 | + "owner/repo/sub/feat@v1": "Could not resolve Feature 'owner/repo/sub/feat@v1'. Ensure the Feature is published and accessible from your current environment.", |
| 128 | + "owner//feat@v1": "Could not resolve Feature 'owner//feat@v1'. Ensure the Feature is published and accessible from your current environment.", |
| 129 | + } { |
| 130 | + var logs bytes.Buffer |
| 131 | + set, err := newMetadataProcessFeature(nil, log.New(log.Options{Level: log.LevelInfo, Format: "text", Writer: &logs}), localFeatureRoots{}, nil)(&features.FNode{UserFeatureID: id}) |
| 132 | + if set != nil || err != nil { |
| 133 | + t.Errorf("%s: set, err = %+v, %v; want neither", id, set, err) |
| 134 | + } |
| 135 | + if !strings.HasSuffix(logs.String(), want+"\n") { |
| 136 | + t.Errorf("%s: logs = %q, want them to end with %q", id, logs.String(), want) |
| 137 | + } |
| 138 | + } |
| 139 | + if _, err := newMetadataProcessFeature(nil, log.Null, localFeatureRoots{}, nil)(&features.FNode{UserFeatureID: "owner/repo/feat@v1"}); err == nil || !strings.HasPrefix(err.Error(), "ERR: Feature 'owner/repo/feat@v1' could not be processed. ") { |
| 140 | + t.Errorf("err = %v; want the reference's parse failure", err) |
| 141 | + } |
| 142 | +} |
0 commit comments