oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/registry_test.go (about) 1 /* 2 Copyright The ORAS Authors. 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 16 package remote 17 18 import ( 19 "bytes" 20 "context" 21 "encoding/json" 22 "fmt" 23 "io" 24 "net/http" 25 "net/http/httptest" 26 "net/url" 27 "reflect" 28 "strconv" 29 "strings" 30 "testing" 31 32 "oras.land/oras-go/v2/errdef" 33 "oras.land/oras-go/v2/registry" 34 ) 35 36 func TestRegistryInterface(t *testing.T) { 37 var reg interface{} = &Registry{} 38 if _, ok := reg.(registry.Registry); !ok { 39 t.Error("&Registry{} does not conform registry.Registry") 40 } 41 } 42 43 func TestRegistry_TLS(t *testing.T) { 44 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 if r.Method != http.MethodGet || r.URL.Path != "/v2/" { 46 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 47 w.WriteHeader(http.StatusNotFound) 48 return 49 } 50 })) 51 defer ts.Close() 52 uri, err := url.Parse(ts.URL) 53 if err != nil { 54 t.Fatalf("invalid test http server: %v", err) 55 } 56 57 reg, err := NewRegistry(uri.Host) 58 if err != nil { 59 t.Fatalf("NewRegistry() error = %v", err) 60 } 61 reg.Client = ts.Client() 62 63 ctx := context.Background() 64 if err := reg.Ping(ctx); err != nil { 65 t.Errorf("Registry.Ping() error = %v", err) 66 } 67 } 68 69 func TestRegistry_Ping(t *testing.T) { 70 v2Implemented := true 71 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 72 if r.Method != http.MethodGet || r.URL.Path != "/v2/" { 73 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 74 w.WriteHeader(http.StatusNotFound) 75 return 76 } 77 78 if v2Implemented { 79 w.WriteHeader(http.StatusOK) 80 } else { 81 w.WriteHeader(http.StatusNotFound) 82 } 83 })) 84 defer ts.Close() 85 uri, err := url.Parse(ts.URL) 86 if err != nil { 87 t.Fatalf("invalid test http server: %v", err) 88 } 89 90 reg, err := NewRegistry(uri.Host) 91 if err != nil { 92 t.Fatalf("NewRegistry() error = %v", err) 93 } 94 reg.PlainHTTP = true 95 96 ctx := context.Background() 97 if err := reg.Ping(ctx); err != nil { 98 t.Errorf("Registry.Ping() error = %v", err) 99 } 100 101 v2Implemented = false 102 if err := reg.Ping(ctx); err == nil { 103 t.Errorf("Registry.Ping() error = %v, wantErr %v", err, errdef.ErrNotFound) 104 } 105 } 106 107 func TestRegistry_Repositories(t *testing.T) { 108 repoSet := [][]string{ 109 {"the", "quick", "brown", "fox"}, 110 {"jumps", "over", "the", "lazy"}, 111 {"dog"}, 112 } 113 var ts *httptest.Server 114 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 115 if r.Method != http.MethodGet || r.URL.Path != "/v2/_catalog" { 116 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 117 w.WriteHeader(http.StatusNotFound) 118 return 119 } 120 q := r.URL.Query() 121 n, err := strconv.Atoi(q.Get("n")) 122 if err != nil || n != 4 { 123 t.Errorf("bad page size: %s", q.Get("n")) 124 w.WriteHeader(http.StatusBadRequest) 125 return 126 } 127 var repos []string 128 switch q.Get("test") { 129 case "foo": 130 repos = repoSet[1] 131 w.Header().Set("Link", fmt.Sprintf(`<%s/v2/_catalog?n=4&test=bar>; rel="next"`, ts.URL)) 132 case "bar": 133 repos = repoSet[2] 134 default: 135 repos = repoSet[0] 136 w.Header().Set("Link", `</v2/_catalog?n=4&test=foo>; rel="next"`) 137 } 138 result := struct { 139 Repositories []string `json:"repositories"` 140 }{ 141 Repositories: repos, 142 } 143 if err := json.NewEncoder(w).Encode(result); err != nil { 144 t.Errorf("failed to write response: %v", err) 145 } 146 })) 147 defer ts.Close() 148 uri, err := url.Parse(ts.URL) 149 if err != nil { 150 t.Fatalf("invalid test http server: %v", err) 151 } 152 153 reg, err := NewRegistry(uri.Host) 154 if err != nil { 155 t.Fatalf("NewRegistry() error = %v", err) 156 } 157 reg.PlainHTTP = true 158 reg.RepositoryListPageSize = 4 159 160 ctx := context.Background() 161 index := 0 162 if err := reg.Repositories(ctx, "", func(got []string) error { 163 if index > 2 { 164 t.Fatalf("out of index bound: %d", index) 165 } 166 repos := repoSet[index] 167 index++ 168 if !reflect.DeepEqual(got, repos) { 169 t.Errorf("Registry.Repositories() = %v, want %v", got, repos) 170 } 171 return nil 172 }); err != nil { 173 t.Fatalf("Registry.Repositories() error = %v", err) 174 } 175 } 176 177 func TestRegistry_Repository(t *testing.T) { 178 reg, err := NewRegistry("localhost:5000") 179 if err != nil { 180 t.Fatalf("NewRegistry() error = %v", err) 181 } 182 reg.PlainHTTP = true 183 reg.SkipReferrersGC = true 184 reg.RepositoryListPageSize = 50 185 reg.TagListPageSize = 100 186 reg.ReferrerListPageSize = 10 187 reg.MaxMetadataBytes = 8 * 1024 * 1024 188 189 ctx := context.Background() 190 got, err := reg.Repository(ctx, "hello-world") 191 if err != nil { 192 t.Fatalf("Registry.Repository() error = %v", err) 193 } 194 reg.Reference.Repository = "hello-world" 195 want := (*Repository)(®.RepositoryOptions) 196 if !reflect.DeepEqual(got, want) { 197 t.Errorf("Registry.Repository() = %v, want %v", got, want) 198 } 199 } 200 201 // Testing `last` parameter for Repositories list 202 func TestRegistry_Repositories_WithLastParam(t *testing.T) { 203 repoSet := strings.Split("abcdefghijklmnopqrstuvwxyz", "") 204 var offset int 205 var ts *httptest.Server 206 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 207 if r.Method != http.MethodGet || r.URL.Path != "/v2/_catalog" { 208 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 209 w.WriteHeader(http.StatusNotFound) 210 return 211 } 212 q := r.URL.Query() 213 n, err := strconv.Atoi(q.Get("n")) 214 if err != nil || n != 4 { 215 t.Errorf("bad page size: %s", q.Get("n")) 216 w.WriteHeader(http.StatusBadRequest) 217 return 218 } 219 last := q.Get("last") 220 if last != "" { 221 offset = indexOf(last, repoSet) + 1 222 } 223 var repos []string 224 switch q.Get("test") { 225 case "foo": 226 repos = repoSet[offset : offset+n] 227 w.Header().Set("Link", fmt.Sprintf(`<%s/v2/_catalog?n=4&last=v&test=bar>; rel="next"`, ts.URL)) 228 case "bar": 229 repos = repoSet[offset : offset+n] 230 default: 231 repos = repoSet[offset : offset+n] 232 w.Header().Set("Link", fmt.Sprintf(`<%s/v2/_catalog?n=4&last=r&test=foo>; rel="next"`, ts.URL)) 233 } 234 result := struct { 235 Repositories []string `json:"repositories"` 236 }{ 237 Repositories: repos, 238 } 239 if err := json.NewEncoder(w).Encode(result); err != nil { 240 t.Errorf("failed to write response: %v", err) 241 } 242 })) 243 defer ts.Close() 244 uri, err := url.Parse(ts.URL) 245 if err != nil { 246 t.Fatalf("invalid test http server: %v", err) 247 } 248 249 reg, err := NewRegistry(uri.Host) 250 if err != nil { 251 t.Fatalf("NewRegistry() error = %v", err) 252 } 253 reg.PlainHTTP = true 254 reg.RepositoryListPageSize = 4 255 last := "n" 256 startInd := indexOf(last, repoSet) + 1 257 258 ctx := context.Background() 259 if err := reg.Repositories(ctx, last, func(got []string) error { 260 want := repoSet[startInd : startInd+reg.RepositoryListPageSize] 261 startInd += reg.RepositoryListPageSize 262 if !reflect.DeepEqual(got, want) { 263 t.Errorf("Registry.Repositories() = %v, want %v", got, want) 264 } 265 return nil 266 }); err != nil { 267 t.Fatalf("Registry.Repositories() error = %v", err) 268 } 269 } 270 271 func TestRegistry_do(t *testing.T) { 272 data := []byte(`hello world!`) 273 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 274 if r.Method != http.MethodGet || r.URL.Path != "/test" { 275 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 276 w.WriteHeader(http.StatusNotFound) 277 return 278 } 279 w.Header().Add("Warning", `299 - "Test 1: Good warning."`) 280 w.Header().Add("Warning", `199 - "Test 2: Warning with a non-299 code."`) 281 w.Header().Add("Warning", `299 - "Test 3: Good warning."`) 282 w.Header().Add("Warning", `299 myregistry.example.com "Test 4: Warning with a non-unknown agent"`) 283 w.Header().Add("Warning", `299 - "Test 5: Warning with a date." "Sat, 25 Aug 2012 23:34:45 GMT"`) 284 w.Header().Add("wArnIng", `299 - "Test 6: Good warning."`) 285 w.Write(data) 286 })) 287 defer ts.Close() 288 uri, err := url.Parse(ts.URL) 289 if err != nil { 290 t.Fatalf("invalid test http server: %v", err) 291 } 292 testURL := ts.URL + "/test" 293 294 // test do() without HandleWarning 295 reg, err := NewRegistry(uri.Host) 296 if err != nil { 297 t.Fatal("NewRegistry() error =", err) 298 } 299 req, err := http.NewRequest(http.MethodGet, testURL, nil) 300 if err != nil { 301 t.Fatal("failed to create test request:", err) 302 } 303 resp, err := reg.do(req) 304 if err != nil { 305 t.Fatal("Registry.do() error =", err) 306 } 307 if resp.StatusCode != http.StatusOK { 308 t.Errorf("Registry.do() status code = %v, want %v", resp.StatusCode, http.StatusOK) 309 } 310 if got := len(resp.Header["Warning"]); got != 6 { 311 t.Errorf("Registry.do() warning header len = %v, want %v", got, 6) 312 } 313 got, err := io.ReadAll(resp.Body) 314 if err != nil { 315 t.Fatal("io.ReadAll() error =", err) 316 } 317 resp.Body.Close() 318 if !bytes.Equal(got, data) { 319 t.Errorf("Registry.do() = %v, want %v", got, data) 320 } 321 322 // test do() with HandleWarning 323 reg, err = NewRegistry(uri.Host) 324 if err != nil { 325 t.Fatal("NewRegistry() error =", err) 326 } 327 var gotWarnings []Warning 328 reg.HandleWarning = func(warning Warning) { 329 gotWarnings = append(gotWarnings, warning) 330 } 331 332 req, err = http.NewRequest(http.MethodGet, testURL, nil) 333 if err != nil { 334 t.Fatal("failed to create test request:", err) 335 } 336 resp, err = reg.do(req) 337 if err != nil { 338 t.Fatal("Registry.do() error =", err) 339 } 340 if resp.StatusCode != http.StatusOK { 341 t.Errorf("Registry.do() status code = %v, want %v", resp.StatusCode, http.StatusOK) 342 } 343 if got := len(resp.Header["Warning"]); got != 6 { 344 t.Errorf("Registry.do() warning header len = %v, want %v", got, 6) 345 } 346 got, err = io.ReadAll(resp.Body) 347 if err != nil { 348 t.Errorf("Registry.do() = %v, want %v", got, data) 349 } 350 resp.Body.Close() 351 if !bytes.Equal(got, data) { 352 t.Errorf("Registry.do() = %v, want %v", got, data) 353 } 354 355 wantWarnings := []Warning{ 356 { 357 WarningValue: WarningValue{ 358 Code: 299, 359 Agent: "-", 360 Text: "Test 1: Good warning.", 361 }, 362 }, 363 { 364 WarningValue: WarningValue{ 365 Code: 299, 366 Agent: "-", 367 Text: "Test 3: Good warning.", 368 }, 369 }, 370 { 371 WarningValue: WarningValue{ 372 Code: 299, 373 Agent: "-", 374 Text: "Test 6: Good warning.", 375 }, 376 }, 377 } 378 if !reflect.DeepEqual(gotWarnings, wantWarnings) { 379 t.Errorf("Registry.do() = %v, want %v", gotWarnings, wantWarnings) 380 } 381 } 382 383 // indexOf returns the index of an element within a slice 384 func indexOf(element string, data []string) int { 385 for ind, val := range data { 386 if element == val { 387 return ind 388 } 389 } 390 return -1 //not found. 391 }