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)(&reg.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  }