github.com/dtroyer-salad/og2/v2@v2.0.0-20240412154159-c47231610877/registry/remote/repository_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  	"crypto/tls"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"net"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"net/url"
    30  	"reflect"
    31  	"strconv"
    32  	"strings"
    33  	"sync/atomic"
    34  	"testing"
    35  
    36  	"github.com/opencontainers/go-digest"
    37  	specs "github.com/opencontainers/image-spec/specs-go"
    38  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    39  	"golang.org/x/sync/errgroup"
    40  	"oras.land/oras-go/v2/content"
    41  	"oras.land/oras-go/v2/errdef"
    42  	"oras.land/oras-go/v2/internal/interfaces"
    43  	"oras.land/oras-go/v2/internal/spec"
    44  	"oras.land/oras-go/v2/registry"
    45  	"oras.land/oras-go/v2/registry/remote/auth"
    46  	"oras.land/oras-go/v2/registry/remote/errcode"
    47  )
    48  
    49  type testIOStruct struct {
    50  	isTag                   bool
    51  	clientSuppliedReference string
    52  	serverCalculatedDigest  digest.Digest // for non-HEAD (body-containing) requests only
    53  	errExpectedOnHEAD       bool
    54  	errExpectedOnGET        bool
    55  }
    56  
    57  const theAmazingBanClan = "Ban Gu, Ban Chao, Ban Zhao"
    58  const theAmazingBanDigest = "b526a4f2be963a2f9b0990c001255669eab8a254ab1a6e3f84f1820212ac7078"
    59  
    60  // The following truth table aims to cover the expected GET/HEAD request outcome
    61  // for all possible permutations of the client/server "containing a digest", for
    62  // both Manifests and Blobs.  Where the results between the two differ, the index
    63  // of the first column has an exclamation mark.
    64  //
    65  // The client is said to "contain a digest" if the user-supplied reference string
    66  // is of the form that contains a digest rather than a tag.  The server, on the
    67  // other hand, is said to "contain a digest" if the server responded with the
    68  // special header `Docker-Content-Digest`.
    69  //
    70  // In this table, anything denoted with an asterisk indicates that the true
    71  // response should actually be the opposite of what's expected; for example,
    72  // `*PASS` means we will get a `PASS`, even though the true answer would be its
    73  // diametric opposite--a `FAIL`. This may seem odd, and deserves an explanation.
    74  // This function has blind-spots, and while it can expend power to gain sight,
    75  // i.e., perform the expensive validation, we chose not to.  The reason is two-
    76  // fold: a) we "know" that even if we say "!PASS", it will eventually fail later
    77  // when checks are performed, and with that assumption, we have the luxury for
    78  // the second point, which is b) performance.
    79  //
    80  //	 _______________________________________________________________________________________________________________
    81  //	| ID | CLIENT          | SERVER           | Manifest.GET          | Blob.GET  | Manifest.HEAD       | Blob.HEAD |
    82  //	|----+-----------------+------------------+-----------------------+-----------+---------------------+-----------+
    83  //	| 1  | tag             | missing          | CALCULATE,PASS        | n/a       | FAIL                | n/a       |
    84  //	| 2  | tag             | presentCorrect   | TRUST,PASS            | n/a       | TRUST,PASS          | n/a       |
    85  //	| 3  | tag             | presentIncorrect | TRUST,*PASS           | n/a       | TRUST,*PASS         | n/a       |
    86  //	| 4  | correctDigest   | missing          | TRUST,PASS            | PASS      | TRUST,PASS          | PASS      |
    87  //	| 5  | correctDigest   | presentCorrect   | TRUST,COMPARE,PASS    | PASS      | TRUST,COMPARE,PASS  | PASS      |
    88  //	| 6  | correctDigest   | presentIncorrect | TRUST,COMPARE,FAIL    | FAIL      | TRUST,COMPARE,FAIL  | FAIL      |
    89  //	 ---------------------------------------------------------------------------------------------------------------
    90  func getTestIOStructMapForGetDescriptorClass() map[string]testIOStruct {
    91  	correctDigest := fmt.Sprintf("sha256:%v", theAmazingBanDigest)
    92  	incorrectDigest := fmt.Sprintf("sha256:%v", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    93  
    94  	return map[string]testIOStruct{
    95  		"1. Client:Tag & Server:DigestMissing": {
    96  			isTag:             true,
    97  			errExpectedOnHEAD: true,
    98  		},
    99  		"2. Client:Tag & Server:DigestValid": {
   100  			isTag:                  true,
   101  			serverCalculatedDigest: digest.Digest(correctDigest),
   102  		},
   103  		"3. Client:Tag & Server:DigestWrongButSyntacticallyValid": {
   104  			isTag:                  true,
   105  			serverCalculatedDigest: digest.Digest(incorrectDigest),
   106  		},
   107  		"4. Client:DigestValid & Server:DigestMissing": {
   108  			clientSuppliedReference: correctDigest,
   109  		},
   110  		"5. Client:DigestValid & Server:DigestValid": {
   111  			clientSuppliedReference: correctDigest,
   112  			serverCalculatedDigest:  digest.Digest(correctDigest),
   113  		},
   114  		"6. Client:DigestValid & Server:DigestWrongButSyntacticallyValid": {
   115  			clientSuppliedReference: correctDigest,
   116  			serverCalculatedDigest:  digest.Digest(incorrectDigest),
   117  			errExpectedOnHEAD:       true,
   118  			errExpectedOnGET:        true,
   119  		},
   120  	}
   121  }
   122  
   123  func TestRepository_Fetch(t *testing.T) {
   124  	blob := []byte("hello world")
   125  	blobDesc := ocispec.Descriptor{
   126  		MediaType: "test",
   127  		Digest:    digest.FromBytes(blob),
   128  		Size:      int64(len(blob)),
   129  	}
   130  	index := []byte(`{"manifests":[]}`)
   131  	indexDesc := ocispec.Descriptor{
   132  		MediaType: ocispec.MediaTypeImageIndex,
   133  		Digest:    digest.FromBytes(index),
   134  		Size:      int64(len(index)),
   135  	}
   136  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   137  		if r.Method != http.MethodGet {
   138  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   139  			w.WriteHeader(http.StatusMethodNotAllowed)
   140  			return
   141  		}
   142  		switch r.URL.Path {
   143  		case "/v2/test/blobs/" + blobDesc.Digest.String():
   144  			w.Header().Set("Content-Type", "application/octet-stream")
   145  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   146  			if _, err := w.Write(blob); err != nil {
   147  				t.Errorf("failed to write %q: %v", r.URL, err)
   148  			}
   149  		case "/v2/test/manifests/" + indexDesc.Digest.String():
   150  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   151  				t.Errorf("manifest not convertable: %s", accept)
   152  				w.WriteHeader(http.StatusBadRequest)
   153  				return
   154  			}
   155  			w.Header().Set("Content-Type", indexDesc.MediaType)
   156  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   157  			if _, err := w.Write(index); err != nil {
   158  				t.Errorf("failed to write %q: %v", r.URL, err)
   159  			}
   160  		default:
   161  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   162  			w.WriteHeader(http.StatusNotFound)
   163  		}
   164  	}))
   165  	defer ts.Close()
   166  	uri, err := url.Parse(ts.URL)
   167  	if err != nil {
   168  		t.Fatalf("invalid test http server: %v", err)
   169  	}
   170  
   171  	repo, err := NewRepository(uri.Host + "/test")
   172  	if err != nil {
   173  		t.Fatalf("NewRepository() error = %v", err)
   174  	}
   175  	repo.PlainHTTP = true
   176  	ctx := context.Background()
   177  
   178  	rc, err := repo.Fetch(ctx, blobDesc)
   179  	if err != nil {
   180  		t.Fatalf("Repository.Fetch() error = %v", err)
   181  	}
   182  	buf := bytes.NewBuffer(nil)
   183  	if _, err := buf.ReadFrom(rc); err != nil {
   184  		t.Errorf("fail to read: %v", err)
   185  	}
   186  	if err := rc.Close(); err != nil {
   187  		t.Errorf("fail to close: %v", err)
   188  	}
   189  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
   190  		t.Errorf("Repository.Fetch() = %v, want %v", got, blob)
   191  	}
   192  
   193  	rc, err = repo.Fetch(ctx, indexDesc)
   194  	if err != nil {
   195  		t.Fatalf("Repository.Fetch() error = %v", err)
   196  	}
   197  	buf.Reset()
   198  	if _, err := buf.ReadFrom(rc); err != nil {
   199  		t.Errorf("fail to read: %v", err)
   200  	}
   201  	if err := rc.Close(); err != nil {
   202  		t.Errorf("fail to close: %v", err)
   203  	}
   204  	if got := buf.Bytes(); !bytes.Equal(got, index) {
   205  		t.Errorf("Repository.Fetch() = %v, want %v", got, index)
   206  	}
   207  }
   208  
   209  func TestRepository_Push(t *testing.T) {
   210  	blob := []byte("hello world")
   211  	blobDesc := ocispec.Descriptor{
   212  		MediaType: "test",
   213  		Digest:    digest.FromBytes(blob),
   214  		Size:      int64(len(blob)),
   215  	}
   216  	var gotBlob []byte
   217  	index := []byte(`{"manifests":[]}`)
   218  	indexDesc := ocispec.Descriptor{
   219  		MediaType: ocispec.MediaTypeImageIndex,
   220  		Digest:    digest.FromBytes(index),
   221  		Size:      int64(len(index)),
   222  	}
   223  	var gotIndex []byte
   224  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
   225  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   226  		switch {
   227  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
   228  			w.Header().Set("Location", "/v2/test/blobs/uploads/"+uuid)
   229  			w.WriteHeader(http.StatusAccepted)
   230  			return
   231  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
   232  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
   233  				w.WriteHeader(http.StatusBadRequest)
   234  				break
   235  			}
   236  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
   237  				w.WriteHeader(http.StatusBadRequest)
   238  				break
   239  			}
   240  			buf := bytes.NewBuffer(nil)
   241  			if _, err := buf.ReadFrom(r.Body); err != nil {
   242  				t.Errorf("fail to read: %v", err)
   243  			}
   244  			gotBlob = buf.Bytes()
   245  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   246  			w.WriteHeader(http.StatusCreated)
   247  			return
   248  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
   249  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
   250  				w.WriteHeader(http.StatusBadRequest)
   251  				break
   252  			}
   253  			buf := bytes.NewBuffer(nil)
   254  			if _, err := buf.ReadFrom(r.Body); err != nil {
   255  				t.Errorf("fail to read: %v", err)
   256  			}
   257  			gotIndex = buf.Bytes()
   258  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   259  			w.WriteHeader(http.StatusCreated)
   260  			return
   261  		default:
   262  			w.WriteHeader(http.StatusForbidden)
   263  		}
   264  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   265  	}))
   266  	defer ts.Close()
   267  	uri, err := url.Parse(ts.URL)
   268  	if err != nil {
   269  		t.Fatalf("invalid test http server: %v", err)
   270  	}
   271  
   272  	repo, err := NewRepository(uri.Host + "/test")
   273  	if err != nil {
   274  		t.Fatalf("NewRepository() error = %v", err)
   275  	}
   276  	repo.PlainHTTP = true
   277  	ctx := context.Background()
   278  
   279  	err = repo.Push(ctx, blobDesc, bytes.NewReader(blob))
   280  	if err != nil {
   281  		t.Fatalf("Repository.Push() error = %v", err)
   282  	}
   283  	if !bytes.Equal(gotBlob, blob) {
   284  		t.Errorf("Repository.Push() = %v, want %v", gotBlob, blob)
   285  	}
   286  
   287  	err = repo.Push(ctx, indexDesc, bytes.NewReader(index))
   288  	if err != nil {
   289  		t.Fatalf("Repository.Push() error = %v", err)
   290  	}
   291  	if !bytes.Equal(gotIndex, index) {
   292  		t.Errorf("Repository.Push() = %v, want %v", gotIndex, index)
   293  	}
   294  }
   295  
   296  func TestRepository_Mount(t *testing.T) {
   297  	blob := []byte("hello world")
   298  	blobDesc := ocispec.Descriptor{
   299  		MediaType: "test",
   300  		Digest:    digest.FromBytes(blob),
   301  		Size:      int64(len(blob)),
   302  	}
   303  	gotMount := 0
   304  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   305  		if got, want := r.Method, "POST"; got != want {
   306  			t.Errorf("unexpected HTTP method; got %q want %q", got, want)
   307  			w.WriteHeader(http.StatusInternalServerError)
   308  			return
   309  		}
   310  		if err := r.ParseForm(); err != nil {
   311  			t.Errorf("invalid form in HTTP request: %v", err)
   312  			w.WriteHeader(http.StatusInternalServerError)
   313  			return
   314  		}
   315  		switch r.URL.Path {
   316  		case "/v2/test2/blobs/uploads/":
   317  			if got, want := r.Form.Get("mount"), blobDesc.Digest; digest.Digest(got) != want {
   318  				t.Errorf("unexpected value for 'mount' parameter; got %q want %q", got, want)
   319  			}
   320  			if got, want := r.Form.Get("from"), "test"; got != want {
   321  				t.Errorf("unexpected value for 'from' parameter; got %q want %q", got, want)
   322  			}
   323  			gotMount++
   324  			w.Header().Set(headerDockerContentDigest, blobDesc.Digest.String())
   325  			w.WriteHeader(201)
   326  			return
   327  		default:
   328  			t.Errorf("unexpected URL for mount request %q", r.URL)
   329  			w.WriteHeader(http.StatusInternalServerError)
   330  		}
   331  	}))
   332  	defer ts.Close()
   333  	uri, err := url.Parse(ts.URL)
   334  	if err != nil {
   335  		t.Fatalf("invalid test http server: %v", err)
   336  	}
   337  	repo, err := NewRepository(uri.Host + "/test2")
   338  	if err != nil {
   339  		t.Fatalf("NewRepository() error = %v", err)
   340  	}
   341  	repo.PlainHTTP = true
   342  	ctx := context.Background()
   343  
   344  	err = repo.Mount(ctx, blobDesc, "test", nil)
   345  	if err != nil {
   346  		t.Fatalf("Repository.Push() error = %v", err)
   347  	}
   348  	if gotMount != 1 {
   349  		t.Errorf("did not get expected mount request")
   350  	}
   351  }
   352  
   353  func TestRepository_Mount_Fallback(t *testing.T) {
   354  	// This test checks the case where the server does not know
   355  	// about the mount query parameters, so the call falls back to
   356  	// the regular push flow. This test is thus very similar to TestPush,
   357  	// except that it doesn't push a manifest because mounts aren't
   358  	// documented to be supported for manifests.
   359  
   360  	blob := []byte("hello world")
   361  	blobDesc := ocispec.Descriptor{
   362  		MediaType: "test",
   363  		Digest:    digest.FromBytes(blob),
   364  		Size:      int64(len(blob)),
   365  	}
   366  	var sequence string
   367  	var gotBlob []byte
   368  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
   369  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   370  		switch {
   371  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test2/blobs/uploads/":
   372  			w.Header().Set("Location", "/v2/test2/blobs/uploads/"+uuid)
   373  			w.WriteHeader(http.StatusAccepted)
   374  			sequence += "post "
   375  			return
   376  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/blobs/"+blobDesc.Digest.String():
   377  			w.Header().Set("Content-Type", "application/octet-stream")
   378  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   379  			if _, err := w.Write(blob); err != nil {
   380  				t.Errorf("failed to write %q: %v", r.URL, err)
   381  			}
   382  			sequence += "get "
   383  			return
   384  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test2/blobs/uploads/"+uuid:
   385  			if got, want := r.Header.Get("Content-Type"), "application/octet-stream"; got != want {
   386  				t.Errorf("unexpected content type; got %q want %q", got, want)
   387  				w.WriteHeader(http.StatusBadRequest)
   388  				return
   389  			}
   390  			if got, want := r.URL.Query().Get("digest"), blobDesc.Digest.String(); got != want {
   391  				t.Errorf("unexpected content digest; got %q want %q", got, want)
   392  				w.WriteHeader(http.StatusBadRequest)
   393  				return
   394  			}
   395  			data, err := io.ReadAll(r.Body)
   396  			if err != nil {
   397  				t.Errorf("error reading body: %v", err)
   398  				w.WriteHeader(http.StatusInternalServerError)
   399  				return
   400  			}
   401  			gotBlob = data
   402  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   403  			w.WriteHeader(http.StatusCreated)
   404  			sequence += "put "
   405  			return
   406  		default:
   407  			w.WriteHeader(http.StatusForbidden)
   408  		}
   409  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   410  	}))
   411  	defer ts.Close()
   412  	uri, err := url.Parse(ts.URL)
   413  	if err != nil {
   414  		t.Fatalf("invalid test http server: %v", err)
   415  	}
   416  
   417  	repo, err := NewRepository(uri.Host + "/test2")
   418  	if err != nil {
   419  		t.Fatalf("NewRepository() error = %v", err)
   420  	}
   421  	repo.PlainHTTP = true
   422  	ctx := context.Background()
   423  
   424  	t.Run("getContent is nil", func(t *testing.T) {
   425  		sequence = ""
   426  
   427  		err = repo.Mount(ctx, blobDesc, "test", nil)
   428  		if err != nil {
   429  			t.Fatalf("Repository.Push() error = %v", err)
   430  		}
   431  		if !bytes.Equal(gotBlob, blob) {
   432  			t.Errorf("Repository.Mount() = %v, want %v", gotBlob, blob)
   433  		}
   434  		if got, want := sequence, "post get put "; got != want {
   435  			t.Errorf("unexpected request sequence; got %q want %q", got, want)
   436  		}
   437  	})
   438  
   439  	t.Run("getContent is non nil", func(t *testing.T) {
   440  		sequence = ""
   441  
   442  		err = repo.Mount(ctx, blobDesc, "test", func() (io.ReadCloser, error) {
   443  			return io.NopCloser(bytes.NewReader(blob)), nil
   444  		})
   445  		if err != nil {
   446  			t.Fatalf("Repository.Push() error = %v", err)
   447  		}
   448  		if !bytes.Equal(gotBlob, blob) {
   449  			t.Errorf("Repository.Mount() = %v, want %v", gotBlob, blob)
   450  		}
   451  		if got, want := sequence, "post put "; got != want {
   452  			t.Errorf("unexpected request sequence; got %q want %q", got, want)
   453  		}
   454  	})
   455  }
   456  
   457  func TestRepository_Mount_Error(t *testing.T) {
   458  	blob := []byte("hello world")
   459  	blobDesc := ocispec.Descriptor{
   460  		MediaType: "test",
   461  		Digest:    digest.FromBytes(blob),
   462  		Size:      int64(len(blob)),
   463  	}
   464  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   465  		if got, want := r.Method, "POST"; got != want {
   466  			t.Errorf("unexpected HTTP method; got %q want %q", got, want)
   467  			w.WriteHeader(http.StatusInternalServerError)
   468  			return
   469  		}
   470  		if err := r.ParseForm(); err != nil {
   471  			t.Errorf("invalid form in HTTP request: %v", err)
   472  			w.WriteHeader(http.StatusInternalServerError)
   473  			return
   474  		}
   475  		switch r.URL.Path {
   476  		case "/v2/test/blobs/uploads/":
   477  			w.WriteHeader(400)
   478  			w.Write([]byte(`{ "errors": [ { "code": "NAME_UNKNOWN", "message": "some error" } ] }`))
   479  		default:
   480  			t.Errorf("unexpected URL for mount request %q", r.URL)
   481  			w.WriteHeader(http.StatusInternalServerError)
   482  		}
   483  	}))
   484  	defer ts.Close()
   485  	uri, err := url.Parse(ts.URL)
   486  	if err != nil {
   487  		t.Fatalf("invalid test http server: %v", err)
   488  	}
   489  	repo, err := NewRepository(uri.Host + "/test")
   490  	if err != nil {
   491  		t.Fatalf("NewRepository() error = %v", err)
   492  	}
   493  	repo.PlainHTTP = true
   494  
   495  	err = repo.Mount(context.Background(), blobDesc, "foo", nil)
   496  	if err == nil {
   497  		t.Fatalf("expected error but got success instead")
   498  	}
   499  	var errResp *errcode.ErrorResponse
   500  	if !errors.As(err, &errResp) {
   501  		t.Fatalf("unexpected error type %#v", err)
   502  	}
   503  	if !reflect.DeepEqual(errResp.Errors, errcode.Errors{{
   504  		Code:    "NAME_UNKNOWN",
   505  		Message: "some error",
   506  	}}) {
   507  		t.Errorf("unexpected errors %#v", errResp.Errors)
   508  	}
   509  }
   510  
   511  func TestRepository_Mount_Fallback_GetContent(t *testing.T) {
   512  	// This test checks the case where the server does not know
   513  	// about the mount query parameters, so the call falls back to
   514  	// the regular push flow, but using the getContent function
   515  	// parameter to get the content to push.
   516  
   517  	blob := []byte("hello world")
   518  	blobDesc := ocispec.Descriptor{
   519  		MediaType: "test",
   520  		Digest:    digest.FromBytes(blob),
   521  		Size:      int64(len(blob)),
   522  	}
   523  	var sequence string
   524  	var gotBlob []byte
   525  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
   526  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   527  		switch {
   528  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test2/blobs/uploads/":
   529  			w.Header().Set("Location", "/v2/test2/blobs/uploads/"+uuid)
   530  			w.WriteHeader(http.StatusAccepted)
   531  			sequence += "post "
   532  			return
   533  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test2/blobs/uploads/"+uuid:
   534  			if got, want := r.Header.Get("Content-Type"), "application/octet-stream"; got != want {
   535  				t.Errorf("unexpected content type; got %q want %q", got, want)
   536  				w.WriteHeader(http.StatusBadRequest)
   537  				return
   538  			}
   539  			if got, want := r.URL.Query().Get("digest"), blobDesc.Digest.String(); got != want {
   540  				t.Errorf("unexpected content digest; got %q want %q", got, want)
   541  				w.WriteHeader(http.StatusBadRequest)
   542  				return
   543  			}
   544  			data, err := io.ReadAll(r.Body)
   545  			if err != nil {
   546  				t.Errorf("error reading body: %v", err)
   547  				w.WriteHeader(http.StatusInternalServerError)
   548  				return
   549  			}
   550  			gotBlob = data
   551  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   552  			w.WriteHeader(http.StatusCreated)
   553  			sequence += "put "
   554  			return
   555  		default:
   556  			w.WriteHeader(http.StatusForbidden)
   557  		}
   558  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   559  	}))
   560  	defer ts.Close()
   561  	uri, err := url.Parse(ts.URL)
   562  	if err != nil {
   563  		t.Fatalf("invalid test http server: %v", err)
   564  	}
   565  
   566  	repo, err := NewRepository(uri.Host + "/test2")
   567  	if err != nil {
   568  		t.Fatalf("NewRepository() error = %v", err)
   569  	}
   570  	repo.PlainHTTP = true
   571  	ctx := context.Background()
   572  
   573  	err = repo.Mount(ctx, blobDesc, "test", func() (io.ReadCloser, error) {
   574  		return io.NopCloser(bytes.NewReader(blob)), nil
   575  	})
   576  	if err != nil {
   577  		t.Fatalf("Repository.Push() error = %v", err)
   578  	}
   579  	if !bytes.Equal(gotBlob, blob) {
   580  		t.Errorf("Repository.Mount() = %v, want %v", gotBlob, blob)
   581  	}
   582  	if got, want := sequence, "post put "; got != want {
   583  		t.Errorf("unexpected request sequence; got %q want %q", got, want)
   584  	}
   585  }
   586  
   587  func TestRepository_Mount_Fallback_GetContentError(t *testing.T) {
   588  	// This test checks the case where the server does not know
   589  	// about the mount query parameters, so the call falls back to
   590  	// the regular push flow, but it's possible the caller wants to
   591  	// avoid the pull/push pattern so returns an error from getContent
   592  	// and checks it to find out that's happened.
   593  
   594  	blob := []byte("hello world")
   595  	blobDesc := ocispec.Descriptor{
   596  		MediaType: "test",
   597  		Digest:    digest.FromBytes(blob),
   598  		Size:      int64(len(blob)),
   599  	}
   600  	var sequence string
   601  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
   602  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   603  		switch {
   604  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test2/blobs/uploads/":
   605  			w.Header().Set("Location", "/v2/test2/blobs/uploads/"+uuid)
   606  			w.WriteHeader(http.StatusAccepted)
   607  			sequence += "post "
   608  			return
   609  		default:
   610  			w.WriteHeader(http.StatusForbidden)
   611  		}
   612  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   613  	}))
   614  	defer ts.Close()
   615  	uri, err := url.Parse(ts.URL)
   616  	if err != nil {
   617  		t.Fatalf("invalid test http server: %v", err)
   618  	}
   619  
   620  	repo, err := NewRepository(uri.Host + "/test2")
   621  	if err != nil {
   622  		t.Fatalf("NewRepository() error = %v", err)
   623  	}
   624  	repo.PlainHTTP = true
   625  	ctx := context.Background()
   626  
   627  	testErr := errors.New("test error")
   628  	err = repo.Mount(ctx, blobDesc, "test", func() (io.ReadCloser, error) {
   629  		return nil, testErr
   630  	})
   631  	if err == nil {
   632  		t.Fatalf("expected error but found no error")
   633  	}
   634  	if !errors.Is(err, testErr) {
   635  		t.Fatalf("expected getContent error to be wrapped")
   636  	}
   637  	if got, want := sequence, "post "; got != want {
   638  		t.Errorf("unexpected request sequence; got %q want %q", got, want)
   639  	}
   640  }
   641  
   642  func TestRepository_Exists(t *testing.T) {
   643  	blob := []byte("hello world")
   644  	blobDesc := ocispec.Descriptor{
   645  		MediaType: "test",
   646  		Digest:    digest.FromBytes(blob),
   647  		Size:      int64(len(blob)),
   648  	}
   649  	index := []byte(`{"manifests":[]}`)
   650  	indexDesc := ocispec.Descriptor{
   651  		MediaType: ocispec.MediaTypeImageIndex,
   652  		Digest:    digest.FromBytes(index),
   653  		Size:      int64(len(index)),
   654  	}
   655  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   656  		if r.Method != http.MethodHead {
   657  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   658  			w.WriteHeader(http.StatusMethodNotAllowed)
   659  			return
   660  		}
   661  		switch r.URL.Path {
   662  		case "/v2/test/blobs/" + blobDesc.Digest.String():
   663  			w.Header().Set("Content-Type", "application/octet-stream")
   664  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   665  			w.Header().Set("Content-Length", strconv.Itoa(int(blobDesc.Size)))
   666  		case "/v2/test/manifests/" + indexDesc.Digest.String():
   667  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   668  				t.Errorf("manifest not convertable: %s", accept)
   669  				w.WriteHeader(http.StatusBadRequest)
   670  				return
   671  			}
   672  			w.Header().Set("Content-Type", indexDesc.MediaType)
   673  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   674  			w.Header().Set("Content-Length", strconv.Itoa(int(indexDesc.Size)))
   675  		default:
   676  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   677  			w.WriteHeader(http.StatusNotFound)
   678  		}
   679  	}))
   680  	defer ts.Close()
   681  	uri, err := url.Parse(ts.URL)
   682  	if err != nil {
   683  		t.Fatalf("invalid test http server: %v", err)
   684  	}
   685  
   686  	repo, err := NewRepository(uri.Host + "/test")
   687  	if err != nil {
   688  		t.Fatalf("NewRepository() error = %v", err)
   689  	}
   690  	repo.PlainHTTP = true
   691  	ctx := context.Background()
   692  
   693  	exists, err := repo.Exists(ctx, blobDesc)
   694  	if err != nil {
   695  		t.Fatalf("Repository.Exists() error = %v", err)
   696  	}
   697  	if !exists {
   698  		t.Errorf("Repository.Exists() = %v, want %v", exists, true)
   699  	}
   700  
   701  	exists, err = repo.Exists(ctx, indexDesc)
   702  	if err != nil {
   703  		t.Fatalf("Repository.Exists() error = %v", err)
   704  	}
   705  	if !exists {
   706  		t.Errorf("Repository.Exists() = %v, want %v", exists, true)
   707  	}
   708  }
   709  
   710  func TestRepository_Delete(t *testing.T) {
   711  	blob := []byte("hello world")
   712  	blobDesc := ocispec.Descriptor{
   713  		MediaType: "test",
   714  		Digest:    digest.FromBytes(blob),
   715  		Size:      int64(len(blob)),
   716  	}
   717  	index := []byte(`{"manifests":[]}`)
   718  	indexDesc := ocispec.Descriptor{
   719  		MediaType: ocispec.MediaTypeImageIndex,
   720  		Digest:    digest.FromBytes(index),
   721  		Size:      int64(len(index)),
   722  	}
   723  
   724  	var blobDeleted bool
   725  	var indexDeleted bool
   726  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   727  		switch {
   728  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/blobs/"+blobDesc.Digest.String():
   729  			blobDeleted = true
   730  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   731  			w.WriteHeader(http.StatusAccepted)
   732  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
   733  			indexDeleted = true
   734  			// no "Docker-Content-Digest" header for manifest deletion
   735  			w.WriteHeader(http.StatusAccepted)
   736  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
   737  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   738  				t.Errorf("manifest not convertable: %s", accept)
   739  				w.WriteHeader(http.StatusBadRequest)
   740  				return
   741  			}
   742  			w.Header().Set("Content-Type", indexDesc.MediaType)
   743  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   744  			if _, err := w.Write(index); err != nil {
   745  				t.Errorf("failed to write %q: %v", r.URL, err)
   746  			}
   747  		default:
   748  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   749  			w.WriteHeader(http.StatusNotFound)
   750  		}
   751  	}))
   752  	defer ts.Close()
   753  	uri, err := url.Parse(ts.URL)
   754  	if err != nil {
   755  		t.Fatalf("invalid test http server: %v", err)
   756  	}
   757  
   758  	repo, err := NewRepository(uri.Host + "/test")
   759  	if err != nil {
   760  		t.Fatalf("NewRepository() error = %v", err)
   761  	}
   762  	repo.PlainHTTP = true
   763  	ctx := context.Background()
   764  
   765  	err = repo.Delete(ctx, blobDesc)
   766  	if err != nil {
   767  		t.Fatalf("Repository.Delete() error = %v", err)
   768  	}
   769  	if !blobDeleted {
   770  		t.Errorf("Repository.Delete() = %v, want %v", blobDeleted, true)
   771  	}
   772  
   773  	err = repo.Delete(ctx, indexDesc)
   774  	if err != nil {
   775  		t.Fatalf("Repository.Delete() error = %v", err)
   776  	}
   777  	if !indexDeleted {
   778  		t.Errorf("Repository.Delete() = %v, want %v", indexDeleted, true)
   779  	}
   780  }
   781  
   782  func TestRepository_Resolve(t *testing.T) {
   783  	blob := []byte("hello world")
   784  	blobDesc := ocispec.Descriptor{
   785  		MediaType: "test",
   786  		Digest:    digest.FromBytes(blob),
   787  		Size:      int64(len(blob)),
   788  	}
   789  	index := []byte(`{"manifests":[]}`)
   790  	indexDesc := ocispec.Descriptor{
   791  		MediaType: ocispec.MediaTypeImageIndex,
   792  		Digest:    digest.FromBytes(index),
   793  		Size:      int64(len(index)),
   794  	}
   795  	ref := "foobar"
   796  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   797  		if r.Method != http.MethodHead {
   798  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   799  			w.WriteHeader(http.StatusMethodNotAllowed)
   800  			return
   801  		}
   802  		switch r.URL.Path {
   803  		case "/v2/test/manifests/" + blobDesc.Digest.String():
   804  			w.WriteHeader(http.StatusNotFound)
   805  		case "/v2/test/manifests/" + indexDesc.Digest.String(),
   806  			"/v2/test/manifests/" + ref:
   807  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   808  				t.Errorf("manifest not convertable: %s", accept)
   809  				w.WriteHeader(http.StatusBadRequest)
   810  				return
   811  			}
   812  			w.Header().Set("Content-Type", indexDesc.MediaType)
   813  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   814  			w.Header().Set("Content-Length", strconv.Itoa(int(indexDesc.Size)))
   815  		default:
   816  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   817  			w.WriteHeader(http.StatusNotFound)
   818  		}
   819  	}))
   820  	defer ts.Close()
   821  	uri, err := url.Parse(ts.URL)
   822  	if err != nil {
   823  		t.Fatalf("invalid test http server: %v", err)
   824  	}
   825  
   826  	repoName := uri.Host + "/test"
   827  	repo, err := NewRepository(repoName)
   828  	if err != nil {
   829  		t.Fatalf("NewRepository() error = %v", err)
   830  	}
   831  	repo.PlainHTTP = true
   832  	ctx := context.Background()
   833  
   834  	_, err = repo.Resolve(ctx, blobDesc.Digest.String())
   835  	if !errors.Is(err, errdef.ErrNotFound) {
   836  		t.Errorf("Repository.Resolve() error = %v, wantErr %v", err, errdef.ErrNotFound)
   837  	}
   838  
   839  	got, err := repo.Resolve(ctx, indexDesc.Digest.String())
   840  	if err != nil {
   841  		t.Fatalf("Repository.Resolve() error = %v", err)
   842  	}
   843  	if !reflect.DeepEqual(got, indexDesc) {
   844  		t.Errorf("Repository.Resolve() = %v, want %v", got, indexDesc)
   845  	}
   846  
   847  	got, err = repo.Resolve(ctx, ref)
   848  	if err != nil {
   849  		t.Fatalf("Repository.Resolve() error = %v", err)
   850  	}
   851  	if !reflect.DeepEqual(got, indexDesc) {
   852  		t.Errorf("Repository.Resolve() = %v, want %v", got, indexDesc)
   853  	}
   854  
   855  	tagDigestRef := "whatever" + "@" + indexDesc.Digest.String()
   856  	got, err = repo.Resolve(ctx, tagDigestRef)
   857  	if err != nil {
   858  		t.Fatalf("Repository.Resolve() error = %v", err)
   859  	}
   860  	if !reflect.DeepEqual(got, indexDesc) {
   861  		t.Errorf("Repository.Resolve() = %v, want %v", got, indexDesc)
   862  	}
   863  
   864  	fqdnRef := repoName + ":" + tagDigestRef
   865  	got, err = repo.Resolve(ctx, fqdnRef)
   866  	if err != nil {
   867  		t.Fatalf("Repository.Resolve() error = %v", err)
   868  	}
   869  	if !reflect.DeepEqual(got, indexDesc) {
   870  		t.Errorf("Repository.Resolve() = %v, want %v", got, indexDesc)
   871  	}
   872  }
   873  
   874  func TestRepository_Tag(t *testing.T) {
   875  	blob := []byte("hello world")
   876  	blobDesc := ocispec.Descriptor{
   877  		MediaType: "test",
   878  		Digest:    digest.FromBytes(blob),
   879  		Size:      int64(len(blob)),
   880  	}
   881  	index := []byte(`{"manifests":[]}`)
   882  	indexDesc := ocispec.Descriptor{
   883  		MediaType: ocispec.MediaTypeImageIndex,
   884  		Digest:    digest.FromBytes(index),
   885  		Size:      int64(len(index)),
   886  	}
   887  	var gotIndex []byte
   888  	ref := "foobar"
   889  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   890  		switch {
   891  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+blobDesc.Digest.String():
   892  			w.WriteHeader(http.StatusNotFound)
   893  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
   894  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   895  				t.Errorf("manifest not convertable: %s", accept)
   896  				w.WriteHeader(http.StatusBadRequest)
   897  				return
   898  			}
   899  			w.Header().Set("Content-Type", indexDesc.MediaType)
   900  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   901  			if _, err := w.Write(index); err != nil {
   902  				t.Errorf("failed to write %q: %v", r.URL, err)
   903  			}
   904  		case r.Method == http.MethodPut &&
   905  			r.URL.Path == "/v2/test/manifests/"+ref || r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
   906  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
   907  				w.WriteHeader(http.StatusBadRequest)
   908  				break
   909  			}
   910  			buf := bytes.NewBuffer(nil)
   911  			if _, err := buf.ReadFrom(r.Body); err != nil {
   912  				t.Errorf("fail to read: %v", err)
   913  			}
   914  			gotIndex = buf.Bytes()
   915  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   916  			w.WriteHeader(http.StatusCreated)
   917  			return
   918  		default:
   919  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   920  			w.WriteHeader(http.StatusForbidden)
   921  		}
   922  	}))
   923  	defer ts.Close()
   924  	uri, err := url.Parse(ts.URL)
   925  	if err != nil {
   926  		t.Fatalf("invalid test http server: %v", err)
   927  	}
   928  
   929  	repo, err := NewRepository(uri.Host + "/test")
   930  	if err != nil {
   931  		t.Fatalf("NewRepository() error = %v", err)
   932  	}
   933  	repo.PlainHTTP = true
   934  	ctx := context.Background()
   935  
   936  	err = repo.Tag(ctx, blobDesc, ref)
   937  	if err == nil {
   938  		t.Errorf("Repository.Tag() error = %v, wantErr %v", err, true)
   939  	}
   940  
   941  	err = repo.Tag(ctx, indexDesc, ref)
   942  	if err != nil {
   943  		t.Fatalf("Repository.Tag() error = %v", err)
   944  	}
   945  	if !bytes.Equal(gotIndex, index) {
   946  		t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index)
   947  	}
   948  
   949  	gotIndex = nil
   950  	err = repo.Tag(ctx, indexDesc, indexDesc.Digest.String())
   951  	if err != nil {
   952  		t.Fatalf("Repository.Tag() error = %v", err)
   953  	}
   954  	if !bytes.Equal(gotIndex, index) {
   955  		t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index)
   956  	}
   957  }
   958  
   959  func TestRepository_PushReference(t *testing.T) {
   960  	index := []byte(`{"manifests":[]}`)
   961  	indexDesc := ocispec.Descriptor{
   962  		MediaType: ocispec.MediaTypeImageIndex,
   963  		Digest:    digest.FromBytes(index),
   964  		Size:      int64(len(index)),
   965  	}
   966  	var gotIndex []byte
   967  	ref := "foobar"
   968  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   969  		switch {
   970  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+ref:
   971  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
   972  				w.WriteHeader(http.StatusBadRequest)
   973  				break
   974  			}
   975  			buf := bytes.NewBuffer(nil)
   976  			if _, err := buf.ReadFrom(r.Body); err != nil {
   977  				t.Errorf("fail to read: %v", err)
   978  			}
   979  			gotIndex = buf.Bytes()
   980  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   981  			w.WriteHeader(http.StatusCreated)
   982  			return
   983  		default:
   984  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   985  			w.WriteHeader(http.StatusForbidden)
   986  		}
   987  	}))
   988  	defer ts.Close()
   989  	uri, err := url.Parse(ts.URL)
   990  	if err != nil {
   991  		t.Fatalf("invalid test http server: %v", err)
   992  	}
   993  
   994  	repo, err := NewRepository(uri.Host + "/test")
   995  	if err != nil {
   996  		t.Fatalf("NewRepository() error = %v", err)
   997  	}
   998  	repo.PlainHTTP = true
   999  	ctx := context.Background()
  1000  	err = repo.PushReference(ctx, indexDesc, bytes.NewReader(index), ref)
  1001  	if err != nil {
  1002  		t.Fatalf("Repository.PushReference() error = %v", err)
  1003  	}
  1004  	if !bytes.Equal(gotIndex, index) {
  1005  		t.Errorf("Repository.PushReference() = %v, want %v", gotIndex, index)
  1006  	}
  1007  }
  1008  
  1009  func TestRepository_FetchReference(t *testing.T) {
  1010  	blob := []byte("hello world")
  1011  	blobDesc := ocispec.Descriptor{
  1012  		MediaType: "test",
  1013  		Digest:    digest.FromBytes(blob),
  1014  		Size:      int64(len(blob)),
  1015  	}
  1016  	index := []byte(`{"manifests":[]}`)
  1017  	indexDesc := ocispec.Descriptor{
  1018  		MediaType: ocispec.MediaTypeImageIndex,
  1019  		Digest:    digest.FromBytes(index),
  1020  		Size:      int64(len(index)),
  1021  	}
  1022  	ref := "foobar"
  1023  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1024  		if r.Method != http.MethodGet {
  1025  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  1026  			w.WriteHeader(http.StatusMethodNotAllowed)
  1027  			return
  1028  		}
  1029  		switch r.URL.Path {
  1030  		case "/v2/test/manifests/" + blobDesc.Digest.String():
  1031  			w.WriteHeader(http.StatusNotFound)
  1032  		case "/v2/test/manifests/" + indexDesc.Digest.String(),
  1033  			"/v2/test/manifests/" + ref:
  1034  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
  1035  				t.Errorf("manifest not convertable: %s", accept)
  1036  				w.WriteHeader(http.StatusBadRequest)
  1037  				return
  1038  			}
  1039  			w.Header().Set("Content-Type", indexDesc.MediaType)
  1040  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  1041  			if _, err := w.Write(index); err != nil {
  1042  				t.Errorf("failed to write %q: %v", r.URL, err)
  1043  			}
  1044  		default:
  1045  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  1046  			w.WriteHeader(http.StatusNotFound)
  1047  		}
  1048  	}))
  1049  	defer ts.Close()
  1050  	uri, err := url.Parse(ts.URL)
  1051  	if err != nil {
  1052  		t.Fatalf("invalid test http server: %v", err)
  1053  	}
  1054  
  1055  	repoName := uri.Host + "/test"
  1056  	repo, err := NewRepository(repoName)
  1057  	if err != nil {
  1058  		t.Fatalf("NewRepository() error = %v", err)
  1059  	}
  1060  	repo.PlainHTTP = true
  1061  	ctx := context.Background()
  1062  
  1063  	// test with blob digest
  1064  	_, _, err = repo.FetchReference(ctx, blobDesc.Digest.String())
  1065  	if !errors.Is(err, errdef.ErrNotFound) {
  1066  		t.Errorf("Repository.FetchReference() error = %v, wantErr %v", err, errdef.ErrNotFound)
  1067  	}
  1068  
  1069  	// test with manifest digest
  1070  	gotDesc, rc, err := repo.FetchReference(ctx, indexDesc.Digest.String())
  1071  	if err != nil {
  1072  		t.Fatalf("Repository.FetchReference() error = %v", err)
  1073  	}
  1074  	if !reflect.DeepEqual(gotDesc, indexDesc) {
  1075  		t.Errorf("Repository.FetchReference() = %v, want %v", gotDesc, indexDesc)
  1076  	}
  1077  	buf := bytes.NewBuffer(nil)
  1078  	if _, err := buf.ReadFrom(rc); err != nil {
  1079  		t.Errorf("fail to read: %v", err)
  1080  	}
  1081  	if err := rc.Close(); err != nil {
  1082  		t.Errorf("fail to close: %v", err)
  1083  	}
  1084  	if got := buf.Bytes(); !bytes.Equal(got, index) {
  1085  		t.Errorf("Repository.FetchReference() = %v, want %v", got, index)
  1086  	}
  1087  
  1088  	// test with manifest tag
  1089  	gotDesc, rc, err = repo.FetchReference(ctx, ref)
  1090  	if err != nil {
  1091  		t.Fatalf("Repository.FetchReference() error = %v", err)
  1092  	}
  1093  	if !reflect.DeepEqual(gotDesc, indexDesc) {
  1094  		t.Errorf("Repository.FetchReference() = %v, want %v", gotDesc, indexDesc)
  1095  	}
  1096  	buf.Reset()
  1097  	if _, err := buf.ReadFrom(rc); err != nil {
  1098  		t.Errorf("fail to read: %v", err)
  1099  	}
  1100  	if err := rc.Close(); err != nil {
  1101  		t.Errorf("fail to close: %v", err)
  1102  	}
  1103  	if got := buf.Bytes(); !bytes.Equal(got, index) {
  1104  		t.Errorf("Repository.FetchReference() = %v, want %v", got, index)
  1105  	}
  1106  
  1107  	// test with manifest tag@digest
  1108  	tagDigestRef := "whatever" + "@" + indexDesc.Digest.String()
  1109  	gotDesc, rc, err = repo.FetchReference(ctx, tagDigestRef)
  1110  	if err != nil {
  1111  		t.Fatalf("Repository.FetchReference() error = %v", err)
  1112  	}
  1113  	if !reflect.DeepEqual(gotDesc, indexDesc) {
  1114  		t.Errorf("Repository.FetchReference() = %v, want %v", gotDesc, indexDesc)
  1115  	}
  1116  	buf.Reset()
  1117  	if _, err := buf.ReadFrom(rc); err != nil {
  1118  		t.Errorf("fail to read: %v", err)
  1119  	}
  1120  	if err := rc.Close(); err != nil {
  1121  		t.Errorf("fail to close: %v", err)
  1122  	}
  1123  	if got := buf.Bytes(); !bytes.Equal(got, index) {
  1124  		t.Errorf("Repository.FetchReference() = %v, want %v", got, index)
  1125  	}
  1126  
  1127  	// test with manifest FQDN
  1128  	fqdnRef := repoName + ":" + tagDigestRef
  1129  	gotDesc, rc, err = repo.FetchReference(ctx, fqdnRef)
  1130  	if err != nil {
  1131  		t.Fatalf("Repository.FetchReference() error = %v", err)
  1132  	}
  1133  	if !reflect.DeepEqual(gotDesc, indexDesc) {
  1134  		t.Errorf("Repository.FetchReference() = %v, want %v", gotDesc, indexDesc)
  1135  	}
  1136  	buf.Reset()
  1137  	if _, err := buf.ReadFrom(rc); err != nil {
  1138  		t.Errorf("fail to read: %v", err)
  1139  	}
  1140  	if err := rc.Close(); err != nil {
  1141  		t.Errorf("fail to close: %v", err)
  1142  	}
  1143  	if got := buf.Bytes(); !bytes.Equal(got, index) {
  1144  		t.Errorf("Repository.FetchReference() = %v, want %v", got, index)
  1145  	}
  1146  }
  1147  
  1148  func TestRepository_Tags(t *testing.T) {
  1149  	tagSet := [][]string{
  1150  		{"the", "quick", "brown", "fox"},
  1151  		{"jumps", "over", "the", "lazy"},
  1152  		{"dog"},
  1153  	}
  1154  	var ts *httptest.Server
  1155  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1156  		if r.Method != http.MethodGet || r.URL.Path != "/v2/test/tags/list" {
  1157  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  1158  			w.WriteHeader(http.StatusNotFound)
  1159  			return
  1160  		}
  1161  		q := r.URL.Query()
  1162  		n, err := strconv.Atoi(q.Get("n"))
  1163  		if err != nil || n != 4 {
  1164  			t.Errorf("bad page size: %s", q.Get("n"))
  1165  			w.WriteHeader(http.StatusBadRequest)
  1166  			return
  1167  		}
  1168  		var tags []string
  1169  		switch q.Get("test") {
  1170  		case "foo":
  1171  			tags = tagSet[1]
  1172  			w.Header().Set("Link", fmt.Sprintf(`<%s/v2/test/tags/list?n=4&test=bar>; rel="next"`, ts.URL))
  1173  		case "bar":
  1174  			tags = tagSet[2]
  1175  		default:
  1176  			tags = tagSet[0]
  1177  			w.Header().Set("Link", `</v2/test/tags/list?n=4&test=foo>; rel="next"`)
  1178  		}
  1179  		result := struct {
  1180  			Tags []string `json:"tags"`
  1181  		}{
  1182  			Tags: tags,
  1183  		}
  1184  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1185  			t.Errorf("failed to write response: %v", err)
  1186  		}
  1187  	}))
  1188  	defer ts.Close()
  1189  	uri, err := url.Parse(ts.URL)
  1190  	if err != nil {
  1191  		t.Fatalf("invalid test http server: %v", err)
  1192  	}
  1193  
  1194  	repo, err := NewRepository(uri.Host + "/test")
  1195  	if err != nil {
  1196  		t.Fatalf("NewRepository() error = %v", err)
  1197  	}
  1198  	repo.PlainHTTP = true
  1199  	repo.TagListPageSize = 4
  1200  
  1201  	ctx := context.Background()
  1202  	index := 0
  1203  	if err := repo.Tags(ctx, "", func(got []string) error {
  1204  		if index > 2 {
  1205  			t.Fatalf("out of index bound: %d", index)
  1206  		}
  1207  		tags := tagSet[index]
  1208  		index++
  1209  		if !reflect.DeepEqual(got, tags) {
  1210  			t.Errorf("Repository.Tags() = %v, want %v", got, tags)
  1211  		}
  1212  		return nil
  1213  	}); err != nil {
  1214  		t.Errorf("Repository.Tags() error = %v", err)
  1215  	}
  1216  }
  1217  
  1218  func TestRepository_Predecessors(t *testing.T) {
  1219  	manifest := []byte(`{"layers":[]}`)
  1220  	manifestDesc := ocispec.Descriptor{
  1221  		MediaType: ocispec.MediaTypeImageManifest,
  1222  		Digest:    digest.FromBytes(manifest),
  1223  		Size:      int64(len(manifest)),
  1224  	}
  1225  	referrerSet := [][]ocispec.Descriptor{
  1226  		{
  1227  			{
  1228  				MediaType:    spec.MediaTypeArtifactManifest,
  1229  				Size:         1,
  1230  				Digest:       digest.FromString("1"),
  1231  				ArtifactType: "application/vnd.test",
  1232  			},
  1233  			{
  1234  				MediaType:    spec.MediaTypeArtifactManifest,
  1235  				Size:         2,
  1236  				Digest:       digest.FromString("2"),
  1237  				ArtifactType: "application/vnd.test",
  1238  			},
  1239  		},
  1240  		{
  1241  			{
  1242  				MediaType:    spec.MediaTypeArtifactManifest,
  1243  				Size:         3,
  1244  				Digest:       digest.FromString("3"),
  1245  				ArtifactType: "application/vnd.test",
  1246  			},
  1247  			{
  1248  				MediaType:    spec.MediaTypeArtifactManifest,
  1249  				Size:         4,
  1250  				Digest:       digest.FromString("4"),
  1251  				ArtifactType: "application/vnd.test",
  1252  			},
  1253  		},
  1254  		{
  1255  			{
  1256  				MediaType:    spec.MediaTypeArtifactManifest,
  1257  				Size:         5,
  1258  				Digest:       digest.FromString("5"),
  1259  				ArtifactType: "application/vnd.test",
  1260  			},
  1261  		},
  1262  	}
  1263  	var ts *httptest.Server
  1264  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1265  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1266  		if r.Method != http.MethodGet || r.URL.Path != path {
  1267  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  1268  			w.WriteHeader(http.StatusNotFound)
  1269  			return
  1270  		}
  1271  		q := r.URL.Query()
  1272  		n, err := strconv.Atoi(q.Get("n"))
  1273  		if err != nil || n != 2 {
  1274  			t.Errorf("bad page size: %s", q.Get("n"))
  1275  			w.WriteHeader(http.StatusBadRequest)
  1276  			return
  1277  		}
  1278  		var referrers []ocispec.Descriptor
  1279  		switch q.Get("test") {
  1280  		case "foo":
  1281  			referrers = referrerSet[1]
  1282  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  1283  		case "bar":
  1284  			referrers = referrerSet[2]
  1285  		default:
  1286  			referrers = referrerSet[0]
  1287  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  1288  		}
  1289  		result := ocispec.Index{
  1290  			Versioned: specs.Versioned{
  1291  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1292  			},
  1293  			MediaType: ocispec.MediaTypeImageIndex,
  1294  			Manifests: referrers,
  1295  		}
  1296  		w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  1297  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1298  			t.Errorf("failed to write response: %v", err)
  1299  		}
  1300  	}))
  1301  	defer ts.Close()
  1302  	uri, err := url.Parse(ts.URL)
  1303  	if err != nil {
  1304  		t.Fatalf("invalid test http server: %v", err)
  1305  	}
  1306  
  1307  	repo, err := NewRepository(uri.Host + "/test")
  1308  	if err != nil {
  1309  		t.Fatalf("NewRepository() error = %v", err)
  1310  	}
  1311  	repo.PlainHTTP = true
  1312  	repo.ReferrerListPageSize = 2
  1313  
  1314  	ctx := context.Background()
  1315  	got, err := repo.Predecessors(ctx, manifestDesc)
  1316  	if err != nil {
  1317  		t.Fatalf("Repository.Predecessors() error = %v", err)
  1318  	}
  1319  	var want []ocispec.Descriptor
  1320  	for _, referrers := range referrerSet {
  1321  		want = append(want, referrers...)
  1322  	}
  1323  	if !reflect.DeepEqual(got, want) {
  1324  		t.Errorf("Repository.Predecessors() = %v, want %v", got, want)
  1325  	}
  1326  }
  1327  
  1328  func TestRepository_Referrers(t *testing.T) {
  1329  	manifest := []byte(`{"layers":[]}`)
  1330  	manifestDesc := ocispec.Descriptor{
  1331  		MediaType: ocispec.MediaTypeImageManifest,
  1332  		Digest:    digest.FromBytes(manifest),
  1333  		Size:      int64(len(manifest)),
  1334  	}
  1335  	referrerSet := [][]ocispec.Descriptor{
  1336  		{
  1337  			{
  1338  				MediaType:    spec.MediaTypeArtifactManifest,
  1339  				Size:         1,
  1340  				Digest:       digest.FromString("1"),
  1341  				ArtifactType: "application/vnd.test",
  1342  			},
  1343  			{
  1344  				MediaType:    spec.MediaTypeArtifactManifest,
  1345  				Size:         2,
  1346  				Digest:       digest.FromString("2"),
  1347  				ArtifactType: "application/vnd.test",
  1348  			},
  1349  		},
  1350  		{
  1351  			{
  1352  				MediaType:    spec.MediaTypeArtifactManifest,
  1353  				Size:         3,
  1354  				Digest:       digest.FromString("3"),
  1355  				ArtifactType: "application/vnd.test",
  1356  			},
  1357  			{
  1358  				MediaType:    spec.MediaTypeArtifactManifest,
  1359  				Size:         4,
  1360  				Digest:       digest.FromString("4"),
  1361  				ArtifactType: "application/vnd.test",
  1362  			},
  1363  		},
  1364  		{
  1365  			{
  1366  				MediaType:    spec.MediaTypeArtifactManifest,
  1367  				Size:         5,
  1368  				Digest:       digest.FromString("5"),
  1369  				ArtifactType: "application/vnd.test",
  1370  			},
  1371  		},
  1372  	}
  1373  	var ts *httptest.Server
  1374  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1375  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1376  		if r.Method != http.MethodGet || r.URL.Path != path {
  1377  			referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1378  			if r.URL.Path != "/v2/test/manifests/"+referrersTag {
  1379  				t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1380  			}
  1381  			w.WriteHeader(http.StatusNotFound)
  1382  			return
  1383  		}
  1384  		q := r.URL.Query()
  1385  		n, err := strconv.Atoi(q.Get("n"))
  1386  		if err != nil || n != 2 {
  1387  			t.Errorf("bad page size: %s", q.Get("n"))
  1388  			w.WriteHeader(http.StatusBadRequest)
  1389  			return
  1390  		}
  1391  		var referrers []ocispec.Descriptor
  1392  		switch q.Get("test") {
  1393  		case "foo":
  1394  			referrers = referrerSet[1]
  1395  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  1396  		case "bar":
  1397  			referrers = referrerSet[2]
  1398  		default:
  1399  			referrers = referrerSet[0]
  1400  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  1401  		}
  1402  		result := ocispec.Index{
  1403  			Versioned: specs.Versioned{
  1404  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1405  			},
  1406  			MediaType: ocispec.MediaTypeImageIndex,
  1407  			Manifests: referrers,
  1408  		}
  1409  		w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  1410  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1411  			t.Errorf("failed to write response: %v", err)
  1412  		}
  1413  	}))
  1414  	defer ts.Close()
  1415  	uri, err := url.Parse(ts.URL)
  1416  	if err != nil {
  1417  		t.Fatalf("invalid test http server: %v", err)
  1418  	}
  1419  
  1420  	ctx := context.Background()
  1421  
  1422  	// test auto detect
  1423  	// remote server supports Referrers, should be no error
  1424  	repo, err := NewRepository(uri.Host + "/test")
  1425  	if err != nil {
  1426  		t.Fatalf("NewRepository() error = %v", err)
  1427  	}
  1428  	repo.PlainHTTP = true
  1429  	repo.ReferrerListPageSize = 2
  1430  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1431  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1432  	}
  1433  	index := 0
  1434  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1435  		if index >= len(referrerSet) {
  1436  			t.Fatalf("out of index bound: %d", index)
  1437  		}
  1438  		referrers := referrerSet[index]
  1439  		index++
  1440  		if !reflect.DeepEqual(got, referrers) {
  1441  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1442  		}
  1443  		return nil
  1444  	}); err != nil {
  1445  		t.Errorf("Repository.Referrers() error = %v", err)
  1446  	}
  1447  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1448  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1449  	}
  1450  
  1451  	// test force attempt Referrers
  1452  	// remote server supports Referrers, should be no error
  1453  	repo, err = NewRepository(uri.Host + "/test")
  1454  	if err != nil {
  1455  		t.Fatalf("NewRepository() error = %v", err)
  1456  	}
  1457  	repo.PlainHTTP = true
  1458  	repo.ReferrerListPageSize = 2
  1459  	repo.SetReferrersCapability(true)
  1460  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1461  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1462  	}
  1463  	index = 0
  1464  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1465  		if index >= len(referrerSet) {
  1466  			t.Fatalf("out of index bound: %d", index)
  1467  		}
  1468  		referrers := referrerSet[index]
  1469  		index++
  1470  		if !reflect.DeepEqual(got, referrers) {
  1471  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1472  		}
  1473  		return nil
  1474  	}); err != nil {
  1475  		t.Errorf("Repository.Referrers() error = %v", err)
  1476  	}
  1477  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1478  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1479  	}
  1480  
  1481  	// test force attempt tag schema
  1482  	// request tag schema but got 404, should be no error
  1483  	repo, err = NewRepository(uri.Host + "/test")
  1484  	if err != nil {
  1485  		t.Fatalf("NewRepository() error = %v", err)
  1486  	}
  1487  	repo.PlainHTTP = true
  1488  	repo.ReferrerListPageSize = 2
  1489  	repo.SetReferrersCapability(false)
  1490  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1491  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1492  	}
  1493  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1494  		return nil
  1495  	}); err != nil {
  1496  		t.Errorf("Repository.Referrers() error = %v", err)
  1497  	}
  1498  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1499  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1500  	}
  1501  }
  1502  
  1503  func TestRepository_Referrers_TagSchemaFallback(t *testing.T) {
  1504  	manifest := []byte(`{"layers":[]}`)
  1505  	manifestDesc := ocispec.Descriptor{
  1506  		MediaType: ocispec.MediaTypeImageManifest,
  1507  		Digest:    digest.FromBytes(manifest),
  1508  		Size:      int64(len(manifest)),
  1509  	}
  1510  
  1511  	referrers := []ocispec.Descriptor{
  1512  		{
  1513  			MediaType:    spec.MediaTypeArtifactManifest,
  1514  			Size:         1,
  1515  			Digest:       digest.FromString("1"),
  1516  			ArtifactType: "application/vnd.test",
  1517  		},
  1518  		{
  1519  			MediaType:    spec.MediaTypeArtifactManifest,
  1520  			Size:         2,
  1521  			Digest:       digest.FromString("2"),
  1522  			ArtifactType: "application/vnd.test",
  1523  		},
  1524  		{
  1525  			MediaType:    spec.MediaTypeArtifactManifest,
  1526  			Size:         3,
  1527  			Digest:       digest.FromString("3"),
  1528  			ArtifactType: "application/vnd.test",
  1529  		},
  1530  		{
  1531  			MediaType:    spec.MediaTypeArtifactManifest,
  1532  			Size:         4,
  1533  			Digest:       digest.FromString("4"),
  1534  			ArtifactType: "application/vnd.test",
  1535  		},
  1536  		{
  1537  			MediaType:    spec.MediaTypeArtifactManifest,
  1538  			Size:         5,
  1539  			Digest:       digest.FromString("5"),
  1540  			ArtifactType: "application/vnd.test",
  1541  		},
  1542  	}
  1543  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1544  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1545  		path := "/v2/test/manifests/" + referrersTag
  1546  		if r.Method != http.MethodGet || r.URL.Path != path {
  1547  			if r.URL.Path != "/v2/test/referrers/"+manifestDesc.Digest.String() {
  1548  				t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1549  			}
  1550  			w.WriteHeader(http.StatusNotFound)
  1551  			return
  1552  		}
  1553  
  1554  		result := ocispec.Index{
  1555  			Versioned: specs.Versioned{
  1556  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1557  			},
  1558  			MediaType: ocispec.MediaTypeImageIndex,
  1559  			Manifests: referrers,
  1560  		}
  1561  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1562  			t.Errorf("failed to write response: %v", err)
  1563  		}
  1564  	}))
  1565  	defer ts.Close()
  1566  	uri, err := url.Parse(ts.URL)
  1567  	if err != nil {
  1568  		t.Fatalf("invalid test http server: %v", err)
  1569  	}
  1570  	ctx := context.Background()
  1571  
  1572  	// test auto detect
  1573  	// remote server does not support Referrers, should fallback to tag schema
  1574  	repo, err := NewRepository(uri.Host + "/test")
  1575  	if err != nil {
  1576  		t.Fatalf("NewRepository() error = %v", err)
  1577  	}
  1578  	repo.PlainHTTP = true
  1579  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1580  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1581  	}
  1582  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1583  		if !reflect.DeepEqual(got, referrers) {
  1584  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1585  		}
  1586  		return nil
  1587  	}); err != nil {
  1588  		t.Errorf("Repository.Referrers() error = %v", err)
  1589  	}
  1590  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1591  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1592  	}
  1593  
  1594  	// test force attempt Referrers
  1595  	// remote server does not support Referrers, should return error
  1596  	repo, err = NewRepository(uri.Host + "/test")
  1597  	if err != nil {
  1598  		t.Fatalf("NewRepository() error = %v", err)
  1599  	}
  1600  	repo.PlainHTTP = true
  1601  	repo.SetReferrersCapability(true)
  1602  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1603  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1604  	}
  1605  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1606  		return nil
  1607  	}); err == nil {
  1608  		t.Errorf("Repository.Referrers() error = %v, wantErr %v", err, true)
  1609  	}
  1610  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1611  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1612  	}
  1613  
  1614  	// test force attempt tag schema
  1615  	// should request tag schema
  1616  	repo, err = NewRepository(uri.Host + "/test")
  1617  	if err != nil {
  1618  		t.Fatalf("NewRepository() error = %v", err)
  1619  	}
  1620  	repo.PlainHTTP = true
  1621  	repo.SetReferrersCapability(false)
  1622  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1623  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1624  	}
  1625  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1626  		if !reflect.DeepEqual(got, referrers) {
  1627  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1628  		}
  1629  		return nil
  1630  	}); err != nil {
  1631  		t.Errorf("Repository.Referrers() error = %v", err)
  1632  	}
  1633  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1634  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1635  	}
  1636  }
  1637  
  1638  func TestRepository_Referrers_TagSchemaFallback_NotFound(t *testing.T) {
  1639  	manifest := []byte(`{"layers":[]}`)
  1640  	manifestDesc := ocispec.Descriptor{
  1641  		MediaType: ocispec.MediaTypeImageManifest,
  1642  		Digest:    digest.FromBytes(manifest),
  1643  		Size:      int64(len(manifest)),
  1644  	}
  1645  
  1646  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1647  		referrersUrl := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1648  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1649  		tagSchemaUrl := "/v2/test/manifests/" + referrersTag
  1650  		if r.Method == http.MethodGet ||
  1651  			r.URL.Path == referrersUrl ||
  1652  			r.URL.Path == tagSchemaUrl {
  1653  			w.WriteHeader(http.StatusNotFound)
  1654  			return
  1655  		}
  1656  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1657  		w.WriteHeader(http.StatusNotFound)
  1658  	}))
  1659  	defer ts.Close()
  1660  	uri, err := url.Parse(ts.URL)
  1661  	if err != nil {
  1662  		t.Fatalf("invalid test http server: %v", err)
  1663  	}
  1664  	ctx := context.Background()
  1665  
  1666  	// test auto detect
  1667  	// tag schema referrers not found, should be no error
  1668  	repo, err := NewRepository(uri.Host + "/test")
  1669  	if err != nil {
  1670  		t.Fatalf("NewRepository() error = %v", err)
  1671  	}
  1672  	repo.PlainHTTP = true
  1673  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1674  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1675  	}
  1676  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1677  		return nil
  1678  	}); err != nil {
  1679  		t.Errorf("Repository.Referrers() error = %v", err)
  1680  	}
  1681  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1682  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1683  	}
  1684  
  1685  	// test force attempt tag schema
  1686  	// tag schema referrers not found, should be no error
  1687  	repo, err = NewRepository(uri.Host + "/test")
  1688  	if err != nil {
  1689  		t.Fatalf("NewRepository() error = %v", err)
  1690  	}
  1691  	repo.PlainHTTP = true
  1692  	repo.SetReferrersCapability(false)
  1693  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1694  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1695  	}
  1696  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1697  		return nil
  1698  	}); err != nil {
  1699  		t.Errorf("Repository.Referrers() error = %v", err)
  1700  	}
  1701  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1702  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1703  	}
  1704  }
  1705  
  1706  func TestRepository_Referrers_TagSchemaFallback_ContentType(t *testing.T) {
  1707  	manifest := []byte(`{"layers":[]}`)
  1708  	manifestDesc := ocispec.Descriptor{
  1709  		MediaType: ocispec.MediaTypeImageManifest,
  1710  		Digest:    digest.FromBytes(manifest),
  1711  		Size:      int64(len(manifest)),
  1712  	}
  1713  
  1714  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1715  		referrersUrl := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1716  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1717  		tagSchemaUrl := "/v2/test/manifests/" + referrersTag
  1718  		if r.URL.Path == referrersUrl {
  1719  			w.Header().Set("Content-Type", "application/json") // not an OCI image index
  1720  			w.WriteHeader(http.StatusOK)
  1721  			return
  1722  		}
  1723  		if r.Method == http.MethodGet ||
  1724  			r.URL.Path == tagSchemaUrl {
  1725  			w.WriteHeader(http.StatusNotFound)
  1726  			return
  1727  		}
  1728  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1729  		w.WriteHeader(http.StatusNotFound)
  1730  	}))
  1731  	defer ts.Close()
  1732  	uri, err := url.Parse(ts.URL)
  1733  	if err != nil {
  1734  		t.Fatalf("invalid test http server: %v", err)
  1735  	}
  1736  	ctx := context.Background()
  1737  
  1738  	// test auto detect
  1739  	// tag schema referrers not found, should be no error
  1740  	repo, err := NewRepository(uri.Host + "/test")
  1741  	if err != nil {
  1742  		t.Fatalf("NewRepository() error = %v", err)
  1743  	}
  1744  	repo.PlainHTTP = true
  1745  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1746  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1747  	}
  1748  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1749  		return nil
  1750  	}); err != nil {
  1751  		t.Errorf("Repository.Referrers() error = %v", err)
  1752  	}
  1753  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1754  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1755  	}
  1756  }
  1757  
  1758  func TestRepository_Referrers_BadRequest(t *testing.T) {
  1759  	manifest := []byte(`{"layers":[]}`)
  1760  	manifestDesc := ocispec.Descriptor{
  1761  		MediaType: ocispec.MediaTypeImageManifest,
  1762  		Digest:    digest.FromBytes(manifest),
  1763  		Size:      int64(len(manifest)),
  1764  	}
  1765  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1766  		referrersUrl := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1767  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1768  		tagSchemaUrl := "/v2/test/manifests/" + referrersTag
  1769  		if r.Method == http.MethodGet ||
  1770  			r.URL.Path == referrersUrl ||
  1771  			r.URL.Path == tagSchemaUrl {
  1772  			w.WriteHeader(http.StatusBadRequest)
  1773  			return
  1774  		}
  1775  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1776  		w.WriteHeader(http.StatusNotFound)
  1777  	}))
  1778  	defer ts.Close()
  1779  	uri, err := url.Parse(ts.URL)
  1780  	if err != nil {
  1781  		t.Fatalf("invalid test http server: %v", err)
  1782  	}
  1783  	ctx := context.Background()
  1784  
  1785  	// test auto detect
  1786  	// Referrers returns error
  1787  	repo, err := NewRepository(uri.Host + "/test")
  1788  	if err != nil {
  1789  		t.Fatalf("NewRepository() error = %v", err)
  1790  	}
  1791  	repo.PlainHTTP = true
  1792  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1793  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1794  	}
  1795  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1796  		return nil
  1797  	}); err == nil {
  1798  		t.Errorf("Repository.Referrers() error = nil, wantErr %v", true)
  1799  	}
  1800  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1801  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1802  	}
  1803  
  1804  	// test force attempt Referrers
  1805  	// Referrers returns error
  1806  	repo, err = NewRepository(uri.Host + "/test")
  1807  	if err != nil {
  1808  		t.Fatalf("NewRepository() error = %v", err)
  1809  	}
  1810  	repo.PlainHTTP = true
  1811  	repo.SetReferrersCapability(true)
  1812  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1813  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1814  	}
  1815  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1816  		return nil
  1817  	}); err == nil {
  1818  		t.Errorf("Repository.Referrers() error = nil, wantErr %v", true)
  1819  	}
  1820  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1821  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1822  	}
  1823  
  1824  	// test force attempt tag schema
  1825  	// Referrers returns error
  1826  	repo, err = NewRepository(uri.Host + "/test")
  1827  	if err != nil {
  1828  		t.Fatalf("NewRepository() error = %v", err)
  1829  	}
  1830  	repo.PlainHTTP = true
  1831  	repo.SetReferrersCapability(false)
  1832  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1833  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1834  	}
  1835  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1836  		return nil
  1837  	}); err == nil {
  1838  		t.Errorf("Repository.Referrers() error = nil, wantErr %v", true)
  1839  	}
  1840  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1841  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1842  	}
  1843  }
  1844  
  1845  func TestRepository_Referrers_RepositoryNotFound(t *testing.T) {
  1846  	manifest := []byte(`{"layers":[]}`)
  1847  	manifestDesc := ocispec.Descriptor{
  1848  		MediaType: ocispec.MediaTypeImageManifest,
  1849  		Digest:    digest.FromBytes(manifest),
  1850  		Size:      int64(len(manifest)),
  1851  	}
  1852  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1853  		referrersUrl := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1854  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1855  		tagSchemaUrl := "/v2/test/manifests/" + referrersTag
  1856  		if r.Method == http.MethodGet &&
  1857  			(r.URL.Path == referrersUrl || r.URL.Path == tagSchemaUrl) {
  1858  			w.WriteHeader(http.StatusNotFound)
  1859  			w.Write([]byte(`{ "errors": [ { "code": "NAME_UNKNOWN", "message": "repository name not known to registry" } ] }`))
  1860  			return
  1861  		}
  1862  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1863  		w.WriteHeader(http.StatusNotFound)
  1864  	}))
  1865  	defer ts.Close()
  1866  	uri, err := url.Parse(ts.URL)
  1867  	if err != nil {
  1868  		t.Fatalf("invalid test http server: %v", err)
  1869  	}
  1870  	ctx := context.Background()
  1871  
  1872  	// test auto detect
  1873  	// repository not found, should return error
  1874  	repo, err := NewRepository(uri.Host + "/test")
  1875  	if err != nil {
  1876  		t.Fatalf("NewRepository() error = %v", err)
  1877  	}
  1878  	repo.PlainHTTP = true
  1879  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1880  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1881  	}
  1882  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1883  		return nil
  1884  	}); err == nil {
  1885  		t.Errorf("Repository.Referrers() error = %v, wantErr %v", err, true)
  1886  	}
  1887  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1888  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1889  	}
  1890  
  1891  	// test force attempt Referrers
  1892  	// repository not found, should return error
  1893  	repo, err = NewRepository(uri.Host + "/test")
  1894  	if err != nil {
  1895  		t.Fatalf("NewRepository() error = %v", err)
  1896  	}
  1897  	repo.PlainHTTP = true
  1898  	repo.SetReferrersCapability(true)
  1899  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1900  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1901  	}
  1902  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1903  		return nil
  1904  	}); err == nil {
  1905  		t.Errorf("Repository.Referrers() error = %v, wantErr %v", err, true)
  1906  	}
  1907  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1908  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1909  	}
  1910  
  1911  	// test force attempt tag schema
  1912  	// repository not found, but should not return error
  1913  	repo, err = NewRepository(uri.Host + "/test")
  1914  	if err != nil {
  1915  		t.Fatalf("NewRepository() error = %v", err)
  1916  	}
  1917  	repo.PlainHTTP = true
  1918  	repo.SetReferrersCapability(false)
  1919  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1920  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1921  	}
  1922  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1923  		return nil
  1924  	}); err != nil {
  1925  		t.Errorf("Repository.Referrers() error = %v, wantErr %v", err, nil)
  1926  	}
  1927  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1928  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1929  	}
  1930  }
  1931  
  1932  func TestRepository_Referrers_ServerFiltering(t *testing.T) {
  1933  	manifest := []byte(`{"layers":[]}`)
  1934  	manifestDesc := ocispec.Descriptor{
  1935  		MediaType: ocispec.MediaTypeImageManifest,
  1936  		Digest:    digest.FromBytes(manifest),
  1937  		Size:      int64(len(manifest)),
  1938  	}
  1939  	referrerSet := [][]ocispec.Descriptor{
  1940  		{
  1941  			{
  1942  				MediaType:    spec.MediaTypeArtifactManifest,
  1943  				Size:         1,
  1944  				Digest:       digest.FromString("1"),
  1945  				ArtifactType: "application/vnd.test",
  1946  			},
  1947  			{
  1948  				MediaType:    spec.MediaTypeArtifactManifest,
  1949  				Size:         2,
  1950  				Digest:       digest.FromString("2"),
  1951  				ArtifactType: "application/vnd.test",
  1952  			},
  1953  		},
  1954  		{
  1955  			{
  1956  				MediaType:    spec.MediaTypeArtifactManifest,
  1957  				Size:         3,
  1958  				Digest:       digest.FromString("3"),
  1959  				ArtifactType: "application/vnd.test",
  1960  			},
  1961  			{
  1962  				MediaType:    spec.MediaTypeArtifactManifest,
  1963  				Size:         4,
  1964  				Digest:       digest.FromString("4"),
  1965  				ArtifactType: "application/vnd.test",
  1966  			},
  1967  		},
  1968  		{
  1969  			{
  1970  				MediaType:    spec.MediaTypeArtifactManifest,
  1971  				Size:         5,
  1972  				Digest:       digest.FromString("5"),
  1973  				ArtifactType: "application/vnd.test",
  1974  			},
  1975  		},
  1976  	}
  1977  
  1978  	// Test with filter annotations only
  1979  	var ts *httptest.Server
  1980  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1981  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1982  		queryParams, err := url.ParseQuery(r.URL.RawQuery)
  1983  		if err != nil {
  1984  			t.Fatal("failed to parse url query")
  1985  		}
  1986  		if r.Method != http.MethodGet ||
  1987  			r.URL.Path != path ||
  1988  			reflect.DeepEqual(queryParams["artifactType"], []string{"application%2Fvnd.test"}) {
  1989  			t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1990  			w.WriteHeader(http.StatusNotFound)
  1991  			return
  1992  		}
  1993  		q := r.URL.Query()
  1994  		n, err := strconv.Atoi(q.Get("n"))
  1995  		if err != nil || n != 2 {
  1996  			t.Errorf("bad page size: %s", q.Get("n"))
  1997  			w.WriteHeader(http.StatusBadRequest)
  1998  			return
  1999  		}
  2000  		var referrers []ocispec.Descriptor
  2001  		switch q.Get("test") {
  2002  		case "foo":
  2003  			referrers = referrerSet[1]
  2004  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  2005  		case "bar":
  2006  			referrers = referrerSet[2]
  2007  		default:
  2008  			referrers = referrerSet[0]
  2009  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  2010  		}
  2011  		result := ocispec.Index{
  2012  			Versioned: specs.Versioned{
  2013  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  2014  			},
  2015  			MediaType: ocispec.MediaTypeImageIndex,
  2016  			Manifests: referrers,
  2017  			// set filter annotations
  2018  			Annotations: map[string]string{
  2019  				spec.AnnotationReferrersFiltersApplied: "artifactType",
  2020  			},
  2021  		}
  2022  		w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  2023  		if err := json.NewEncoder(w).Encode(result); err != nil {
  2024  			t.Errorf("failed to write response: %v", err)
  2025  		}
  2026  	}))
  2027  	defer ts.Close()
  2028  	uri, err := url.Parse(ts.URL)
  2029  	if err != nil {
  2030  		t.Fatalf("invalid test http server: %v", err)
  2031  	}
  2032  
  2033  	repo, err := NewRepository(uri.Host + "/test")
  2034  	if err != nil {
  2035  		t.Fatalf("NewRepository() error = %v", err)
  2036  	}
  2037  	repo.PlainHTTP = true
  2038  	repo.ReferrerListPageSize = 2
  2039  
  2040  	ctx := context.Background()
  2041  	index := 0
  2042  	if err := repo.Referrers(ctx, manifestDesc, "application/vnd.test", func(got []ocispec.Descriptor) error {
  2043  		if index >= len(referrerSet) {
  2044  			t.Fatalf("out of index bound: %d", index)
  2045  		}
  2046  		referrers := referrerSet[index]
  2047  		index++
  2048  		if !reflect.DeepEqual(got, referrers) {
  2049  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  2050  		}
  2051  		return nil
  2052  	}); err != nil {
  2053  		t.Errorf("Repository.Referrers() error = %v", err)
  2054  	}
  2055  	if index != len(referrerSet) {
  2056  		t.Errorf("fn invoked %d time(s), want %d", index, len(referrerSet))
  2057  	}
  2058  
  2059  	// Test with filter header only
  2060  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2061  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  2062  		queryParams, err := url.ParseQuery(r.URL.RawQuery)
  2063  		if err != nil {
  2064  			t.Fatal("failed to parse url query")
  2065  		}
  2066  		if r.Method != http.MethodGet ||
  2067  			r.URL.Path != path ||
  2068  			reflect.DeepEqual(queryParams["artifactType"], []string{"application%2Fvnd.test"}) {
  2069  			t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  2070  			w.WriteHeader(http.StatusNotFound)
  2071  			return
  2072  		}
  2073  		q := r.URL.Query()
  2074  		n, err := strconv.Atoi(q.Get("n"))
  2075  		if err != nil || n != 2 {
  2076  			t.Errorf("bad page size: %s", q.Get("n"))
  2077  			w.WriteHeader(http.StatusBadRequest)
  2078  			return
  2079  		}
  2080  		var referrers []ocispec.Descriptor
  2081  		switch q.Get("test") {
  2082  		case "foo":
  2083  			referrers = referrerSet[1]
  2084  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  2085  		case "bar":
  2086  			referrers = referrerSet[2]
  2087  		default:
  2088  			referrers = referrerSet[0]
  2089  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  2090  		}
  2091  		result := ocispec.Index{
  2092  			Versioned: specs.Versioned{
  2093  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  2094  			},
  2095  			MediaType: ocispec.MediaTypeImageIndex,
  2096  			Manifests: referrers,
  2097  		}
  2098  		w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  2099  		// set filter header
  2100  		w.Header().Set("OCI-Filters-Applied", "artifactType")
  2101  		if err := json.NewEncoder(w).Encode(result); err != nil {
  2102  			t.Errorf("failed to write response: %v", err)
  2103  		}
  2104  	}))
  2105  	defer ts.Close()
  2106  	uri, err = url.Parse(ts.URL)
  2107  	if err != nil {
  2108  		t.Fatalf("invalid test http server: %v", err)
  2109  	}
  2110  
  2111  	repo, err = NewRepository(uri.Host + "/test")
  2112  	if err != nil {
  2113  		t.Fatalf("NewRepository() error = %v", err)
  2114  	}
  2115  	repo.PlainHTTP = true
  2116  	repo.ReferrerListPageSize = 2
  2117  
  2118  	ctx = context.Background()
  2119  	index = 0
  2120  	if err := repo.Referrers(ctx, manifestDesc, "application/vnd.test", func(got []ocispec.Descriptor) error {
  2121  		if index >= len(referrerSet) {
  2122  			t.Fatalf("out of index bound: %d", index)
  2123  		}
  2124  		referrers := referrerSet[index]
  2125  		index++
  2126  		if !reflect.DeepEqual(got, referrers) {
  2127  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  2128  		}
  2129  		return nil
  2130  	}); err != nil {
  2131  		t.Errorf("Repository.Referrers() error = %v", err)
  2132  	}
  2133  	if index != len(referrerSet) {
  2134  		t.Errorf("fn invoked %d time(s), want %d", index, len(referrerSet))
  2135  	}
  2136  
  2137  	// Test with both filter annotation and filter header
  2138  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2139  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  2140  		queryParams, err := url.ParseQuery(r.URL.RawQuery)
  2141  		if err != nil {
  2142  			t.Fatal("failed to parse url query")
  2143  		}
  2144  		if r.Method != http.MethodGet ||
  2145  			r.URL.Path != path ||
  2146  			reflect.DeepEqual(queryParams["artifactType"], []string{"application%2Fvnd.test"}) {
  2147  			t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  2148  			w.WriteHeader(http.StatusNotFound)
  2149  			return
  2150  		}
  2151  		q := r.URL.Query()
  2152  		n, err := strconv.Atoi(q.Get("n"))
  2153  		if err != nil || n != 2 {
  2154  			t.Errorf("bad page size: %s", q.Get("n"))
  2155  			w.WriteHeader(http.StatusBadRequest)
  2156  			return
  2157  		}
  2158  		var referrers []ocispec.Descriptor
  2159  		switch q.Get("test") {
  2160  		case "foo":
  2161  			referrers = referrerSet[1]
  2162  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  2163  		case "bar":
  2164  			referrers = referrerSet[2]
  2165  		default:
  2166  			referrers = referrerSet[0]
  2167  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  2168  		}
  2169  		result := ocispec.Index{
  2170  			Versioned: specs.Versioned{
  2171  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  2172  			},
  2173  			MediaType: ocispec.MediaTypeImageIndex,
  2174  			Manifests: referrers,
  2175  			// set filter annotation
  2176  			Annotations: map[string]string{
  2177  				spec.AnnotationReferrersFiltersApplied: "artifactType",
  2178  			},
  2179  		}
  2180  		w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  2181  		// set filter header
  2182  		w.Header().Set("OCI-Filters-Applied", "artifactType")
  2183  		if err := json.NewEncoder(w).Encode(result); err != nil {
  2184  			t.Errorf("failed to write response: %v", err)
  2185  		}
  2186  	}))
  2187  	defer ts.Close()
  2188  	uri, err = url.Parse(ts.URL)
  2189  	if err != nil {
  2190  		t.Fatalf("invalid test http server: %v", err)
  2191  	}
  2192  
  2193  	repo, err = NewRepository(uri.Host + "/test")
  2194  	if err != nil {
  2195  		t.Fatalf("NewRepository() error = %v", err)
  2196  	}
  2197  	repo.PlainHTTP = true
  2198  	repo.ReferrerListPageSize = 2
  2199  
  2200  	ctx = context.Background()
  2201  	index = 0
  2202  	if err := repo.Referrers(ctx, manifestDesc, "application/vnd.test", func(got []ocispec.Descriptor) error {
  2203  		if index >= len(referrerSet) {
  2204  			t.Fatalf("out of index bound: %d", index)
  2205  		}
  2206  		referrers := referrerSet[index]
  2207  		index++
  2208  		if !reflect.DeepEqual(got, referrers) {
  2209  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  2210  		}
  2211  		return nil
  2212  	}); err != nil {
  2213  		t.Errorf("Repository.Referrers() error = %v", err)
  2214  	}
  2215  	if index != len(referrerSet) {
  2216  		t.Errorf("fn invoked %d time(s), want %d", index, len(referrerSet))
  2217  	}
  2218  }
  2219  
  2220  func TestRepository_Referrers_ClientFiltering(t *testing.T) {
  2221  	manifest := []byte(`{"layers":[]}`)
  2222  	manifestDesc := ocispec.Descriptor{
  2223  		MediaType: ocispec.MediaTypeImageManifest,
  2224  		Digest:    digest.FromBytes(manifest),
  2225  		Size:      int64(len(manifest)),
  2226  	}
  2227  	referrerSet := [][]ocispec.Descriptor{
  2228  		{
  2229  			{
  2230  				MediaType:    spec.MediaTypeArtifactManifest,
  2231  				Size:         1,
  2232  				Digest:       digest.FromString("1"),
  2233  				ArtifactType: "application/vnd.test",
  2234  			},
  2235  			{
  2236  				MediaType:    spec.MediaTypeArtifactManifest,
  2237  				Size:         2,
  2238  				Digest:       digest.FromString("2"),
  2239  				ArtifactType: "application/vnd.foo",
  2240  			},
  2241  		},
  2242  		{
  2243  			{
  2244  				MediaType:    spec.MediaTypeArtifactManifest,
  2245  				Size:         3,
  2246  				Digest:       digest.FromString("3"),
  2247  				ArtifactType: "application/vnd.test",
  2248  			},
  2249  			{
  2250  				MediaType:    spec.MediaTypeArtifactManifest,
  2251  				Size:         4,
  2252  				Digest:       digest.FromString("4"),
  2253  				ArtifactType: "application/vnd.bar",
  2254  			},
  2255  		},
  2256  		{
  2257  			{
  2258  				MediaType:    spec.MediaTypeArtifactManifest,
  2259  				Size:         5,
  2260  				Digest:       digest.FromString("5"),
  2261  				ArtifactType: "application/vnd.baz",
  2262  			},
  2263  		},
  2264  	}
  2265  	filteredReferrerSet := [][]ocispec.Descriptor{
  2266  		{
  2267  			{
  2268  				MediaType:    spec.MediaTypeArtifactManifest,
  2269  				Size:         1,
  2270  				Digest:       digest.FromString("1"),
  2271  				ArtifactType: "application/vnd.test",
  2272  			},
  2273  		},
  2274  		{
  2275  			{
  2276  				MediaType:    spec.MediaTypeArtifactManifest,
  2277  				Size:         3,
  2278  				Digest:       digest.FromString("3"),
  2279  				ArtifactType: "application/vnd.test",
  2280  			},
  2281  		},
  2282  	}
  2283  	var ts *httptest.Server
  2284  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2285  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  2286  		queryParams, err := url.ParseQuery(r.URL.RawQuery)
  2287  		if err != nil {
  2288  			t.Fatal("failed to parse url query")
  2289  		}
  2290  		if r.Method != http.MethodGet ||
  2291  			r.URL.Path != path ||
  2292  			reflect.DeepEqual(queryParams["artifactType"], []string{"application%2Fvnd.test"}) {
  2293  			t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  2294  			w.WriteHeader(http.StatusNotFound)
  2295  			return
  2296  		}
  2297  		q := r.URL.Query()
  2298  		n, err := strconv.Atoi(q.Get("n"))
  2299  		if err != nil || n != 2 {
  2300  			t.Errorf("bad page size: %s", q.Get("n"))
  2301  			w.WriteHeader(http.StatusBadRequest)
  2302  			return
  2303  		}
  2304  		var referrers []ocispec.Descriptor
  2305  		switch q.Get("test") {
  2306  		case "foo":
  2307  			referrers = referrerSet[1]
  2308  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  2309  		case "bar":
  2310  			referrers = referrerSet[2]
  2311  		default:
  2312  			referrers = referrerSet[0]
  2313  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  2314  		}
  2315  		result := ocispec.Index{
  2316  			Versioned: specs.Versioned{
  2317  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  2318  			},
  2319  			MediaType: ocispec.MediaTypeImageIndex,
  2320  			Manifests: referrers,
  2321  		}
  2322  		w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  2323  		if err := json.NewEncoder(w).Encode(result); err != nil {
  2324  			t.Errorf("failed to write response: %v", err)
  2325  		}
  2326  	}))
  2327  	defer ts.Close()
  2328  	uri, err := url.Parse(ts.URL)
  2329  	if err != nil {
  2330  		t.Fatalf("invalid test http server: %v", err)
  2331  	}
  2332  
  2333  	repo, err := NewRepository(uri.Host + "/test")
  2334  	if err != nil {
  2335  		t.Fatalf("NewRepository() error = %v", err)
  2336  	}
  2337  	repo.PlainHTTP = true
  2338  	repo.ReferrerListPageSize = 2
  2339  
  2340  	ctx := context.Background()
  2341  	index := 0
  2342  	if err := repo.Referrers(ctx, manifestDesc, "application/vnd.test", func(got []ocispec.Descriptor) error {
  2343  		if index >= len(filteredReferrerSet) {
  2344  			t.Fatalf("out of index bound: %d", index)
  2345  		}
  2346  		referrers := filteredReferrerSet[index]
  2347  		index++
  2348  		if !reflect.DeepEqual(got, referrers) {
  2349  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  2350  		}
  2351  		return nil
  2352  	}); err != nil {
  2353  		t.Errorf("Repository.Referrers() error = %v", err)
  2354  	}
  2355  	if index != len(filteredReferrerSet) {
  2356  		t.Errorf("fn invoked %d time(s), want %d", index, len(referrerSet))
  2357  	}
  2358  }
  2359  
  2360  func TestRepository_Referrers_TagSchemaFallback_ClientFiltering(t *testing.T) {
  2361  	manifest := []byte(`{"layers":[]}`)
  2362  	manifestDesc := ocispec.Descriptor{
  2363  		MediaType: ocispec.MediaTypeImageManifest,
  2364  		Digest:    digest.FromBytes(manifest),
  2365  		Size:      int64(len(manifest)),
  2366  	}
  2367  
  2368  	referrers := []ocispec.Descriptor{
  2369  		{
  2370  			MediaType:    spec.MediaTypeArtifactManifest,
  2371  			Size:         1,
  2372  			Digest:       digest.FromString("1"),
  2373  			ArtifactType: "application/vnd.test",
  2374  		},
  2375  		{
  2376  			MediaType:    spec.MediaTypeArtifactManifest,
  2377  			Size:         2,
  2378  			Digest:       digest.FromString("2"),
  2379  			ArtifactType: "application/vnd.foo",
  2380  		},
  2381  		{
  2382  			MediaType:    spec.MediaTypeArtifactManifest,
  2383  			Size:         3,
  2384  			Digest:       digest.FromString("3"),
  2385  			ArtifactType: "application/vnd.test",
  2386  		},
  2387  		{
  2388  			MediaType:    spec.MediaTypeArtifactManifest,
  2389  			Size:         4,
  2390  			Digest:       digest.FromString("4"),
  2391  			ArtifactType: "application/vnd.bar",
  2392  		},
  2393  		{
  2394  			MediaType:    spec.MediaTypeArtifactManifest,
  2395  			Size:         5,
  2396  			Digest:       digest.FromString("5"),
  2397  			ArtifactType: "application/vnd.baz",
  2398  		},
  2399  	}
  2400  	filteredReferrers := []ocispec.Descriptor{
  2401  		{
  2402  			MediaType:    spec.MediaTypeArtifactManifest,
  2403  			Size:         1,
  2404  			Digest:       digest.FromString("1"),
  2405  			ArtifactType: "application/vnd.test",
  2406  		},
  2407  		{
  2408  			MediaType:    spec.MediaTypeArtifactManifest,
  2409  			Size:         3,
  2410  			Digest:       digest.FromString("3"),
  2411  			ArtifactType: "application/vnd.test",
  2412  		},
  2413  	}
  2414  
  2415  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2416  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  2417  		path := "/v2/test/manifests/" + referrersTag
  2418  		if r.Method != http.MethodGet || r.URL.Path != path {
  2419  			if r.URL.Path != "/v2/test/referrers/"+manifestDesc.Digest.String() {
  2420  				t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  2421  			}
  2422  			w.WriteHeader(http.StatusNotFound)
  2423  			return
  2424  		}
  2425  
  2426  		result := ocispec.Index{
  2427  			Versioned: specs.Versioned{
  2428  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  2429  			},
  2430  			MediaType: ocispec.MediaTypeImageIndex,
  2431  			Manifests: referrers,
  2432  		}
  2433  		if err := json.NewEncoder(w).Encode(result); err != nil {
  2434  			t.Errorf("failed to write response: %v", err)
  2435  		}
  2436  	}))
  2437  	defer ts.Close()
  2438  	uri, err := url.Parse(ts.URL)
  2439  	if err != nil {
  2440  		t.Fatalf("invalid test http server: %v", err)
  2441  	}
  2442  
  2443  	repo, err := NewRepository(uri.Host + "/test")
  2444  	if err != nil {
  2445  		t.Fatalf("NewRepository() error = %v", err)
  2446  	}
  2447  	repo.PlainHTTP = true
  2448  
  2449  	ctx := context.Background()
  2450  	if err := repo.Referrers(ctx, manifestDesc, "application/vnd.test", func(got []ocispec.Descriptor) error {
  2451  		if !reflect.DeepEqual(got, filteredReferrers) {
  2452  			t.Errorf("Repository.Referrers() = %v, want %v", got, filteredReferrers)
  2453  		}
  2454  		return nil
  2455  	}); err != nil {
  2456  		t.Errorf("Repository.Referrers() error = %v", err)
  2457  	}
  2458  }
  2459  
  2460  func Test_BlobStore_Fetch(t *testing.T) {
  2461  	blob := []byte("hello world")
  2462  	blobDesc := ocispec.Descriptor{
  2463  		MediaType: "test",
  2464  		Digest:    digest.FromBytes(blob),
  2465  		Size:      int64(len(blob)),
  2466  	}
  2467  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2468  		if r.Method != http.MethodGet {
  2469  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2470  			w.WriteHeader(http.StatusMethodNotAllowed)
  2471  			return
  2472  		}
  2473  		switch r.URL.Path {
  2474  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2475  			w.Header().Set("Content-Type", "application/octet-stream")
  2476  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2477  			if _, err := w.Write(blob); err != nil {
  2478  				t.Errorf("failed to write %q: %v", r.URL, err)
  2479  			}
  2480  		default:
  2481  			w.WriteHeader(http.StatusNotFound)
  2482  		}
  2483  	}))
  2484  	defer ts.Close()
  2485  	uri, err := url.Parse(ts.URL)
  2486  	if err != nil {
  2487  		t.Fatalf("invalid test http server: %v", err)
  2488  	}
  2489  
  2490  	repo, err := NewRepository(uri.Host + "/test")
  2491  	if err != nil {
  2492  		t.Fatalf("NewRepository() error = %v", err)
  2493  	}
  2494  	repo.PlainHTTP = true
  2495  	store := repo.Blobs()
  2496  	ctx := context.Background()
  2497  
  2498  	rc, err := store.Fetch(ctx, blobDesc)
  2499  	if err != nil {
  2500  		t.Fatalf("Blobs.Fetch() error = %v", err)
  2501  	}
  2502  	buf := bytes.NewBuffer(nil)
  2503  	if _, err := buf.ReadFrom(rc); err != nil {
  2504  		t.Errorf("fail to read: %v", err)
  2505  	}
  2506  	if err := rc.Close(); err != nil {
  2507  		t.Errorf("fail to close: %v", err)
  2508  	}
  2509  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2510  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob)
  2511  	}
  2512  
  2513  	content := []byte("foobar")
  2514  	contentDesc := ocispec.Descriptor{
  2515  		MediaType: "test",
  2516  		Digest:    digest.FromBytes(content),
  2517  		Size:      int64(len(content)),
  2518  	}
  2519  	_, err = store.Fetch(ctx, contentDesc)
  2520  	if !errors.Is(err, errdef.ErrNotFound) {
  2521  		t.Errorf("Blobs.Fetch() error = %v, wantErr %v", err, errdef.ErrNotFound)
  2522  	}
  2523  }
  2524  
  2525  func Test_BlobStore_Fetch_Seek(t *testing.T) {
  2526  	blob := []byte("hello world")
  2527  	blobDesc := ocispec.Descriptor{
  2528  		MediaType: "test",
  2529  		Digest:    digest.FromBytes(blob),
  2530  		Size:      int64(len(blob)),
  2531  	}
  2532  	seekable := false
  2533  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2534  		if r.Method != http.MethodGet {
  2535  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2536  			w.WriteHeader(http.StatusMethodNotAllowed)
  2537  			return
  2538  		}
  2539  		switch r.URL.Path {
  2540  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2541  			w.Header().Set("Content-Type", "application/octet-stream")
  2542  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2543  			if seekable {
  2544  				w.Header().Set("Accept-Ranges", "bytes")
  2545  			}
  2546  			rangeHeader := r.Header.Get("Range")
  2547  			if !seekable || rangeHeader == "" {
  2548  				w.WriteHeader(http.StatusOK)
  2549  				if _, err := w.Write(blob); err != nil {
  2550  					t.Errorf("failed to write %q: %v", r.URL, err)
  2551  				}
  2552  				return
  2553  			}
  2554  			var start, end int
  2555  			_, err := fmt.Sscanf(rangeHeader, "bytes=%d-%d", &start, &end)
  2556  			if err != nil {
  2557  				t.Errorf("invalid range header: %s", rangeHeader)
  2558  				w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
  2559  				return
  2560  			}
  2561  			if start < 0 || start > end || start >= int(blobDesc.Size) {
  2562  				t.Errorf("invalid range: %s", rangeHeader)
  2563  				w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
  2564  				return
  2565  			}
  2566  			end++
  2567  			if end > int(blobDesc.Size) {
  2568  				end = int(blobDesc.Size)
  2569  			}
  2570  			w.WriteHeader(http.StatusPartialContent)
  2571  			if _, err := w.Write(blob[start:end]); err != nil {
  2572  				t.Errorf("failed to write %q: %v", r.URL, err)
  2573  			}
  2574  		default:
  2575  			w.WriteHeader(http.StatusNotFound)
  2576  		}
  2577  	}))
  2578  	defer ts.Close()
  2579  	uri, err := url.Parse(ts.URL)
  2580  	if err != nil {
  2581  		t.Fatalf("invalid test http server: %v", err)
  2582  	}
  2583  
  2584  	repo, err := NewRepository(uri.Host + "/test")
  2585  	if err != nil {
  2586  		t.Fatalf("NewRepository() error = %v", err)
  2587  	}
  2588  	repo.PlainHTTP = true
  2589  	store := repo.Blobs()
  2590  	ctx := context.Background()
  2591  
  2592  	rc, err := store.Fetch(ctx, blobDesc)
  2593  	if err != nil {
  2594  		t.Fatalf("Blobs.Fetch() error = %v", err)
  2595  	}
  2596  	if _, ok := rc.(io.Seeker); ok {
  2597  		t.Errorf("Blobs.Fetch() returns io.Seeker on non-seekable content")
  2598  	}
  2599  	buf := bytes.NewBuffer(nil)
  2600  	if _, err := buf.ReadFrom(rc); err != nil {
  2601  		t.Errorf("fail to read: %v", err)
  2602  	}
  2603  	if err := rc.Close(); err != nil {
  2604  		t.Errorf("fail to close: %v", err)
  2605  	}
  2606  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2607  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob)
  2608  	}
  2609  
  2610  	seekable = true
  2611  	rc, err = store.Fetch(ctx, blobDesc)
  2612  	if err != nil {
  2613  		t.Fatalf("Blobs.Fetch() error = %v", err)
  2614  	}
  2615  	s, ok := rc.(io.Seeker)
  2616  	if !ok {
  2617  		t.Fatalf("Blobs.Fetch() = %v, want io.Seeker", rc)
  2618  	}
  2619  	buf.Reset()
  2620  	if _, err := buf.ReadFrom(rc); err != nil {
  2621  		t.Errorf("fail to read: %v", err)
  2622  	}
  2623  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2624  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob)
  2625  	}
  2626  
  2627  	_, err = s.Seek(3, io.SeekStart)
  2628  	if err != nil {
  2629  		t.Errorf("fail to seek: %v", err)
  2630  	}
  2631  	buf.Reset()
  2632  	if _, err := buf.ReadFrom(rc); err != nil {
  2633  		t.Errorf("fail to read: %v", err)
  2634  	}
  2635  	if got := buf.Bytes(); !bytes.Equal(got, blob[3:]) {
  2636  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob[3:])
  2637  	}
  2638  
  2639  	if err := rc.Close(); err != nil {
  2640  		t.Errorf("fail to close: %v", err)
  2641  	}
  2642  }
  2643  
  2644  func Test_BlobStore_Fetch_ZeroSizedBlob(t *testing.T) {
  2645  	blob := []byte("")
  2646  	blobDesc := ocispec.Descriptor{
  2647  		MediaType: "test",
  2648  		Digest:    digest.FromBytes(blob),
  2649  		Size:      int64(len(blob)),
  2650  	}
  2651  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2652  		if r.Method != http.MethodGet {
  2653  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2654  			w.WriteHeader(http.StatusMethodNotAllowed)
  2655  			return
  2656  		}
  2657  
  2658  		switch r.URL.Path {
  2659  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2660  			if rangeHeader := r.Header.Get("Range"); rangeHeader != "" {
  2661  				t.Errorf("unexpected range header")
  2662  				w.WriteHeader(http.StatusBadRequest)
  2663  				return
  2664  			}
  2665  			w.Header().Set("Content-Type", "application/octet-stream")
  2666  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2667  		default:
  2668  			w.WriteHeader(http.StatusNotFound)
  2669  		}
  2670  	}))
  2671  	defer ts.Close()
  2672  	uri, err := url.Parse(ts.URL)
  2673  	if err != nil {
  2674  		t.Fatalf("invalid test http server: %v", err)
  2675  	}
  2676  
  2677  	repo, err := NewRepository(uri.Host + "/test")
  2678  	if err != nil {
  2679  		t.Fatalf("NewRepository() error = %v", err)
  2680  	}
  2681  	repo.PlainHTTP = true
  2682  	store := repo.Blobs()
  2683  	ctx := context.Background()
  2684  
  2685  	rc, err := store.Fetch(ctx, blobDesc)
  2686  	if err != nil {
  2687  		t.Fatalf("Blobs.Fetch() error = %v", err)
  2688  	}
  2689  	buf := bytes.NewBuffer(nil)
  2690  	if _, err := buf.ReadFrom(rc); err != nil {
  2691  		t.Errorf("fail to read: %v", err)
  2692  	}
  2693  	if err := rc.Close(); err != nil {
  2694  		t.Errorf("fail to close: %v", err)
  2695  	}
  2696  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2697  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob)
  2698  	}
  2699  }
  2700  
  2701  func Test_BlobStore_Push(t *testing.T) {
  2702  	blob := []byte("hello world")
  2703  	blobDesc := ocispec.Descriptor{
  2704  		MediaType: "test",
  2705  		Digest:    digest.FromBytes(blob),
  2706  		Size:      int64(len(blob)),
  2707  	}
  2708  	var gotBlob []byte
  2709  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
  2710  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2711  		switch {
  2712  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
  2713  			w.Header().Set("Location", "/v2/test/blobs/uploads/"+uuid)
  2714  			w.WriteHeader(http.StatusAccepted)
  2715  			return
  2716  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
  2717  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
  2718  				w.WriteHeader(http.StatusBadRequest)
  2719  				break
  2720  			}
  2721  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
  2722  				w.WriteHeader(http.StatusBadRequest)
  2723  				break
  2724  			}
  2725  			buf := bytes.NewBuffer(nil)
  2726  			if _, err := buf.ReadFrom(r.Body); err != nil {
  2727  				t.Errorf("fail to read: %v", err)
  2728  			}
  2729  			gotBlob = buf.Bytes()
  2730  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2731  			w.WriteHeader(http.StatusCreated)
  2732  			return
  2733  		default:
  2734  			w.WriteHeader(http.StatusForbidden)
  2735  		}
  2736  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2737  	}))
  2738  	defer ts.Close()
  2739  	uri, err := url.Parse(ts.URL)
  2740  	if err != nil {
  2741  		t.Fatalf("invalid test http server: %v", err)
  2742  	}
  2743  
  2744  	repo, err := NewRepository(uri.Host + "/test")
  2745  	if err != nil {
  2746  		t.Fatalf("NewRepository() error = %v", err)
  2747  	}
  2748  	repo.PlainHTTP = true
  2749  	store := repo.Blobs()
  2750  	ctx := context.Background()
  2751  
  2752  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  2753  	if err != nil {
  2754  		t.Fatalf("Blobs.Push() error = %v", err)
  2755  	}
  2756  	if !bytes.Equal(gotBlob, blob) {
  2757  		t.Errorf("Blobs.Push() = %v, want %v", gotBlob, blob)
  2758  	}
  2759  }
  2760  
  2761  func Test_BlobStore_Exists(t *testing.T) {
  2762  	blob := []byte("hello world")
  2763  	blobDesc := ocispec.Descriptor{
  2764  		MediaType: "test",
  2765  		Digest:    digest.FromBytes(blob),
  2766  		Size:      int64(len(blob)),
  2767  	}
  2768  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2769  		if r.Method != http.MethodHead {
  2770  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2771  			w.WriteHeader(http.StatusMethodNotAllowed)
  2772  			return
  2773  		}
  2774  		switch r.URL.Path {
  2775  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2776  			w.Header().Set("Content-Type", "application/octet-stream")
  2777  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2778  			w.Header().Set("Content-Length", strconv.Itoa(int(blobDesc.Size)))
  2779  		default:
  2780  			w.WriteHeader(http.StatusNotFound)
  2781  		}
  2782  	}))
  2783  	defer ts.Close()
  2784  	uri, err := url.Parse(ts.URL)
  2785  	if err != nil {
  2786  		t.Fatalf("invalid test http server: %v", err)
  2787  	}
  2788  
  2789  	repo, err := NewRepository(uri.Host + "/test")
  2790  	if err != nil {
  2791  		t.Fatalf("NewRepository() error = %v", err)
  2792  	}
  2793  	repo.PlainHTTP = true
  2794  	store := repo.Blobs()
  2795  	ctx := context.Background()
  2796  
  2797  	exists, err := store.Exists(ctx, blobDesc)
  2798  	if err != nil {
  2799  		t.Fatalf("Blobs.Exists() error = %v", err)
  2800  	}
  2801  	if !exists {
  2802  		t.Errorf("Blobs.Exists() = %v, want %v", exists, true)
  2803  	}
  2804  
  2805  	content := []byte("foobar")
  2806  	contentDesc := ocispec.Descriptor{
  2807  		MediaType: "test",
  2808  		Digest:    digest.FromBytes(content),
  2809  		Size:      int64(len(content)),
  2810  	}
  2811  	exists, err = store.Exists(ctx, contentDesc)
  2812  	if err != nil {
  2813  		t.Fatalf("Blobs.Exists() error = %v", err)
  2814  	}
  2815  	if exists {
  2816  		t.Errorf("Blobs.Exists() = %v, want %v", exists, false)
  2817  	}
  2818  }
  2819  
  2820  func Test_BlobStore_Delete(t *testing.T) {
  2821  	blob := []byte("hello world")
  2822  	blobDesc := ocispec.Descriptor{
  2823  		MediaType: "test",
  2824  		Digest:    digest.FromBytes(blob),
  2825  		Size:      int64(len(blob)),
  2826  	}
  2827  	blobDeleted := false
  2828  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2829  		if r.Method != http.MethodDelete {
  2830  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2831  			w.WriteHeader(http.StatusMethodNotAllowed)
  2832  			return
  2833  		}
  2834  		switch r.URL.Path {
  2835  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2836  			blobDeleted = true
  2837  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2838  			w.WriteHeader(http.StatusAccepted)
  2839  		default:
  2840  			w.WriteHeader(http.StatusNotFound)
  2841  		}
  2842  	}))
  2843  	defer ts.Close()
  2844  	uri, err := url.Parse(ts.URL)
  2845  	if err != nil {
  2846  		t.Fatalf("invalid test http server: %v", err)
  2847  	}
  2848  
  2849  	repo, err := NewRepository(uri.Host + "/test")
  2850  	if err != nil {
  2851  		t.Fatalf("NewRepository() error = %v", err)
  2852  	}
  2853  	repo.PlainHTTP = true
  2854  	store := repo.Blobs()
  2855  	ctx := context.Background()
  2856  
  2857  	err = store.Delete(ctx, blobDesc)
  2858  	if err != nil {
  2859  		t.Fatalf("Blobs.Delete() error = %v", err)
  2860  	}
  2861  	if !blobDeleted {
  2862  		t.Errorf("Blobs.Delete() = %v, want %v", blobDeleted, true)
  2863  	}
  2864  
  2865  	content := []byte("foobar")
  2866  	contentDesc := ocispec.Descriptor{
  2867  		MediaType: "test",
  2868  		Digest:    digest.FromBytes(content),
  2869  		Size:      int64(len(content)),
  2870  	}
  2871  	err = store.Delete(ctx, contentDesc)
  2872  	if !errors.Is(err, errdef.ErrNotFound) {
  2873  		t.Errorf("Blobs.Delete() error = %v, wantErr %v", err, errdef.ErrNotFound)
  2874  	}
  2875  }
  2876  
  2877  func Test_BlobStore_Resolve(t *testing.T) {
  2878  	blob := []byte("hello world")
  2879  	blobDesc := ocispec.Descriptor{
  2880  		MediaType: "test",
  2881  		Digest:    digest.FromBytes(blob),
  2882  		Size:      int64(len(blob)),
  2883  	}
  2884  	ref := "foobar"
  2885  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2886  		if r.Method != http.MethodHead {
  2887  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2888  			w.WriteHeader(http.StatusMethodNotAllowed)
  2889  			return
  2890  		}
  2891  		switch r.URL.Path {
  2892  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2893  			w.Header().Set("Content-Type", "application/octet-stream")
  2894  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2895  			w.Header().Set("Content-Length", strconv.Itoa(int(blobDesc.Size)))
  2896  		default:
  2897  			w.WriteHeader(http.StatusNotFound)
  2898  		}
  2899  	}))
  2900  	defer ts.Close()
  2901  	uri, err := url.Parse(ts.URL)
  2902  	if err != nil {
  2903  		t.Fatalf("invalid test http server: %v", err)
  2904  	}
  2905  
  2906  	repoName := uri.Host + "/test"
  2907  	repo, err := NewRepository(repoName)
  2908  	if err != nil {
  2909  		t.Fatalf("NewRepository() error = %v", err)
  2910  	}
  2911  	repo.PlainHTTP = true
  2912  	store := repo.Blobs()
  2913  	ctx := context.Background()
  2914  
  2915  	got, err := store.Resolve(ctx, blobDesc.Digest.String())
  2916  	if err != nil {
  2917  		t.Fatalf("Blobs.Resolve() error = %v", err)
  2918  	}
  2919  	if got.Digest != blobDesc.Digest || got.Size != blobDesc.Size {
  2920  		t.Errorf("Blobs.Resolve() = %v, want %v", got, blobDesc)
  2921  	}
  2922  
  2923  	_, err = store.Resolve(ctx, ref)
  2924  	if !errors.Is(err, digest.ErrDigestInvalidFormat) {
  2925  		t.Errorf("Blobs.Resolve() error = %v, wantErr %v", err, digest.ErrDigestInvalidFormat)
  2926  	}
  2927  
  2928  	fqdnRef := repoName + "@" + blobDesc.Digest.String()
  2929  	got, err = store.Resolve(ctx, fqdnRef)
  2930  	if err != nil {
  2931  		t.Fatalf("Blobs.Resolve() error = %v", err)
  2932  	}
  2933  	if got.Digest != blobDesc.Digest || got.Size != blobDesc.Size {
  2934  		t.Errorf("Blobs.Resolve() = %v, want %v", got, blobDesc)
  2935  	}
  2936  
  2937  	content := []byte("foobar")
  2938  	contentDesc := ocispec.Descriptor{
  2939  		MediaType: "test",
  2940  		Digest:    digest.FromBytes(content),
  2941  		Size:      int64(len(content)),
  2942  	}
  2943  	_, err = store.Resolve(ctx, contentDesc.Digest.String())
  2944  	if !errors.Is(err, errdef.ErrNotFound) {
  2945  		t.Errorf("Blobs.Resolve() error = %v, wantErr %v", err, errdef.ErrNotFound)
  2946  	}
  2947  }
  2948  
  2949  func Test_BlobStore_FetchReference(t *testing.T) {
  2950  	blob := []byte("hello world")
  2951  	blobDesc := ocispec.Descriptor{
  2952  		MediaType: "test",
  2953  		Digest:    digest.FromBytes(blob),
  2954  		Size:      int64(len(blob)),
  2955  	}
  2956  	ref := "foobar"
  2957  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2958  		if r.Method != http.MethodGet {
  2959  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2960  			w.WriteHeader(http.StatusMethodNotAllowed)
  2961  			return
  2962  		}
  2963  		switch r.URL.Path {
  2964  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2965  			w.Header().Set("Content-Type", "application/octet-stream")
  2966  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2967  			if _, err := w.Write(blob); err != nil {
  2968  				t.Errorf("failed to write %q: %v", r.URL, err)
  2969  			}
  2970  		default:
  2971  			w.WriteHeader(http.StatusNotFound)
  2972  		}
  2973  	}))
  2974  	defer ts.Close()
  2975  	uri, err := url.Parse(ts.URL)
  2976  	if err != nil {
  2977  		t.Fatalf("invalid test http server: %v", err)
  2978  	}
  2979  
  2980  	repoName := uri.Host + "/test"
  2981  	repo, err := NewRepository(repoName)
  2982  	if err != nil {
  2983  		t.Fatalf("NewRepository() error = %v", err)
  2984  	}
  2985  	repo.PlainHTTP = true
  2986  	store := repo.Blobs()
  2987  	ctx := context.Background()
  2988  
  2989  	// test with digest
  2990  	gotDesc, rc, err := store.FetchReference(ctx, blobDesc.Digest.String())
  2991  	if err != nil {
  2992  		t.Fatalf("Blobs.FetchReference() error = %v", err)
  2993  	}
  2994  	if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size {
  2995  		t.Errorf("Blobs.FetchReference() = %v, want %v", gotDesc, blobDesc)
  2996  	}
  2997  	buf := bytes.NewBuffer(nil)
  2998  	if _, err := buf.ReadFrom(rc); err != nil {
  2999  		t.Errorf("fail to read: %v", err)
  3000  	}
  3001  	if err := rc.Close(); err != nil {
  3002  		t.Errorf("fail to close: %v", err)
  3003  	}
  3004  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  3005  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob)
  3006  	}
  3007  
  3008  	// test with non-digest reference
  3009  	_, _, err = store.FetchReference(ctx, ref)
  3010  	if !errors.Is(err, digest.ErrDigestInvalidFormat) {
  3011  		t.Errorf("Blobs.FetchReference() error = %v, wantErr %v", err, digest.ErrDigestInvalidFormat)
  3012  	}
  3013  
  3014  	// test with FQDN reference
  3015  	fqdnRef := repoName + "@" + blobDesc.Digest.String()
  3016  	gotDesc, rc, err = store.FetchReference(ctx, fqdnRef)
  3017  	if err != nil {
  3018  		t.Fatalf("Blobs.FetchReference() error = %v", err)
  3019  	}
  3020  	if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size {
  3021  		t.Errorf("Blobs.FetchReference() = %v, want %v", gotDesc, blobDesc)
  3022  	}
  3023  	buf.Reset()
  3024  	if _, err := buf.ReadFrom(rc); err != nil {
  3025  		t.Errorf("fail to read: %v", err)
  3026  	}
  3027  	if err := rc.Close(); err != nil {
  3028  		t.Errorf("fail to close: %v", err)
  3029  	}
  3030  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  3031  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob)
  3032  	}
  3033  
  3034  	content := []byte("foobar")
  3035  	contentDesc := ocispec.Descriptor{
  3036  		MediaType: "test",
  3037  		Digest:    digest.FromBytes(content),
  3038  		Size:      int64(len(content)),
  3039  	}
  3040  
  3041  	// test with other digest
  3042  	_, _, err = store.FetchReference(ctx, contentDesc.Digest.String())
  3043  	if !errors.Is(err, errdef.ErrNotFound) {
  3044  		t.Errorf("Blobs.FetchReference() error = %v, wantErr %v", err, errdef.ErrNotFound)
  3045  	}
  3046  }
  3047  
  3048  func Test_BlobStore_FetchReference_Seek(t *testing.T) {
  3049  	blob := []byte("hello world")
  3050  	blobDesc := ocispec.Descriptor{
  3051  		MediaType: "test",
  3052  		Digest:    digest.FromBytes(blob),
  3053  		Size:      int64(len(blob)),
  3054  	}
  3055  	seekable := false
  3056  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3057  		if r.Method != http.MethodGet {
  3058  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3059  			w.WriteHeader(http.StatusMethodNotAllowed)
  3060  			return
  3061  		}
  3062  		switch r.URL.Path {
  3063  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  3064  			w.Header().Set("Content-Type", "application/octet-stream")
  3065  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  3066  			if seekable {
  3067  				w.Header().Set("Accept-Ranges", "bytes")
  3068  			}
  3069  			rangeHeader := r.Header.Get("Range")
  3070  			if !seekable || rangeHeader == "" {
  3071  				w.WriteHeader(http.StatusOK)
  3072  				if _, err := w.Write(blob); err != nil {
  3073  					t.Errorf("failed to write %q: %v", r.URL, err)
  3074  				}
  3075  				return
  3076  			}
  3077  			var start int
  3078  			_, err := fmt.Sscanf(rangeHeader, "bytes=%d-", &start)
  3079  			if err != nil {
  3080  				t.Errorf("invalid range header: %s", rangeHeader)
  3081  				w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
  3082  				return
  3083  			}
  3084  			if start < 0 || start >= int(blobDesc.Size) {
  3085  				t.Errorf("invalid range: %s", rangeHeader)
  3086  				w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
  3087  				return
  3088  			}
  3089  
  3090  			w.WriteHeader(http.StatusPartialContent)
  3091  			if _, err := w.Write(blob[start:]); err != nil {
  3092  				t.Errorf("failed to write %q: %v", r.URL, err)
  3093  			}
  3094  		default:
  3095  			w.WriteHeader(http.StatusNotFound)
  3096  		}
  3097  	}))
  3098  	defer ts.Close()
  3099  	uri, err := url.Parse(ts.URL)
  3100  	if err != nil {
  3101  		t.Fatalf("invalid test http server: %v", err)
  3102  	}
  3103  
  3104  	repo, err := NewRepository(uri.Host + "/test")
  3105  	if err != nil {
  3106  		t.Fatalf("NewRepository() error = %v", err)
  3107  	}
  3108  	repo.PlainHTTP = true
  3109  	store := repo.Blobs()
  3110  	ctx := context.Background()
  3111  
  3112  	// test non-seekable content
  3113  	gotDesc, rc, err := store.FetchReference(ctx, blobDesc.Digest.String())
  3114  	if err != nil {
  3115  		t.Fatalf("Blobs.FetchReference() error = %v", err)
  3116  	}
  3117  	if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size {
  3118  		t.Errorf("Blobs.FetchReference() = %v, want %v", gotDesc, blobDesc)
  3119  	}
  3120  	if _, ok := rc.(io.Seeker); ok {
  3121  		t.Errorf("Blobs.FetchReference() returns io.Seeker on non-seekable content")
  3122  	}
  3123  	buf := bytes.NewBuffer(nil)
  3124  	if _, err := buf.ReadFrom(rc); err != nil {
  3125  		t.Errorf("fail to read: %v", err)
  3126  	}
  3127  	if err := rc.Close(); err != nil {
  3128  		t.Errorf("fail to close: %v", err)
  3129  	}
  3130  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  3131  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob)
  3132  	}
  3133  
  3134  	// test seekable content
  3135  	seekable = true
  3136  	gotDesc, rc, err = store.FetchReference(ctx, blobDesc.Digest.String())
  3137  	if err != nil {
  3138  		t.Fatalf("Blobs.FetchReference() error = %v", err)
  3139  	}
  3140  	if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size {
  3141  		t.Errorf("Blobs.FetchReference() = %v, want %v", gotDesc, blobDesc)
  3142  	}
  3143  	s, ok := rc.(io.Seeker)
  3144  	if !ok {
  3145  		t.Fatalf("Blobs.FetchReference() = %v, want io.Seeker", rc)
  3146  	}
  3147  	buf.Reset()
  3148  	if _, err := buf.ReadFrom(rc); err != nil {
  3149  		t.Errorf("fail to read: %v", err)
  3150  	}
  3151  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  3152  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob)
  3153  	}
  3154  
  3155  	_, err = s.Seek(3, io.SeekStart)
  3156  	if err != nil {
  3157  		t.Errorf("fail to seek: %v", err)
  3158  	}
  3159  	buf.Reset()
  3160  	if _, err := buf.ReadFrom(rc); err != nil {
  3161  		t.Errorf("fail to read: %v", err)
  3162  	}
  3163  	if got := buf.Bytes(); !bytes.Equal(got, blob[3:]) {
  3164  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob[3:])
  3165  	}
  3166  
  3167  	if err := rc.Close(); err != nil {
  3168  		t.Errorf("fail to close: %v", err)
  3169  	}
  3170  }
  3171  
  3172  func Test_generateBlobDescriptorWithVariousDockerContentDigestHeaders(t *testing.T) {
  3173  	reference := registry.Reference{
  3174  		Registry:   "eastern.haan.com",
  3175  		Reference:  "<calculate>",
  3176  		Repository: "from25to220ce",
  3177  	}
  3178  	tests := getTestIOStructMapForGetDescriptorClass()
  3179  	for testName, dcdIOStruct := range tests {
  3180  		if dcdIOStruct.isTag {
  3181  			continue
  3182  		}
  3183  
  3184  		for i, method := range []string{http.MethodGet, http.MethodHead} {
  3185  			reference.Reference = dcdIOStruct.clientSuppliedReference
  3186  
  3187  			resp := http.Response{
  3188  				Header: http.Header{
  3189  					"Content-Type":            []string{"application/vnd.docker.distribution.manifest.v2+json"},
  3190  					headerDockerContentDigest: []string{dcdIOStruct.serverCalculatedDigest.String()},
  3191  				},
  3192  			}
  3193  			if method == http.MethodGet {
  3194  				resp.Body = io.NopCloser(bytes.NewBufferString(theAmazingBanClan))
  3195  			}
  3196  			resp.Request = &http.Request{
  3197  				Method: method,
  3198  			}
  3199  
  3200  			var err error
  3201  			var d digest.Digest
  3202  			if d, err = reference.Digest(); err != nil {
  3203  				t.Errorf(
  3204  					"[Blob.%v] %v; got digest from a tag reference unexpectedly",
  3205  					method, testName,
  3206  				)
  3207  			}
  3208  
  3209  			errExpected := []bool{dcdIOStruct.errExpectedOnGET, dcdIOStruct.errExpectedOnHEAD}[i]
  3210  			if len(d) == 0 {
  3211  				// To avoid an otherwise impossible scenario in the tested code
  3212  				// path, we set d so that verifyContentDigest does not break.
  3213  				d = dcdIOStruct.serverCalculatedDigest
  3214  			}
  3215  			_, err = generateBlobDescriptor(&resp, d)
  3216  			if !errExpected && err != nil {
  3217  				t.Errorf(
  3218  					"[Blob.%v] %v; expected no error for request, but got err: %v",
  3219  					method, testName, err,
  3220  				)
  3221  			} else if errExpected && err == nil {
  3222  				t.Errorf(
  3223  					"[Blob.%v] %v; expected an error for request, but got none",
  3224  					method, testName,
  3225  				)
  3226  			}
  3227  		}
  3228  	}
  3229  }
  3230  
  3231  func TestManifestStoreInterface(t *testing.T) {
  3232  	var ms interface{} = &manifestStore{}
  3233  	if _, ok := ms.(interfaces.ReferenceParser); !ok {
  3234  		t.Error("&manifestStore{} does not conform interfaces.ReferenceParser")
  3235  	}
  3236  }
  3237  
  3238  func TestRepositoryMounterInterface(t *testing.T) {
  3239  	var r interface{} = &Repository{}
  3240  	if _, ok := r.(registry.Mounter); !ok {
  3241  		t.Error("&Repository{} does not conform to registry.Mounter")
  3242  	}
  3243  }
  3244  
  3245  func Test_ManifestStore_Fetch(t *testing.T) {
  3246  	manifest := []byte(`{"layers":[]}`)
  3247  	manifestDesc := ocispec.Descriptor{
  3248  		MediaType: ocispec.MediaTypeImageManifest,
  3249  		Digest:    digest.FromBytes(manifest),
  3250  		Size:      int64(len(manifest)),
  3251  	}
  3252  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3253  		if r.Method != http.MethodGet {
  3254  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3255  			w.WriteHeader(http.StatusMethodNotAllowed)
  3256  			return
  3257  		}
  3258  		switch r.URL.Path {
  3259  		case "/v2/test/manifests/" + manifestDesc.Digest.String():
  3260  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  3261  				t.Errorf("manifest not convertable: %s", accept)
  3262  				w.WriteHeader(http.StatusBadRequest)
  3263  				return
  3264  			}
  3265  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  3266  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3267  			if _, err := w.Write(manifest); err != nil {
  3268  				t.Errorf("failed to write %q: %v", r.URL, err)
  3269  			}
  3270  		default:
  3271  			w.WriteHeader(http.StatusNotFound)
  3272  		}
  3273  	}))
  3274  	defer ts.Close()
  3275  	uri, err := url.Parse(ts.URL)
  3276  	if err != nil {
  3277  		t.Fatalf("invalid test http server: %v", err)
  3278  	}
  3279  
  3280  	repo, err := NewRepository(uri.Host + "/test")
  3281  	if err != nil {
  3282  		t.Fatalf("NewRepository() error = %v", err)
  3283  	}
  3284  	repo.PlainHTTP = true
  3285  	store := repo.Manifests()
  3286  	ctx := context.Background()
  3287  
  3288  	rc, err := store.Fetch(ctx, manifestDesc)
  3289  	if err != nil {
  3290  		t.Fatalf("Manifests.Fetch() error = %v", err)
  3291  	}
  3292  	buf := bytes.NewBuffer(nil)
  3293  	if _, err := buf.ReadFrom(rc); err != nil {
  3294  		t.Errorf("fail to read: %v", err)
  3295  	}
  3296  	if err := rc.Close(); err != nil {
  3297  		t.Errorf("fail to close: %v", err)
  3298  	}
  3299  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  3300  		t.Errorf("Manifests.Fetch() = %v, want %v", got, manifest)
  3301  	}
  3302  
  3303  	content := []byte(`{"manifests":[]}`)
  3304  	contentDesc := ocispec.Descriptor{
  3305  		MediaType: ocispec.MediaTypeImageIndex,
  3306  		Digest:    digest.FromBytes(content),
  3307  		Size:      int64(len(content)),
  3308  	}
  3309  	_, err = store.Fetch(ctx, contentDesc)
  3310  	if !errors.Is(err, errdef.ErrNotFound) {
  3311  		t.Errorf("Manifests.Fetch() error = %v, wantErr %v", err, errdef.ErrNotFound)
  3312  	}
  3313  }
  3314  
  3315  func Test_ManifestStore_Push(t *testing.T) {
  3316  	manifest := []byte(`{"layers":[]}`)
  3317  	manifestDesc := ocispec.Descriptor{
  3318  		MediaType: ocispec.MediaTypeImageManifest,
  3319  		Digest:    digest.FromBytes(manifest),
  3320  		Size:      int64(len(manifest)),
  3321  	}
  3322  	var gotManifest []byte
  3323  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3324  		switch {
  3325  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3326  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  3327  				w.WriteHeader(http.StatusBadRequest)
  3328  				break
  3329  			}
  3330  			buf := bytes.NewBuffer(nil)
  3331  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3332  				t.Errorf("fail to read: %v", err)
  3333  			}
  3334  			gotManifest = buf.Bytes()
  3335  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3336  			w.WriteHeader(http.StatusCreated)
  3337  			return
  3338  		default:
  3339  			w.WriteHeader(http.StatusForbidden)
  3340  		}
  3341  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3342  	}))
  3343  	defer ts.Close()
  3344  	uri, err := url.Parse(ts.URL)
  3345  	if err != nil {
  3346  		t.Fatalf("invalid test http server: %v", err)
  3347  	}
  3348  
  3349  	repo, err := NewRepository(uri.Host + "/test")
  3350  	if err != nil {
  3351  		t.Fatalf("NewRepository() error = %v", err)
  3352  	}
  3353  	repo.PlainHTTP = true
  3354  	store := repo.Manifests()
  3355  	ctx := context.Background()
  3356  
  3357  	err = store.Push(ctx, manifestDesc, bytes.NewReader(manifest))
  3358  	if err != nil {
  3359  		t.Fatalf("Manifests.Push() error = %v", err)
  3360  	}
  3361  	if !bytes.Equal(gotManifest, manifest) {
  3362  		t.Errorf("Manifests.Push() = %v, want %v", gotManifest, manifest)
  3363  	}
  3364  }
  3365  
  3366  func Test_ManifestStore_Push_ReferrersAPIAvailable(t *testing.T) {
  3367  	// generate test content
  3368  	subject := []byte(`{"layers":[]}`)
  3369  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  3370  	artifact := spec.Artifact{
  3371  		MediaType: spec.MediaTypeArtifactManifest,
  3372  		Subject:   &subjectDesc,
  3373  	}
  3374  	artifactJSON, err := json.Marshal(artifact)
  3375  	if err != nil {
  3376  		t.Fatalf("failed to marshal manifest: %v", err)
  3377  	}
  3378  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  3379  	manifest := ocispec.Manifest{
  3380  		MediaType: ocispec.MediaTypeImageManifest,
  3381  		Subject:   &subjectDesc,
  3382  	}
  3383  	manifestJSON, err := json.Marshal(manifest)
  3384  	if err != nil {
  3385  		t.Fatalf("failed to marshal manifest: %v", err)
  3386  	}
  3387  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  3388  	index := ocispec.Index{
  3389  		MediaType: ocispec.MediaTypeImageIndex,
  3390  		Subject:   &subjectDesc,
  3391  	}
  3392  	indexJSON, err := json.Marshal(index)
  3393  	if err != nil {
  3394  		t.Fatalf("failed to marshal manifest: %v", err)
  3395  	}
  3396  	indexDesc := content.NewDescriptorFromBytes(manifest.MediaType, indexJSON)
  3397  
  3398  	var gotManifest []byte
  3399  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3400  		switch {
  3401  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3402  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  3403  				w.WriteHeader(http.StatusBadRequest)
  3404  				break
  3405  			}
  3406  			buf := bytes.NewBuffer(nil)
  3407  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3408  				t.Errorf("fail to read: %v", err)
  3409  			}
  3410  			gotManifest = buf.Bytes()
  3411  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  3412  			w.Header().Set("OCI-Subject", subjectDesc.Digest.String())
  3413  			w.WriteHeader(http.StatusCreated)
  3414  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3415  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  3416  				w.WriteHeader(http.StatusBadRequest)
  3417  				break
  3418  			}
  3419  			buf := bytes.NewBuffer(nil)
  3420  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3421  				t.Errorf("fail to read: %v", err)
  3422  			}
  3423  			gotManifest = buf.Bytes()
  3424  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3425  			w.Header().Set("OCI-Subject", subjectDesc.Digest.String())
  3426  			w.WriteHeader(http.StatusCreated)
  3427  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
  3428  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
  3429  				w.WriteHeader(http.StatusBadRequest)
  3430  				break
  3431  			}
  3432  			buf := bytes.NewBuffer(nil)
  3433  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3434  				t.Errorf("fail to read: %v", err)
  3435  			}
  3436  			gotManifest = buf.Bytes()
  3437  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  3438  			w.Header().Set("OCI-Subject", subjectDesc.Digest.String())
  3439  			w.WriteHeader(http.StatusCreated)
  3440  		default:
  3441  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3442  			w.WriteHeader(http.StatusNotFound)
  3443  		}
  3444  	}))
  3445  	defer ts.Close()
  3446  	uri, err := url.Parse(ts.URL)
  3447  	if err != nil {
  3448  		t.Fatalf("invalid test http server: %v", err)
  3449  	}
  3450  	ctx := context.Background()
  3451  
  3452  	// test pushing artifact with subject
  3453  	repo, err := NewRepository(uri.Host + "/test")
  3454  	if err != nil {
  3455  		t.Fatalf("NewRepository() error = %v", err)
  3456  	}
  3457  	repo.PlainHTTP = true
  3458  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3459  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3460  	}
  3461  	err = repo.Push(ctx, artifactDesc, bytes.NewReader(artifactJSON))
  3462  	if err != nil {
  3463  		t.Fatalf("Manifests.Push() error = %v", err)
  3464  	}
  3465  	if !bytes.Equal(gotManifest, artifactJSON) {
  3466  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  3467  	}
  3468  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  3469  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  3470  	}
  3471  
  3472  	// test pushing image manifest with subject
  3473  	repo, err = NewRepository(uri.Host + "/test")
  3474  	if err != nil {
  3475  		t.Fatalf("NewRepository() error = %v", err)
  3476  	}
  3477  	repo.PlainHTTP = true
  3478  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3479  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3480  	}
  3481  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  3482  	if err != nil {
  3483  		t.Fatalf("Manifests.Push() error = %v", err)
  3484  	}
  3485  	if !bytes.Equal(gotManifest, manifestJSON) {
  3486  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  3487  	}
  3488  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  3489  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  3490  	}
  3491  
  3492  	// test pushing image index with subject
  3493  	err = repo.Push(ctx, indexDesc, bytes.NewReader(indexJSON))
  3494  	if err != nil {
  3495  		t.Fatalf("Manifests.Push() error = %v", err)
  3496  	}
  3497  	if !bytes.Equal(gotManifest, indexJSON) {
  3498  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(indexJSON))
  3499  	}
  3500  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  3501  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  3502  	}
  3503  }
  3504  
  3505  func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) {
  3506  	// generate test content
  3507  	subject := []byte(`{"layers":[]}`)
  3508  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  3509  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  3510  	artifact := spec.Artifact{
  3511  		MediaType:    spec.MediaTypeArtifactManifest,
  3512  		Subject:      &subjectDesc,
  3513  		ArtifactType: "application/vnd.test",
  3514  		Annotations:  map[string]string{"foo": "bar"},
  3515  	}
  3516  	artifactJSON, err := json.Marshal(artifact)
  3517  	if err != nil {
  3518  		t.Fatalf("failed to marshal manifest: %v", err)
  3519  	}
  3520  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  3521  	artifactDesc.ArtifactType = artifact.ArtifactType
  3522  	artifactDesc.Annotations = artifact.Annotations
  3523  
  3524  	// test pushing artifact with subject, a referrer list should be created
  3525  	index_1 := ocispec.Index{
  3526  		Versioned: specs.Versioned{
  3527  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3528  		},
  3529  		MediaType: ocispec.MediaTypeImageIndex,
  3530  		Manifests: []ocispec.Descriptor{
  3531  			artifactDesc,
  3532  		},
  3533  	}
  3534  	indexJSON_1, err := json.Marshal(index_1)
  3535  	if err != nil {
  3536  		t.Fatalf("failed to marshal manifest: %v", err)
  3537  	}
  3538  	indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1)
  3539  	var gotManifest []byte
  3540  	var gotReferrerIndex []byte
  3541  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3542  		switch {
  3543  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3544  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  3545  				w.WriteHeader(http.StatusBadRequest)
  3546  				break
  3547  			}
  3548  			buf := bytes.NewBuffer(nil)
  3549  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3550  				t.Errorf("fail to read: %v", err)
  3551  			}
  3552  			gotManifest = buf.Bytes()
  3553  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  3554  			w.WriteHeader(http.StatusCreated)
  3555  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3556  			w.WriteHeader(http.StatusNotFound)
  3557  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3558  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  3559  				w.WriteHeader(http.StatusBadRequest)
  3560  				break
  3561  			}
  3562  			buf := bytes.NewBuffer(nil)
  3563  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3564  				t.Errorf("fail to read: %v", err)
  3565  			}
  3566  			gotReferrerIndex = buf.Bytes()
  3567  			w.Header().Set("Docker-Content-Digest", indexDesc_1.Digest.String())
  3568  			w.WriteHeader(http.StatusCreated)
  3569  		default:
  3570  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3571  			w.WriteHeader(http.StatusNotFound)
  3572  		}
  3573  	}))
  3574  	defer ts.Close()
  3575  	uri, err := url.Parse(ts.URL)
  3576  	if err != nil {
  3577  		t.Fatalf("invalid test http server: %v", err)
  3578  	}
  3579  
  3580  	ctx := context.Background()
  3581  	repo, err := NewRepository(uri.Host + "/test")
  3582  	if err != nil {
  3583  		t.Fatalf("NewRepository() error = %v", err)
  3584  	}
  3585  	repo.PlainHTTP = true
  3586  
  3587  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3588  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3589  	}
  3590  	err = repo.Push(ctx, artifactDesc, bytes.NewReader(artifactJSON))
  3591  	if err != nil {
  3592  		t.Fatalf("Manifests.Push() error = %v", err)
  3593  	}
  3594  	if !bytes.Equal(gotManifest, artifactJSON) {
  3595  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  3596  	}
  3597  	if !bytes.Equal(gotReferrerIndex, indexJSON_1) {
  3598  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1))
  3599  	}
  3600  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3601  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3602  	}
  3603  
  3604  	// test pushing artifact with subject when an old empty referrer list exists,
  3605  	// the referrer list should be updated
  3606  	emptyIndex := ocispec.Index{
  3607  		Versioned: specs.Versioned{
  3608  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3609  		},
  3610  		MediaType: ocispec.MediaTypeImageIndex,
  3611  	}
  3612  	emptyIndexJSON, err := json.Marshal(emptyIndex)
  3613  	if err != nil {
  3614  		t.Error("failed to marshal index", err)
  3615  	}
  3616  	emptyIndexDesc := content.NewDescriptorFromBytes(emptyIndex.MediaType, emptyIndexJSON)
  3617  	var indexDeleted bool
  3618  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3619  		switch {
  3620  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3621  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  3622  				w.WriteHeader(http.StatusBadRequest)
  3623  				break
  3624  			}
  3625  			buf := bytes.NewBuffer(nil)
  3626  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3627  				t.Errorf("fail to read: %v", err)
  3628  			}
  3629  			gotManifest = buf.Bytes()
  3630  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  3631  			w.WriteHeader(http.StatusCreated)
  3632  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3633  			w.Write(emptyIndexJSON)
  3634  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3635  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  3636  				w.WriteHeader(http.StatusBadRequest)
  3637  				break
  3638  			}
  3639  			buf := bytes.NewBuffer(nil)
  3640  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3641  				t.Errorf("fail to read: %v", err)
  3642  			}
  3643  			gotReferrerIndex = buf.Bytes()
  3644  			w.Header().Set("Docker-Content-Digest", indexDesc_1.Digest.String())
  3645  			w.WriteHeader(http.StatusCreated)
  3646  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+emptyIndexDesc.Digest.String():
  3647  			indexDeleted = true
  3648  			// no "Docker-Content-Digest" header for manifest deletion
  3649  			w.WriteHeader(http.StatusAccepted)
  3650  		default:
  3651  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3652  			w.WriteHeader(http.StatusNotFound)
  3653  		}
  3654  	}))
  3655  	defer ts.Close()
  3656  	uri, err = url.Parse(ts.URL)
  3657  	if err != nil {
  3658  		t.Fatalf("invalid test http server: %v", err)
  3659  	}
  3660  
  3661  	ctx = context.Background()
  3662  	repo, err = NewRepository(uri.Host + "/test")
  3663  	if err != nil {
  3664  		t.Fatalf("NewRepository() error = %v", err)
  3665  	}
  3666  	repo.PlainHTTP = true
  3667  
  3668  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3669  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3670  	}
  3671  	err = repo.Push(ctx, artifactDesc, bytes.NewReader(artifactJSON))
  3672  	if err != nil {
  3673  		t.Fatalf("Manifests.Push() error = %v", err)
  3674  	}
  3675  	if !bytes.Equal(gotManifest, artifactJSON) {
  3676  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  3677  	}
  3678  	if !bytes.Equal(gotReferrerIndex, indexJSON_1) {
  3679  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1))
  3680  	}
  3681  	if !indexDeleted {
  3682  		t.Errorf("indexDeleted = %v, want %v", indexDeleted, true)
  3683  	}
  3684  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3685  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3686  	}
  3687  
  3688  	// test pushing image manifest with subject, referrer list should be updated
  3689  	manifest := ocispec.Manifest{
  3690  		MediaType: ocispec.MediaTypeImageManifest,
  3691  		Config: ocispec.Descriptor{
  3692  			MediaType: "testconfig",
  3693  		},
  3694  		Subject:     &subjectDesc,
  3695  		Annotations: map[string]string{"foo": "bar"},
  3696  	}
  3697  	manifestJSON, err := json.Marshal(manifest)
  3698  	if err != nil {
  3699  		t.Fatalf("failed to marshal manifest: %v", err)
  3700  	}
  3701  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  3702  	manifestDesc.ArtifactType = manifest.Config.MediaType
  3703  	manifestDesc.Annotations = manifest.Annotations
  3704  	index_2 := ocispec.Index{
  3705  		Versioned: specs.Versioned{
  3706  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3707  		},
  3708  		MediaType: ocispec.MediaTypeImageIndex,
  3709  		Manifests: []ocispec.Descriptor{
  3710  			artifactDesc,
  3711  			manifestDesc,
  3712  		},
  3713  	}
  3714  	indexJSON_2, err := json.Marshal(index_2)
  3715  	if err != nil {
  3716  		t.Fatalf("failed to marshal manifest: %v", err)
  3717  	}
  3718  	indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2)
  3719  	indexDeleted = false
  3720  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3721  		switch {
  3722  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3723  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  3724  				w.WriteHeader(http.StatusBadRequest)
  3725  				break
  3726  			}
  3727  			buf := bytes.NewBuffer(nil)
  3728  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3729  				t.Errorf("fail to read: %v", err)
  3730  			}
  3731  			gotManifest = buf.Bytes()
  3732  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3733  			w.WriteHeader(http.StatusCreated)
  3734  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3735  			w.Write(indexJSON_1)
  3736  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3737  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  3738  				w.WriteHeader(http.StatusBadRequest)
  3739  				break
  3740  			}
  3741  			buf := bytes.NewBuffer(nil)
  3742  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3743  				t.Errorf("fail to read: %v", err)
  3744  			}
  3745  			gotReferrerIndex = buf.Bytes()
  3746  			w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String())
  3747  			w.WriteHeader(http.StatusCreated)
  3748  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_1.Digest.String():
  3749  			indexDeleted = true
  3750  			// no "Docker-Content-Digest" header for manifest deletion
  3751  			w.WriteHeader(http.StatusAccepted)
  3752  		default:
  3753  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3754  			w.WriteHeader(http.StatusNotFound)
  3755  		}
  3756  	}))
  3757  	defer ts.Close()
  3758  	uri, err = url.Parse(ts.URL)
  3759  	if err != nil {
  3760  		t.Fatalf("invalid test http server: %v", err)
  3761  	}
  3762  
  3763  	ctx = context.Background()
  3764  	repo, err = NewRepository(uri.Host + "/test")
  3765  	if err != nil {
  3766  		t.Fatalf("NewRepository() error = %v", err)
  3767  	}
  3768  	repo.PlainHTTP = true
  3769  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3770  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3771  	}
  3772  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  3773  	if err != nil {
  3774  		t.Fatalf("Manifests.Push() error = %v", err)
  3775  	}
  3776  	if !bytes.Equal(gotManifest, manifestJSON) {
  3777  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  3778  	}
  3779  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  3780  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  3781  	}
  3782  	if !indexDeleted {
  3783  		t.Errorf("indexDeleted = %v, want %v", indexDeleted, true)
  3784  	}
  3785  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3786  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3787  	}
  3788  
  3789  	// test pushing image manifest with subject again, referrers list should not be changed
  3790  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3791  		switch {
  3792  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3793  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  3794  				w.WriteHeader(http.StatusBadRequest)
  3795  				break
  3796  			}
  3797  			buf := bytes.NewBuffer(nil)
  3798  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3799  				t.Errorf("fail to read: %v", err)
  3800  			}
  3801  			gotManifest = buf.Bytes()
  3802  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3803  			w.WriteHeader(http.StatusCreated)
  3804  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3805  			w.Write(indexJSON_2)
  3806  		default:
  3807  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3808  			w.WriteHeader(http.StatusNotFound)
  3809  		}
  3810  	}))
  3811  	defer ts.Close()
  3812  	uri, err = url.Parse(ts.URL)
  3813  	if err != nil {
  3814  		t.Fatalf("invalid test http server: %v", err)
  3815  	}
  3816  
  3817  	ctx = context.Background()
  3818  	repo, err = NewRepository(uri.Host + "/test")
  3819  	if err != nil {
  3820  		t.Fatalf("NewRepository() error = %v", err)
  3821  	}
  3822  	repo.PlainHTTP = true
  3823  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3824  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3825  	}
  3826  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  3827  	if err != nil {
  3828  		t.Fatalf("Manifests.Push() error = %v", err)
  3829  	}
  3830  	if !bytes.Equal(gotManifest, manifestJSON) {
  3831  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  3832  	}
  3833  	// referrers list should not be changed
  3834  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  3835  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  3836  	}
  3837  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3838  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3839  	}
  3840  
  3841  	// push image index with subject, referrer list should be updated
  3842  	indexManifest := ocispec.Index{
  3843  		MediaType:    ocispec.MediaTypeImageIndex,
  3844  		Subject:      &subjectDesc,
  3845  		ArtifactType: "test/index",
  3846  		Annotations:  map[string]string{"foo": "bar"},
  3847  	}
  3848  	indexManifestJSON, err := json.Marshal(indexManifest)
  3849  	if err != nil {
  3850  		t.Fatalf("failed to marshal manifest: %v", err)
  3851  	}
  3852  	indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON)
  3853  	indexManifestDesc.ArtifactType = indexManifest.ArtifactType
  3854  	indexManifestDesc.Annotations = indexManifest.Annotations
  3855  	index_3 := ocispec.Index{
  3856  		Versioned: specs.Versioned{
  3857  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3858  		},
  3859  		MediaType: ocispec.MediaTypeImageIndex,
  3860  		Manifests: []ocispec.Descriptor{
  3861  			artifactDesc,
  3862  			manifestDesc,
  3863  			indexManifestDesc,
  3864  		},
  3865  	}
  3866  	indexJSON_3, err := json.Marshal(index_3)
  3867  	if err != nil {
  3868  		t.Fatalf("failed to marshal manifest: %v", err)
  3869  	}
  3870  	indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3)
  3871  	indexDeleted = false
  3872  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3873  		switch {
  3874  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String():
  3875  			if contentType := r.Header.Get("Content-Type"); contentType != indexManifestDesc.MediaType {
  3876  				w.WriteHeader(http.StatusBadRequest)
  3877  				break
  3878  			}
  3879  			buf := bytes.NewBuffer(nil)
  3880  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3881  				t.Errorf("fail to read: %v", err)
  3882  			}
  3883  			gotManifest = buf.Bytes()
  3884  			w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String())
  3885  			w.WriteHeader(http.StatusCreated)
  3886  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3887  			w.Write(indexJSON_2)
  3888  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3889  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  3890  				w.WriteHeader(http.StatusBadRequest)
  3891  				break
  3892  			}
  3893  			buf := bytes.NewBuffer(nil)
  3894  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3895  				t.Errorf("fail to read: %v", err)
  3896  			}
  3897  			gotReferrerIndex = buf.Bytes()
  3898  			w.Header().Set("Docker-Content-Digest", indexDesc_3.Digest.String())
  3899  			w.WriteHeader(http.StatusCreated)
  3900  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_2.Digest.String():
  3901  			indexDeleted = true
  3902  			// no "Docker-Content-Digest" header for manifest deletion
  3903  			w.WriteHeader(http.StatusAccepted)
  3904  		default:
  3905  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3906  			w.WriteHeader(http.StatusNotFound)
  3907  		}
  3908  	}))
  3909  	defer ts.Close()
  3910  	uri, err = url.Parse(ts.URL)
  3911  	if err != nil {
  3912  		t.Fatalf("invalid test http server: %v", err)
  3913  	}
  3914  
  3915  	ctx = context.Background()
  3916  	repo, err = NewRepository(uri.Host + "/test")
  3917  	if err != nil {
  3918  		t.Fatalf("NewRepository() error = %v", err)
  3919  	}
  3920  	repo.PlainHTTP = true
  3921  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3922  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3923  	}
  3924  	err = repo.Push(ctx, indexManifestDesc, bytes.NewReader(indexManifestJSON))
  3925  	if err != nil {
  3926  		t.Fatalf("Manifests.Push() error = %v", err)
  3927  	}
  3928  	if !bytes.Equal(gotManifest, indexManifestJSON) {
  3929  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(indexManifestJSON))
  3930  	}
  3931  	if !bytes.Equal(gotReferrerIndex, indexJSON_3) {
  3932  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_3))
  3933  	}
  3934  	if !indexDeleted {
  3935  		t.Errorf("indexDeleted = %v, want %v", indexDeleted, true)
  3936  	}
  3937  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3938  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3939  	}
  3940  }
  3941  
  3942  func Test_ManifestStore_Push_ReferrersAPIUnavailable_SkipReferrersGC(t *testing.T) {
  3943  	// generate test content
  3944  	subject := []byte(`{"layers":[]}`)
  3945  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  3946  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  3947  	manifest := ocispec.Manifest{
  3948  		MediaType: ocispec.MediaTypeImageManifest,
  3949  		Config: ocispec.Descriptor{
  3950  			MediaType: "testconfig",
  3951  		},
  3952  		Subject:     &subjectDesc,
  3953  		Annotations: map[string]string{"foo": "bar"},
  3954  	}
  3955  	manifestJSON, err := json.Marshal(manifest)
  3956  	if err != nil {
  3957  		t.Fatalf("failed to marshal manifest: %v", err)
  3958  	}
  3959  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  3960  	manifestDesc.ArtifactType = manifest.Config.MediaType
  3961  	manifestDesc.Annotations = manifest.Annotations
  3962  	index_1 := ocispec.Index{
  3963  		Versioned: specs.Versioned{
  3964  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3965  		},
  3966  		MediaType: ocispec.MediaTypeImageIndex,
  3967  		Manifests: []ocispec.Descriptor{
  3968  			manifestDesc,
  3969  		},
  3970  	}
  3971  
  3972  	// test pushing image manifest with subject, a referrers list should be created
  3973  	indexJSON_1, err := json.Marshal(index_1)
  3974  	if err != nil {
  3975  		t.Fatalf("failed to marshal manifest: %v", err)
  3976  	}
  3977  	indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1)
  3978  	var gotManifest []byte
  3979  	var gotReferrerIndex []byte
  3980  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3981  		switch {
  3982  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3983  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  3984  				w.WriteHeader(http.StatusBadRequest)
  3985  				break
  3986  			}
  3987  			buf := bytes.NewBuffer(nil)
  3988  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3989  				t.Errorf("fail to read: %v", err)
  3990  			}
  3991  			gotManifest = buf.Bytes()
  3992  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3993  			w.WriteHeader(http.StatusCreated)
  3994  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3995  			w.WriteHeader(http.StatusNotFound)
  3996  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3997  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  3998  				w.WriteHeader(http.StatusBadRequest)
  3999  				break
  4000  			}
  4001  			buf := bytes.NewBuffer(nil)
  4002  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4003  				t.Errorf("fail to read: %v", err)
  4004  			}
  4005  			gotReferrerIndex = buf.Bytes()
  4006  			w.Header().Set("Docker-Content-Digest", indexDesc_1.Digest.String())
  4007  			w.WriteHeader(http.StatusCreated)
  4008  		default:
  4009  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4010  			w.WriteHeader(http.StatusNotFound)
  4011  		}
  4012  	}))
  4013  	defer ts.Close()
  4014  	uri, err := url.Parse(ts.URL)
  4015  	if err != nil {
  4016  		t.Fatalf("invalid test http server: %v", err)
  4017  	}
  4018  
  4019  	ctx := context.Background()
  4020  	repo, err := NewRepository(uri.Host + "/test")
  4021  	if err != nil {
  4022  		t.Fatalf("NewRepository() error = %v", err)
  4023  	}
  4024  	repo.PlainHTTP = true
  4025  	repo.SkipReferrersGC = true
  4026  
  4027  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4028  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4029  	}
  4030  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  4031  	if err != nil {
  4032  		t.Fatalf("Manifests.Push() error = %v", err)
  4033  	}
  4034  	if !bytes.Equal(gotManifest, manifestJSON) {
  4035  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  4036  	}
  4037  	if !bytes.Equal(gotReferrerIndex, indexJSON_1) {
  4038  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1))
  4039  	}
  4040  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4041  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4042  	}
  4043  
  4044  	// test pushing image manifest with subject when an old empty referrer list exists,
  4045  	// the referrer list should be updated
  4046  	emptyIndex := ocispec.Index{
  4047  		Versioned: specs.Versioned{
  4048  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4049  		},
  4050  		MediaType: ocispec.MediaTypeImageIndex,
  4051  	}
  4052  	emptyIndexJSON, err := json.Marshal(emptyIndex)
  4053  	if err != nil {
  4054  		t.Error("failed to marshal index", err)
  4055  	}
  4056  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4057  		switch {
  4058  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4059  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  4060  				w.WriteHeader(http.StatusBadRequest)
  4061  				break
  4062  			}
  4063  			buf := bytes.NewBuffer(nil)
  4064  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4065  				t.Errorf("fail to read: %v", err)
  4066  			}
  4067  			gotManifest = buf.Bytes()
  4068  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  4069  			w.WriteHeader(http.StatusCreated)
  4070  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4071  			w.Write(emptyIndexJSON)
  4072  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4073  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  4074  				w.WriteHeader(http.StatusBadRequest)
  4075  				break
  4076  			}
  4077  			buf := bytes.NewBuffer(nil)
  4078  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4079  				t.Errorf("fail to read: %v", err)
  4080  			}
  4081  			gotReferrerIndex = buf.Bytes()
  4082  			w.Header().Set("Docker-Content-Digest", indexDesc_1.Digest.String())
  4083  			w.WriteHeader(http.StatusCreated)
  4084  		default:
  4085  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4086  			w.WriteHeader(http.StatusNotFound)
  4087  		}
  4088  	}))
  4089  	defer ts.Close()
  4090  	uri, err = url.Parse(ts.URL)
  4091  	if err != nil {
  4092  		t.Fatalf("invalid test http server: %v", err)
  4093  	}
  4094  
  4095  	ctx = context.Background()
  4096  	repo, err = NewRepository(uri.Host + "/test")
  4097  	if err != nil {
  4098  		t.Fatalf("NewRepository() error = %v", err)
  4099  	}
  4100  	repo.PlainHTTP = true
  4101  	repo.SkipReferrersGC = true
  4102  
  4103  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4104  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4105  	}
  4106  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  4107  	if err != nil {
  4108  		t.Fatalf("Manifests.Push() error = %v", err)
  4109  	}
  4110  	if !bytes.Equal(gotManifest, manifestJSON) {
  4111  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  4112  	}
  4113  	if !bytes.Equal(gotReferrerIndex, indexJSON_1) {
  4114  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1))
  4115  	}
  4116  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4117  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4118  	}
  4119  
  4120  	// push image index with subject, referrer list should be updated, the old
  4121  	// one should not be deleted
  4122  	indexManifest := ocispec.Index{
  4123  		MediaType:    ocispec.MediaTypeImageIndex,
  4124  		Subject:      &subjectDesc,
  4125  		ArtifactType: "test/index",
  4126  		Annotations:  map[string]string{"foo": "bar"},
  4127  	}
  4128  	indexManifestJSON, err := json.Marshal(indexManifest)
  4129  	if err != nil {
  4130  		t.Fatalf("failed to marshal manifest: %v", err)
  4131  	}
  4132  	indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON)
  4133  	indexManifestDesc.ArtifactType = indexManifest.ArtifactType
  4134  	indexManifestDesc.Annotations = indexManifest.Annotations
  4135  	index_2 := ocispec.Index{
  4136  		Versioned: specs.Versioned{
  4137  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4138  		},
  4139  		MediaType: ocispec.MediaTypeImageIndex,
  4140  		Manifests: []ocispec.Descriptor{
  4141  			manifestDesc,
  4142  			indexManifestDesc,
  4143  		},
  4144  	}
  4145  	indexJSON_2, err := json.Marshal(index_2)
  4146  	if err != nil {
  4147  		t.Fatalf("failed to marshal manifest: %v", err)
  4148  	}
  4149  	indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2)
  4150  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4151  		switch {
  4152  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String():
  4153  			if contentType := r.Header.Get("Content-Type"); contentType != indexManifestDesc.MediaType {
  4154  				w.WriteHeader(http.StatusBadRequest)
  4155  				break
  4156  			}
  4157  			buf := bytes.NewBuffer(nil)
  4158  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4159  				t.Errorf("fail to read: %v", err)
  4160  			}
  4161  			gotManifest = buf.Bytes()
  4162  			w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String())
  4163  			w.WriteHeader(http.StatusCreated)
  4164  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4165  			w.Write(indexJSON_1)
  4166  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4167  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  4168  				w.WriteHeader(http.StatusBadRequest)
  4169  				break
  4170  			}
  4171  			buf := bytes.NewBuffer(nil)
  4172  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4173  				t.Errorf("fail to read: %v", err)
  4174  			}
  4175  			gotReferrerIndex = buf.Bytes()
  4176  			w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String())
  4177  			w.WriteHeader(http.StatusCreated)
  4178  		default:
  4179  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4180  			w.WriteHeader(http.StatusNotFound)
  4181  		}
  4182  	}))
  4183  	defer ts.Close()
  4184  	uri, err = url.Parse(ts.URL)
  4185  	if err != nil {
  4186  		t.Fatalf("invalid test http server: %v", err)
  4187  	}
  4188  
  4189  	ctx = context.Background()
  4190  	repo, err = NewRepository(uri.Host + "/test")
  4191  	if err != nil {
  4192  		t.Fatalf("NewRepository() error = %v", err)
  4193  	}
  4194  	repo.PlainHTTP = true
  4195  	repo.SkipReferrersGC = true
  4196  
  4197  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4198  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4199  	}
  4200  	err = repo.Push(ctx, indexManifestDesc, bytes.NewReader(indexManifestJSON))
  4201  	if err != nil {
  4202  		t.Fatalf("Manifests.Push() error = %v", err)
  4203  	}
  4204  	if !bytes.Equal(gotManifest, indexManifestJSON) {
  4205  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(indexManifestJSON))
  4206  	}
  4207  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  4208  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  4209  	}
  4210  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4211  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4212  	}
  4213  }
  4214  
  4215  func Test_ManifestStore_Exists(t *testing.T) {
  4216  	manifest := []byte(`{"layers":[]}`)
  4217  	manifestDesc := ocispec.Descriptor{
  4218  		MediaType: ocispec.MediaTypeImageManifest,
  4219  		Digest:    digest.FromBytes(manifest),
  4220  		Size:      int64(len(manifest)),
  4221  	}
  4222  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4223  		if r.Method != http.MethodHead {
  4224  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4225  			w.WriteHeader(http.StatusMethodNotAllowed)
  4226  			return
  4227  		}
  4228  		switch r.URL.Path {
  4229  		case "/v2/test/manifests/" + manifestDesc.Digest.String():
  4230  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  4231  				t.Errorf("manifest not convertable: %s", accept)
  4232  				w.WriteHeader(http.StatusBadRequest)
  4233  				return
  4234  			}
  4235  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  4236  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  4237  			w.Header().Set("Content-Length", strconv.Itoa(int(manifestDesc.Size)))
  4238  		default:
  4239  			w.WriteHeader(http.StatusNotFound)
  4240  		}
  4241  	}))
  4242  	defer ts.Close()
  4243  	uri, err := url.Parse(ts.URL)
  4244  	if err != nil {
  4245  		t.Fatalf("invalid test http server: %v", err)
  4246  	}
  4247  
  4248  	repo, err := NewRepository(uri.Host + "/test")
  4249  	if err != nil {
  4250  		t.Fatalf("NewRepository() error = %v", err)
  4251  	}
  4252  	repo.PlainHTTP = true
  4253  	store := repo.Manifests()
  4254  	ctx := context.Background()
  4255  
  4256  	exists, err := store.Exists(ctx, manifestDesc)
  4257  	if err != nil {
  4258  		t.Fatalf("Manifests.Exists() error = %v", err)
  4259  	}
  4260  	if !exists {
  4261  		t.Errorf("Manifests.Exists() = %v, want %v", exists, true)
  4262  	}
  4263  
  4264  	content := []byte(`{"manifests":[]}`)
  4265  	contentDesc := ocispec.Descriptor{
  4266  		MediaType: ocispec.MediaTypeImageIndex,
  4267  		Digest:    digest.FromBytes(content),
  4268  		Size:      int64(len(content)),
  4269  	}
  4270  	exists, err = store.Exists(ctx, contentDesc)
  4271  	if err != nil {
  4272  		t.Fatalf("Manifests.Exists() error = %v", err)
  4273  	}
  4274  	if exists {
  4275  		t.Errorf("Manifests.Exists() = %v, want %v", exists, false)
  4276  	}
  4277  }
  4278  
  4279  func Test_ManifestStore_Delete(t *testing.T) {
  4280  	manifest := []byte(`{"layers":[]}`)
  4281  	manifestDesc := ocispec.Descriptor{
  4282  		MediaType: ocispec.MediaTypeImageManifest,
  4283  		Digest:    digest.FromBytes(manifest),
  4284  		Size:      int64(len(manifest)),
  4285  	}
  4286  	manifestDeleted := false
  4287  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4288  		if r.Method != http.MethodDelete && r.Method != http.MethodGet {
  4289  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4290  			w.WriteHeader(http.StatusMethodNotAllowed)
  4291  		}
  4292  		switch {
  4293  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4294  			manifestDeleted = true
  4295  			// no "Docker-Content-Digest" header for manifest deletion
  4296  			w.WriteHeader(http.StatusAccepted)
  4297  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4298  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  4299  				t.Errorf("manifest not convertable: %s", accept)
  4300  				w.WriteHeader(http.StatusBadRequest)
  4301  				return
  4302  			}
  4303  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  4304  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  4305  			if _, err := w.Write(manifest); err != nil {
  4306  				t.Errorf("failed to write %q: %v", r.URL, err)
  4307  			}
  4308  		default:
  4309  			w.WriteHeader(http.StatusNotFound)
  4310  		}
  4311  	}))
  4312  	defer ts.Close()
  4313  	uri, err := url.Parse(ts.URL)
  4314  	if err != nil {
  4315  		t.Fatalf("invalid test http server: %v", err)
  4316  	}
  4317  
  4318  	repo, err := NewRepository(uri.Host + "/test")
  4319  	if err != nil {
  4320  		t.Fatalf("NewRepository() error = %v", err)
  4321  	}
  4322  	repo.PlainHTTP = true
  4323  	store := repo.Manifests()
  4324  	ctx := context.Background()
  4325  
  4326  	// test deleting manifest without subject
  4327  	err = store.Delete(ctx, manifestDesc)
  4328  	if err != nil {
  4329  		t.Fatalf("Manifests.Delete() error = %v", err)
  4330  	}
  4331  	if !manifestDeleted {
  4332  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4333  	}
  4334  
  4335  	// test deleting content that does not exist
  4336  	content := []byte(`{"manifests":[]}`)
  4337  	contentDesc := ocispec.Descriptor{
  4338  		MediaType: ocispec.MediaTypeImageIndex,
  4339  		Digest:    digest.FromBytes(content),
  4340  		Size:      int64(len(content)),
  4341  	}
  4342  	err = store.Delete(ctx, contentDesc)
  4343  	if !errors.Is(err, errdef.ErrNotFound) {
  4344  		t.Errorf("Manifests.Delete() error = %v, wantErr %v", err, errdef.ErrNotFound)
  4345  	}
  4346  }
  4347  
  4348  func Test_ManifestStore_Delete_ReferrersAPIAvailable(t *testing.T) {
  4349  	// generate test content
  4350  	subject := []byte(`{"layers":[]}`)
  4351  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  4352  	artifact := spec.Artifact{
  4353  		MediaType: spec.MediaTypeArtifactManifest,
  4354  		Subject:   &subjectDesc,
  4355  	}
  4356  	artifactJSON, err := json.Marshal(artifact)
  4357  	if err != nil {
  4358  		t.Fatalf("failed to marshal manifest: %v", err)
  4359  	}
  4360  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  4361  
  4362  	manifest := ocispec.Manifest{
  4363  		MediaType: ocispec.MediaTypeImageManifest,
  4364  		Subject:   &subjectDesc,
  4365  	}
  4366  	manifestJSON, err := json.Marshal(manifest)
  4367  	if err != nil {
  4368  		t.Fatalf("failed to marshal manifest: %v", err)
  4369  	}
  4370  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  4371  
  4372  	index := ocispec.Index{
  4373  		MediaType: ocispec.MediaTypeImageIndex,
  4374  		Subject:   &subjectDesc,
  4375  	}
  4376  	indexJSON, err := json.Marshal(index)
  4377  	if err != nil {
  4378  		t.Fatalf("failed to marshal manifest: %v", err)
  4379  	}
  4380  	indexDesc := content.NewDescriptorFromBytes(index.MediaType, indexJSON)
  4381  
  4382  	var manifestDeleted bool
  4383  	var artifactDeleted bool
  4384  	var indexDeleted bool
  4385  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4386  		if r.Method != http.MethodDelete && r.Method != http.MethodGet {
  4387  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4388  			w.WriteHeader(http.StatusMethodNotAllowed)
  4389  		}
  4390  		switch {
  4391  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  4392  			artifactDeleted = true
  4393  			// no "Docker-Content-Digest" header for manifest deletion
  4394  			w.WriteHeader(http.StatusAccepted)
  4395  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4396  			manifestDeleted = true
  4397  			// no "Docker-Content-Digest" header for manifest deletion
  4398  			w.WriteHeader(http.StatusAccepted)
  4399  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
  4400  			indexDeleted = true
  4401  			// no "Docker-Content-Digest" header for manifest deletion
  4402  			w.WriteHeader(http.StatusAccepted)
  4403  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  4404  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  4405  				t.Errorf("manifest not convertable: %s", accept)
  4406  				w.WriteHeader(http.StatusBadRequest)
  4407  				return
  4408  			}
  4409  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  4410  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  4411  			if _, err := w.Write(artifactJSON); err != nil {
  4412  				t.Errorf("failed to write %q: %v", r.URL, err)
  4413  			}
  4414  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4415  			result := ocispec.Index{
  4416  				Versioned: specs.Versioned{
  4417  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4418  				},
  4419  				MediaType: ocispec.MediaTypeImageIndex,
  4420  				Manifests: []ocispec.Descriptor{},
  4421  			}
  4422  			w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  4423  			if err := json.NewEncoder(w).Encode(result); err != nil {
  4424  				t.Errorf("failed to write response: %v", err)
  4425  			}
  4426  		default:
  4427  			w.WriteHeader(http.StatusNotFound)
  4428  		}
  4429  	}))
  4430  	defer ts.Close()
  4431  	uri, err := url.Parse(ts.URL)
  4432  	if err != nil {
  4433  		t.Fatalf("invalid test http server: %v", err)
  4434  	}
  4435  	repo, err := NewRepository(uri.Host + "/test")
  4436  	if err != nil {
  4437  		t.Fatalf("NewRepository() error = %v", err)
  4438  	}
  4439  	repo.PlainHTTP = true
  4440  	store := repo.Manifests()
  4441  	ctx := context.Background()
  4442  	// test deleting artifact with subject
  4443  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4444  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4445  	}
  4446  	err = store.Delete(ctx, artifactDesc)
  4447  	if err != nil {
  4448  		t.Fatalf("Manifests.Delete() error = %v", err)
  4449  	}
  4450  	if !artifactDeleted {
  4451  		t.Errorf("Manifests.Delete() = %v, want %v", artifactDeleted, true)
  4452  	}
  4453  
  4454  	// test deleting manifest with subject
  4455  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  4456  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  4457  	}
  4458  	err = store.Delete(ctx, manifestDesc)
  4459  	if err != nil {
  4460  		t.Fatalf("Manifests.Delete() error = %v", err)
  4461  	}
  4462  	if !manifestDeleted {
  4463  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4464  	}
  4465  
  4466  	// test deleting index with subject
  4467  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  4468  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  4469  	}
  4470  	err = store.Delete(ctx, indexDesc)
  4471  	if err != nil {
  4472  		t.Fatalf("Manifests.Delete() error = %v", err)
  4473  	}
  4474  	if !indexDeleted {
  4475  		t.Errorf("Manifests.Delete() = %v, want %v", indexDeleted, true)
  4476  	}
  4477  
  4478  	// test deleting content that does not exist
  4479  	content := []byte("whatever")
  4480  	contentDesc := ocispec.Descriptor{
  4481  		MediaType: ocispec.MediaTypeImageManifest,
  4482  		Digest:    digest.FromBytes(content),
  4483  		Size:      int64(len(content)),
  4484  	}
  4485  	ctx = context.Background()
  4486  	err = store.Delete(ctx, contentDesc)
  4487  	if !errors.Is(err, errdef.ErrNotFound) {
  4488  		t.Errorf("Manifests.Delete() error = %v, wantErr %v", err, errdef.ErrNotFound)
  4489  	}
  4490  }
  4491  
  4492  func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) {
  4493  	// generate test content
  4494  	subject := []byte(`{"layers":[]}`)
  4495  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  4496  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  4497  
  4498  	artifact := spec.Artifact{
  4499  		MediaType: spec.MediaTypeArtifactManifest,
  4500  		Subject:   &subjectDesc,
  4501  	}
  4502  	artifactJSON, err := json.Marshal(artifact)
  4503  	if err != nil {
  4504  		t.Fatalf("failed to marshal manifest: %v", err)
  4505  	}
  4506  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  4507  
  4508  	manifest := ocispec.Manifest{
  4509  		MediaType: ocispec.MediaTypeImageManifest,
  4510  		Subject:   &subjectDesc,
  4511  	}
  4512  	manifestJSON, err := json.Marshal(manifest)
  4513  	if err != nil {
  4514  		t.Fatalf("failed to marshal manifest: %v", err)
  4515  	}
  4516  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  4517  
  4518  	indexManifest := ocispec.Index{
  4519  		MediaType: ocispec.MediaTypeImageIndex,
  4520  		Subject:   &subjectDesc,
  4521  	}
  4522  	indexManifestJSON, err := json.Marshal(indexManifest)
  4523  	if err != nil {
  4524  		t.Fatalf("failed to marshal manifest: %v", err)
  4525  	}
  4526  	indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON)
  4527  
  4528  	index_1 := ocispec.Index{
  4529  		Versioned: specs.Versioned{
  4530  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4531  		},
  4532  		MediaType: ocispec.MediaTypeImageIndex,
  4533  		Manifests: []ocispec.Descriptor{
  4534  			artifactDesc,
  4535  			manifestDesc,
  4536  			indexManifestDesc,
  4537  		},
  4538  	}
  4539  	indexJSON_1, err := json.Marshal(index_1)
  4540  	if err != nil {
  4541  		t.Fatalf("failed to marshal manifest: %v", err)
  4542  	}
  4543  	indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1)
  4544  	index_2 := ocispec.Index{
  4545  		Versioned: specs.Versioned{
  4546  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4547  		},
  4548  		MediaType: ocispec.MediaTypeImageIndex,
  4549  		Manifests: []ocispec.Descriptor{
  4550  			manifestDesc,
  4551  			indexManifestDesc,
  4552  		},
  4553  	}
  4554  	indexJSON_2, err := json.Marshal(index_2)
  4555  	if err != nil {
  4556  		t.Fatalf("failed to marshal manifest: %v", err)
  4557  	}
  4558  	indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2)
  4559  	index_3 := ocispec.Index{
  4560  		Versioned: specs.Versioned{
  4561  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4562  		},
  4563  		MediaType: ocispec.MediaTypeImageIndex,
  4564  		Manifests: []ocispec.Descriptor{
  4565  			indexManifestDesc,
  4566  		},
  4567  	}
  4568  	indexJSON_3, err := json.Marshal(index_3)
  4569  	if err != nil {
  4570  		t.Fatalf("failed to marshal manifest: %v", err)
  4571  	}
  4572  	indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3)
  4573  
  4574  	// test deleting artifact with subject, referrers list should be updated
  4575  	manifestDeleted := false
  4576  	indexDeleted := false
  4577  	var gotReferrerIndex []byte
  4578  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4579  		switch {
  4580  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  4581  			manifestDeleted = true
  4582  			// no "Docker-Content-Digest" header for manifest deletion
  4583  			w.WriteHeader(http.StatusAccepted)
  4584  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  4585  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  4586  				t.Errorf("manifest not convertable: %s", accept)
  4587  				w.WriteHeader(http.StatusBadRequest)
  4588  				return
  4589  			}
  4590  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  4591  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  4592  			if _, err := w.Write(artifactJSON); err != nil {
  4593  				t.Errorf("failed to write %q: %v", r.URL, err)
  4594  			}
  4595  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4596  			w.WriteHeader(http.StatusNotFound)
  4597  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4598  			w.Write(indexJSON_1)
  4599  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4600  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  4601  				w.WriteHeader(http.StatusBadRequest)
  4602  				break
  4603  			}
  4604  			buf := bytes.NewBuffer(nil)
  4605  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4606  				t.Errorf("fail to read: %v", err)
  4607  			}
  4608  			gotReferrerIndex = buf.Bytes()
  4609  			w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String())
  4610  			w.WriteHeader(http.StatusCreated)
  4611  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_1.Digest.String():
  4612  			indexDeleted = true
  4613  			// no "Docker-Content-Digest" header for manifest deletion
  4614  			w.WriteHeader(http.StatusAccepted)
  4615  		default:
  4616  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4617  			w.WriteHeader(http.StatusNotFound)
  4618  		}
  4619  	}))
  4620  	defer ts.Close()
  4621  	uri, err := url.Parse(ts.URL)
  4622  	if err != nil {
  4623  		t.Fatalf("invalid test http server: %v", err)
  4624  	}
  4625  	repo, err := NewRepository(uri.Host + "/test")
  4626  	if err != nil {
  4627  		t.Fatalf("NewRepository() error = %v", err)
  4628  	}
  4629  	repo.PlainHTTP = true
  4630  	store := repo.Manifests()
  4631  	ctx := context.Background()
  4632  
  4633  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4634  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4635  	}
  4636  	err = store.Delete(ctx, artifactDesc)
  4637  	if err != nil {
  4638  		t.Fatalf("Manifests.Delete() error = %v", err)
  4639  	}
  4640  	if !manifestDeleted {
  4641  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4642  	}
  4643  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  4644  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  4645  	}
  4646  	if !indexDeleted {
  4647  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4648  	}
  4649  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4650  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4651  	}
  4652  
  4653  	// test deleting manifest with subject, referrers list should be updated
  4654  	manifestDeleted = false
  4655  	indexDeleted = false
  4656  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4657  		switch {
  4658  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4659  			manifestDeleted = true
  4660  			// no "Docker-Content-Digest" header for manifest deletion
  4661  			w.WriteHeader(http.StatusAccepted)
  4662  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4663  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  4664  				t.Errorf("manifest not convertable: %s", accept)
  4665  				w.WriteHeader(http.StatusBadRequest)
  4666  				return
  4667  			}
  4668  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  4669  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  4670  			if _, err := w.Write(manifestJSON); err != nil {
  4671  				t.Errorf("failed to write %q: %v", r.URL, err)
  4672  			}
  4673  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4674  			w.WriteHeader(http.StatusNotFound)
  4675  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4676  			w.Write(indexJSON_2)
  4677  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4678  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  4679  				w.WriteHeader(http.StatusBadRequest)
  4680  				break
  4681  			}
  4682  			buf := bytes.NewBuffer(nil)
  4683  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4684  				t.Errorf("fail to read: %v", err)
  4685  			}
  4686  			gotReferrerIndex = buf.Bytes()
  4687  			w.Header().Set("Docker-Content-Digest", indexDesc_3.Digest.String())
  4688  			w.WriteHeader(http.StatusCreated)
  4689  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_2.Digest.String():
  4690  			indexDeleted = true
  4691  			// no "Docker-Content-Digest" header for manifest deletion
  4692  			w.WriteHeader(http.StatusAccepted)
  4693  		default:
  4694  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4695  			w.WriteHeader(http.StatusNotFound)
  4696  		}
  4697  	}))
  4698  	defer ts.Close()
  4699  	uri, err = url.Parse(ts.URL)
  4700  	if err != nil {
  4701  		t.Fatalf("invalid test http server: %v", err)
  4702  	}
  4703  	repo, err = NewRepository(uri.Host + "/test")
  4704  	if err != nil {
  4705  		t.Fatalf("NewRepository() error = %v", err)
  4706  	}
  4707  	repo.PlainHTTP = true
  4708  	store = repo.Manifests()
  4709  	ctx = context.Background()
  4710  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4711  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4712  	}
  4713  	err = store.Delete(ctx, manifestDesc)
  4714  	if err != nil {
  4715  		t.Fatalf("Manifests.Delete() error = %v", err)
  4716  	}
  4717  	if !manifestDeleted {
  4718  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4719  	}
  4720  	if !indexDeleted {
  4721  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4722  	}
  4723  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4724  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4725  	}
  4726  
  4727  	// test deleting index with a subject, referrers list should be updated
  4728  	manifestDeleted = false
  4729  	indexDeleted = false
  4730  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4731  		switch {
  4732  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String():
  4733  			manifestDeleted = true
  4734  			// no "Docker-Content-Digest" header for manifest deletion
  4735  			w.WriteHeader(http.StatusAccepted)
  4736  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String():
  4737  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexManifestDesc.MediaType) {
  4738  				t.Errorf("manifest not convertable: %s", accept)
  4739  				w.WriteHeader(http.StatusBadRequest)
  4740  				return
  4741  			}
  4742  			w.Header().Set("Content-Type", indexManifestDesc.MediaType)
  4743  			w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String())
  4744  			if _, err := w.Write(indexManifestJSON); err != nil {
  4745  				t.Errorf("failed to write %q: %v", r.URL, err)
  4746  			}
  4747  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4748  			w.WriteHeader(http.StatusNotFound)
  4749  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4750  			w.Write(indexJSON_3)
  4751  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_3.Digest.String():
  4752  			indexDeleted = true
  4753  			// no "Docker-Content-Digest" header for manifest deletion
  4754  			w.WriteHeader(http.StatusAccepted)
  4755  		default:
  4756  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4757  			w.WriteHeader(http.StatusNotFound)
  4758  		}
  4759  	}))
  4760  	defer ts.Close()
  4761  	uri, err = url.Parse(ts.URL)
  4762  	if err != nil {
  4763  		t.Fatalf("invalid test http server: %v", err)
  4764  	}
  4765  	repo, err = NewRepository(uri.Host + "/test")
  4766  	if err != nil {
  4767  		t.Fatalf("NewRepository() error = %v", err)
  4768  	}
  4769  	repo.PlainHTTP = true
  4770  	store = repo.Manifests()
  4771  	ctx = context.Background()
  4772  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4773  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4774  	}
  4775  	err = store.Delete(ctx, indexManifestDesc)
  4776  	if err != nil {
  4777  		t.Fatalf("Manifests.Delete() error = %v", err)
  4778  	}
  4779  	if !manifestDeleted {
  4780  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4781  	}
  4782  	if !indexDeleted {
  4783  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4784  	}
  4785  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4786  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4787  	}
  4788  }
  4789  
  4790  func Test_ManifestStore_Delete_ReferrersAPIUnavailable_SkipReferrersGC(t *testing.T) {
  4791  	// generate test content
  4792  	subject := []byte(`{"layers":[]}`)
  4793  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  4794  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  4795  
  4796  	manifest := ocispec.Manifest{
  4797  		MediaType: ocispec.MediaTypeImageManifest,
  4798  		Subject:   &subjectDesc,
  4799  	}
  4800  	manifestJSON, err := json.Marshal(manifest)
  4801  	if err != nil {
  4802  		t.Fatalf("failed to marshal manifest: %v", err)
  4803  	}
  4804  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  4805  
  4806  	indexManifest := ocispec.Index{
  4807  		MediaType: ocispec.MediaTypeImageIndex,
  4808  		Subject:   &subjectDesc,
  4809  	}
  4810  	indexManifestJSON, err := json.Marshal(indexManifest)
  4811  	if err != nil {
  4812  		t.Fatalf("failed to marshal manifest: %v", err)
  4813  	}
  4814  	indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON)
  4815  
  4816  	index_1 := ocispec.Index{
  4817  		Versioned: specs.Versioned{
  4818  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4819  		},
  4820  		MediaType: ocispec.MediaTypeImageIndex,
  4821  		Manifests: []ocispec.Descriptor{
  4822  			manifestDesc,
  4823  			indexManifestDesc,
  4824  		},
  4825  	}
  4826  	indexJSON_1, err := json.Marshal(index_1)
  4827  	if err != nil {
  4828  		t.Fatalf("failed to marshal manifest: %v", err)
  4829  	}
  4830  	index_2 := ocispec.Index{
  4831  		Versioned: specs.Versioned{
  4832  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4833  		},
  4834  		MediaType: ocispec.MediaTypeImageIndex,
  4835  		Manifests: []ocispec.Descriptor{
  4836  			indexManifestDesc,
  4837  		},
  4838  	}
  4839  	indexJSON_2, err := json.Marshal(index_2)
  4840  	if err != nil {
  4841  		t.Fatalf("failed to marshal manifest: %v", err)
  4842  	}
  4843  	indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2)
  4844  	index_3 := ocispec.Index{
  4845  		Versioned: specs.Versioned{
  4846  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4847  		},
  4848  		MediaType: ocispec.MediaTypeImageIndex,
  4849  		Manifests: []ocispec.Descriptor{},
  4850  	}
  4851  	indexJSON_3, err := json.Marshal(index_3)
  4852  	if err != nil {
  4853  		t.Fatalf("failed to marshal manifest: %v", err)
  4854  	}
  4855  	indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3)
  4856  
  4857  	// test deleting image manifest with subject, referrers list should be updated,
  4858  	// the old one should not be deleted
  4859  	manifestDeleted := false
  4860  	var gotReferrerIndex []byte
  4861  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4862  		switch {
  4863  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4864  			manifestDeleted = true
  4865  			// no "Docker-Content-Digest" header for manifest deletion
  4866  			w.WriteHeader(http.StatusAccepted)
  4867  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4868  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  4869  				t.Errorf("manifest not convertable: %s", accept)
  4870  				w.WriteHeader(http.StatusBadRequest)
  4871  				return
  4872  			}
  4873  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  4874  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  4875  			if _, err := w.Write(manifestJSON); err != nil {
  4876  				t.Errorf("failed to write %q: %v", r.URL, err)
  4877  			}
  4878  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4879  			w.WriteHeader(http.StatusNotFound)
  4880  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4881  			w.Write(indexJSON_1)
  4882  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4883  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  4884  				w.WriteHeader(http.StatusBadRequest)
  4885  				break
  4886  			}
  4887  			buf := bytes.NewBuffer(nil)
  4888  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4889  				t.Errorf("fail to read: %v", err)
  4890  			}
  4891  			gotReferrerIndex = buf.Bytes()
  4892  			w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String())
  4893  			w.WriteHeader(http.StatusCreated)
  4894  		default:
  4895  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4896  			w.WriteHeader(http.StatusNotFound)
  4897  		}
  4898  	}))
  4899  	defer ts.Close()
  4900  	uri, err := url.Parse(ts.URL)
  4901  	if err != nil {
  4902  		t.Fatalf("invalid test http server: %v", err)
  4903  	}
  4904  	repo, err := NewRepository(uri.Host + "/test")
  4905  	if err != nil {
  4906  		t.Fatalf("NewRepository() error = %v", err)
  4907  	}
  4908  	repo.PlainHTTP = true
  4909  	repo.SkipReferrersGC = true
  4910  	store := repo.Manifests()
  4911  	ctx := context.Background()
  4912  
  4913  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4914  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4915  	}
  4916  	err = store.Delete(ctx, manifestDesc)
  4917  	if err != nil {
  4918  		t.Fatalf("Manifests.Delete() error = %v", err)
  4919  	}
  4920  	if !manifestDeleted {
  4921  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4922  	}
  4923  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  4924  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  4925  	}
  4926  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4927  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4928  	}
  4929  
  4930  	// test deleting index with a subject, referrers list should be updated,
  4931  	// the old one should not be deleted, an empty one should be pushed
  4932  	manifestDeleted = false
  4933  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4934  		switch {
  4935  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String():
  4936  			manifestDeleted = true
  4937  			// no "Docker-Content-Digest" header for manifest deletion
  4938  			w.WriteHeader(http.StatusAccepted)
  4939  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexManifestDesc.Digest.String():
  4940  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexManifestDesc.MediaType) {
  4941  				t.Errorf("manifest not convertable: %s", accept)
  4942  				w.WriteHeader(http.StatusBadRequest)
  4943  				return
  4944  			}
  4945  			w.Header().Set("Content-Type", indexManifestDesc.MediaType)
  4946  			w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String())
  4947  			if _, err := w.Write(indexManifestJSON); err != nil {
  4948  				t.Errorf("failed to write %q: %v", r.URL, err)
  4949  			}
  4950  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4951  			w.WriteHeader(http.StatusNotFound)
  4952  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4953  			w.Write(indexJSON_2)
  4954  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4955  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  4956  				w.WriteHeader(http.StatusBadRequest)
  4957  				break
  4958  			}
  4959  			buf := bytes.NewBuffer(nil)
  4960  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4961  				t.Errorf("fail to read: %v", err)
  4962  			}
  4963  			gotReferrerIndex = buf.Bytes()
  4964  			w.Header().Set("Docker-Content-Digest", indexDesc_3.Digest.String())
  4965  			w.WriteHeader(http.StatusCreated)
  4966  		default:
  4967  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4968  			w.WriteHeader(http.StatusNotFound)
  4969  		}
  4970  	}))
  4971  	defer ts.Close()
  4972  	uri, err = url.Parse(ts.URL)
  4973  	if err != nil {
  4974  		t.Fatalf("invalid test http server: %v", err)
  4975  	}
  4976  	repo, err = NewRepository(uri.Host + "/test")
  4977  	if err != nil {
  4978  		t.Fatalf("NewRepository() error = %v", err)
  4979  	}
  4980  	repo.PlainHTTP = true
  4981  	repo.SkipReferrersGC = true
  4982  	store = repo.Manifests()
  4983  	ctx = context.Background()
  4984  
  4985  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4986  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4987  	}
  4988  	err = store.Delete(ctx, indexManifestDesc)
  4989  	if err != nil {
  4990  		t.Fatalf("Manifests.Delete() error = %v", err)
  4991  	}
  4992  	if !manifestDeleted {
  4993  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  4994  	}
  4995  	if !bytes.Equal(gotReferrerIndex, indexJSON_3) {
  4996  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_3))
  4997  	}
  4998  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4999  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5000  	}
  5001  }
  5002  
  5003  func Test_ManifestStore_Delete_ReferrersAPIUnavailable_InconsistentIndex(t *testing.T) {
  5004  	// generate test content
  5005  	subject := []byte(`{"layers":[]}`)
  5006  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  5007  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  5008  	artifact := spec.Artifact{
  5009  		MediaType: spec.MediaTypeArtifactManifest,
  5010  		Subject:   &subjectDesc,
  5011  	}
  5012  	artifactJSON, err := json.Marshal(artifact)
  5013  	if err != nil {
  5014  		t.Fatalf("failed to marshal manifest: %v", err)
  5015  	}
  5016  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  5017  
  5018  	// test inconsistent state: index not found
  5019  	manifestDeleted := true
  5020  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5021  		switch {
  5022  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  5023  			manifestDeleted = true
  5024  			// no "Docker-Content-Digest" header for manifest deletion
  5025  			w.WriteHeader(http.StatusAccepted)
  5026  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  5027  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  5028  				t.Errorf("manifest not convertable: %s", accept)
  5029  				w.WriteHeader(http.StatusBadRequest)
  5030  				return
  5031  			}
  5032  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  5033  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  5034  			if _, err := w.Write(artifactJSON); err != nil {
  5035  				t.Errorf("failed to write %q: %v", r.URL, err)
  5036  			}
  5037  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  5038  			w.WriteHeader(http.StatusNotFound)
  5039  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  5040  			w.WriteHeader(http.StatusNotFound)
  5041  		default:
  5042  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5043  			w.WriteHeader(http.StatusNotFound)
  5044  		}
  5045  	}))
  5046  	defer ts.Close()
  5047  	uri, err := url.Parse(ts.URL)
  5048  	if err != nil {
  5049  		t.Fatalf("invalid test http server: %v", err)
  5050  	}
  5051  	repo, err := NewRepository(uri.Host + "/test")
  5052  	if err != nil {
  5053  		t.Fatalf("NewRepository() error = %v", err)
  5054  	}
  5055  	repo.PlainHTTP = true
  5056  	store := repo.Manifests()
  5057  	ctx := context.Background()
  5058  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5059  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5060  	}
  5061  	err = store.Delete(ctx, artifactDesc)
  5062  	if err != nil {
  5063  		t.Fatalf("Manifests.Delete() error = %v", err)
  5064  	}
  5065  	if !manifestDeleted {
  5066  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  5067  	}
  5068  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5069  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5070  	}
  5071  
  5072  	// test inconsistent state: empty referrers list
  5073  	manifestDeleted = true
  5074  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5075  		switch {
  5076  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  5077  			manifestDeleted = true
  5078  			// no "Docker-Content-Digest" header for manifest deletion
  5079  			w.WriteHeader(http.StatusAccepted)
  5080  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  5081  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  5082  				t.Errorf("manifest not convertable: %s", accept)
  5083  				w.WriteHeader(http.StatusBadRequest)
  5084  				return
  5085  			}
  5086  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  5087  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  5088  			if _, err := w.Write(artifactJSON); err != nil {
  5089  				t.Errorf("failed to write %q: %v", r.URL, err)
  5090  			}
  5091  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  5092  			w.WriteHeader(http.StatusNotFound)
  5093  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  5094  			result := ocispec.Index{
  5095  				Versioned: specs.Versioned{
  5096  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  5097  				},
  5098  				MediaType: ocispec.MediaTypeImageIndex,
  5099  				Manifests: []ocispec.Descriptor{},
  5100  			}
  5101  			if err := json.NewEncoder(w).Encode(result); err != nil {
  5102  				t.Errorf("failed to write response: %v", err)
  5103  			}
  5104  		default:
  5105  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5106  			w.WriteHeader(http.StatusNotFound)
  5107  		}
  5108  	}))
  5109  	defer ts.Close()
  5110  	uri, err = url.Parse(ts.URL)
  5111  	if err != nil {
  5112  		t.Fatalf("invalid test http server: %v", err)
  5113  	}
  5114  	repo, err = NewRepository(uri.Host + "/test")
  5115  	if err != nil {
  5116  		t.Fatalf("NewRepository() error = %v", err)
  5117  	}
  5118  	repo.PlainHTTP = true
  5119  	store = repo.Manifests()
  5120  	ctx = context.Background()
  5121  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5122  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5123  	}
  5124  	err = store.Delete(ctx, artifactDesc)
  5125  	if err != nil {
  5126  		t.Fatalf("Manifests.Delete() error = %v", err)
  5127  	}
  5128  	if !manifestDeleted {
  5129  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  5130  	}
  5131  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5132  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5133  	}
  5134  
  5135  	// test inconsistent state: current referrer is not in referrers list
  5136  	manifestDeleted = true
  5137  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5138  		switch {
  5139  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  5140  			manifestDeleted = true
  5141  			// no "Docker-Content-Digest" header for manifest deletion
  5142  			w.WriteHeader(http.StatusAccepted)
  5143  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  5144  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  5145  				t.Errorf("manifest not convertable: %s", accept)
  5146  				w.WriteHeader(http.StatusBadRequest)
  5147  				return
  5148  			}
  5149  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  5150  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  5151  			if _, err := w.Write(artifactJSON); err != nil {
  5152  				t.Errorf("failed to write %q: %v", r.URL, err)
  5153  			}
  5154  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  5155  			w.WriteHeader(http.StatusNotFound)
  5156  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  5157  			result := ocispec.Index{
  5158  				Versioned: specs.Versioned{
  5159  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  5160  				},
  5161  				MediaType: ocispec.MediaTypeImageIndex,
  5162  				Manifests: []ocispec.Descriptor{
  5163  					content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, []byte("whaterver")),
  5164  				},
  5165  			}
  5166  			if err := json.NewEncoder(w).Encode(result); err != nil {
  5167  				t.Errorf("failed to write response: %v", err)
  5168  			}
  5169  		default:
  5170  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5171  			w.WriteHeader(http.StatusNotFound)
  5172  		}
  5173  	}))
  5174  	defer ts.Close()
  5175  	uri, err = url.Parse(ts.URL)
  5176  	if err != nil {
  5177  		t.Fatalf("invalid test http server: %v", err)
  5178  	}
  5179  	repo, err = NewRepository(uri.Host + "/test")
  5180  	if err != nil {
  5181  		t.Fatalf("NewRepository() error = %v", err)
  5182  	}
  5183  	repo.PlainHTTP = true
  5184  	store = repo.Manifests()
  5185  	ctx = context.Background()
  5186  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5187  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5188  	}
  5189  	err = store.Delete(ctx, artifactDesc)
  5190  	if err != nil {
  5191  		t.Fatalf("Manifests.Delete() error = %v", err)
  5192  	}
  5193  	if !manifestDeleted {
  5194  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  5195  	}
  5196  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5197  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5198  	}
  5199  }
  5200  
  5201  func Test_ManifestStore_Resolve(t *testing.T) {
  5202  	manifest := []byte(`{"layers":[]}`)
  5203  	manifestDesc := ocispec.Descriptor{
  5204  		MediaType: ocispec.MediaTypeImageIndex,
  5205  		Digest:    digest.FromBytes(manifest),
  5206  		Size:      int64(len(manifest)),
  5207  	}
  5208  	ref := "foobar"
  5209  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5210  		if r.Method != http.MethodHead {
  5211  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5212  			w.WriteHeader(http.StatusMethodNotAllowed)
  5213  			return
  5214  		}
  5215  		switch r.URL.Path {
  5216  		case "/v2/test/manifests/" + manifestDesc.Digest.String(),
  5217  			"/v2/test/manifests/" + ref:
  5218  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  5219  				t.Errorf("manifest not convertable: %s", accept)
  5220  				w.WriteHeader(http.StatusBadRequest)
  5221  				return
  5222  			}
  5223  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  5224  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  5225  			w.Header().Set("Content-Length", strconv.Itoa(int(manifestDesc.Size)))
  5226  		default:
  5227  			w.WriteHeader(http.StatusNotFound)
  5228  		}
  5229  	}))
  5230  	defer ts.Close()
  5231  	uri, err := url.Parse(ts.URL)
  5232  	if err != nil {
  5233  		t.Fatalf("invalid test http server: %v", err)
  5234  	}
  5235  
  5236  	repoName := uri.Host + "/test"
  5237  	repo, err := NewRepository(repoName)
  5238  	if err != nil {
  5239  		t.Fatalf("NewRepository() error = %v", err)
  5240  	}
  5241  	repo.PlainHTTP = true
  5242  	store := repo.Manifests()
  5243  	ctx := context.Background()
  5244  
  5245  	got, err := store.Resolve(ctx, manifestDesc.Digest.String())
  5246  	if err != nil {
  5247  		t.Fatalf("Manifests.Resolve() error = %v", err)
  5248  	}
  5249  	if !reflect.DeepEqual(got, manifestDesc) {
  5250  		t.Errorf("Manifests.Resolve() = %v, want %v", got, manifestDesc)
  5251  	}
  5252  
  5253  	got, err = store.Resolve(ctx, ref)
  5254  	if err != nil {
  5255  		t.Fatalf("Manifests.Resolve() error = %v", err)
  5256  	}
  5257  	if !reflect.DeepEqual(got, manifestDesc) {
  5258  		t.Errorf("Manifests.Resolve() = %v, want %v", got, manifestDesc)
  5259  	}
  5260  
  5261  	tagDigestRef := "whatever" + "@" + manifestDesc.Digest.String()
  5262  	got, err = repo.Resolve(ctx, tagDigestRef)
  5263  	if err != nil {
  5264  		t.Fatalf("Manifests.Resolve() error = %v", err)
  5265  	}
  5266  	if !reflect.DeepEqual(got, manifestDesc) {
  5267  		t.Errorf("Manifests.Resolve() = %v, want %v", got, manifestDesc)
  5268  	}
  5269  
  5270  	fqdnRef := repoName + ":" + tagDigestRef
  5271  	got, err = repo.Resolve(ctx, fqdnRef)
  5272  	if err != nil {
  5273  		t.Fatalf("Manifests.Resolve() error = %v", err)
  5274  	}
  5275  	if !reflect.DeepEqual(got, manifestDesc) {
  5276  		t.Errorf("Manifests.Resolve() = %v, want %v", got, manifestDesc)
  5277  	}
  5278  
  5279  	content := []byte(`{"manifests":[]}`)
  5280  	contentDesc := ocispec.Descriptor{
  5281  		MediaType: ocispec.MediaTypeImageIndex,
  5282  		Digest:    digest.FromBytes(content),
  5283  		Size:      int64(len(content)),
  5284  	}
  5285  	_, err = store.Resolve(ctx, contentDesc.Digest.String())
  5286  	if !errors.Is(err, errdef.ErrNotFound) {
  5287  		t.Errorf("Manifests.Resolve() error = %v, wantErr %v", err, errdef.ErrNotFound)
  5288  	}
  5289  }
  5290  
  5291  func Test_ManifestStore_FetchReference(t *testing.T) {
  5292  	manifest := []byte(`{"layers":[]}`)
  5293  	manifestDesc := ocispec.Descriptor{
  5294  		MediaType: ocispec.MediaTypeImageIndex,
  5295  		Digest:    digest.FromBytes(manifest),
  5296  		Size:      int64(len(manifest)),
  5297  	}
  5298  	ref := "foobar"
  5299  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5300  		if r.Method != http.MethodGet {
  5301  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5302  			w.WriteHeader(http.StatusMethodNotAllowed)
  5303  			return
  5304  		}
  5305  		switch r.URL.Path {
  5306  		case "/v2/test/manifests/" + manifestDesc.Digest.String(),
  5307  			"/v2/test/manifests/" + ref:
  5308  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  5309  				t.Errorf("manifest not convertable: %s", accept)
  5310  				w.WriteHeader(http.StatusBadRequest)
  5311  				return
  5312  			}
  5313  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  5314  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  5315  			if _, err := w.Write(manifest); err != nil {
  5316  				t.Errorf("failed to write %q: %v", r.URL, err)
  5317  			}
  5318  		default:
  5319  			w.WriteHeader(http.StatusNotFound)
  5320  		}
  5321  	}))
  5322  	defer ts.Close()
  5323  	uri, err := url.Parse(ts.URL)
  5324  	if err != nil {
  5325  		t.Fatalf("invalid test http server: %v", err)
  5326  	}
  5327  
  5328  	repoName := uri.Host + "/test"
  5329  	repo, err := NewRepository(repoName)
  5330  	if err != nil {
  5331  		t.Fatalf("NewRepository() error = %v", err)
  5332  	}
  5333  	repo.PlainHTTP = true
  5334  	store := repo.Manifests()
  5335  	ctx := context.Background()
  5336  
  5337  	// test with tag
  5338  	gotDesc, rc, err := store.FetchReference(ctx, ref)
  5339  	if err != nil {
  5340  		t.Fatalf("Manifests.FetchReference() error = %v", err)
  5341  	}
  5342  	if !reflect.DeepEqual(gotDesc, manifestDesc) {
  5343  		t.Errorf("Manifests.FetchReference() = %v, want %v", gotDesc, manifestDesc)
  5344  	}
  5345  	buf := bytes.NewBuffer(nil)
  5346  	if _, err := buf.ReadFrom(rc); err != nil {
  5347  		t.Errorf("fail to read: %v", err)
  5348  	}
  5349  	if err := rc.Close(); err != nil {
  5350  		t.Errorf("fail to close: %v", err)
  5351  	}
  5352  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  5353  		t.Errorf("Manifests.FetchReference() = %v, want %v", got, manifest)
  5354  	}
  5355  
  5356  	// test with other tag
  5357  	randomRef := "whatever"
  5358  	_, _, err = store.FetchReference(ctx, randomRef)
  5359  	if !errors.Is(err, errdef.ErrNotFound) {
  5360  		t.Errorf("Manifests.FetchReference() error = %v, wantErr %v", err, errdef.ErrNotFound)
  5361  	}
  5362  
  5363  	// test with digest
  5364  	gotDesc, rc, err = store.FetchReference(ctx, manifestDesc.Digest.String())
  5365  	if err != nil {
  5366  		t.Fatalf("Manifests.FetchReference() error = %v", err)
  5367  	}
  5368  	if !reflect.DeepEqual(gotDesc, manifestDesc) {
  5369  		t.Errorf("Manifests.FetchReference() = %v, want %v", gotDesc, manifestDesc)
  5370  	}
  5371  	buf.Reset()
  5372  	if _, err := buf.ReadFrom(rc); err != nil {
  5373  		t.Errorf("fail to read: %v", err)
  5374  	}
  5375  	if err := rc.Close(); err != nil {
  5376  		t.Errorf("fail to close: %v", err)
  5377  	}
  5378  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  5379  		t.Errorf("Manifests.FetchReference() = %v, want %v", got, manifest)
  5380  	}
  5381  
  5382  	// test with other digest
  5383  	randomContent := []byte("whatever")
  5384  	randomContentDigest := digest.FromBytes(randomContent)
  5385  	_, _, err = store.FetchReference(ctx, randomContentDigest.String())
  5386  	if !errors.Is(err, errdef.ErrNotFound) {
  5387  		t.Errorf("Manifests.FetchReference() error = %v, wantErr %v", err, errdef.ErrNotFound)
  5388  	}
  5389  
  5390  	// test with tag@digest
  5391  	tagDigestRef := randomRef + "@" + manifestDesc.Digest.String()
  5392  	gotDesc, rc, err = store.FetchReference(ctx, tagDigestRef)
  5393  	if err != nil {
  5394  		t.Fatalf("Manifests.FetchReference() error = %v", err)
  5395  	}
  5396  	if !reflect.DeepEqual(gotDesc, manifestDesc) {
  5397  		t.Errorf("Manifests.FetchReference() = %v, want %v", gotDesc, manifestDesc)
  5398  	}
  5399  	buf.Reset()
  5400  	if _, err := buf.ReadFrom(rc); err != nil {
  5401  		t.Errorf("fail to read: %v", err)
  5402  	}
  5403  	if err := rc.Close(); err != nil {
  5404  		t.Errorf("fail to close: %v", err)
  5405  	}
  5406  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  5407  		t.Errorf("Manifests.FetchReference() = %v, want %v", got, manifest)
  5408  	}
  5409  
  5410  	// test with FQDN
  5411  	fqdnRef := repoName + ":" + tagDigestRef
  5412  	gotDesc, rc, err = store.FetchReference(ctx, fqdnRef)
  5413  	if err != nil {
  5414  		t.Fatalf("Manifests.FetchReference() error = %v", err)
  5415  	}
  5416  	if !reflect.DeepEqual(gotDesc, manifestDesc) {
  5417  		t.Errorf("Manifests.FetchReference() = %v, want %v", gotDesc, manifestDesc)
  5418  	}
  5419  	buf.Reset()
  5420  	if _, err := buf.ReadFrom(rc); err != nil {
  5421  		t.Errorf("fail to read: %v", err)
  5422  	}
  5423  	if err := rc.Close(); err != nil {
  5424  		t.Errorf("fail to close: %v", err)
  5425  	}
  5426  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  5427  		t.Errorf("Manifests.FetchReference() = %v, want %v", got, manifest)
  5428  	}
  5429  }
  5430  
  5431  func Test_ManifestStore_Tag(t *testing.T) {
  5432  	blob := []byte("hello world")
  5433  	blobDesc := ocispec.Descriptor{
  5434  		MediaType: "test",
  5435  		Digest:    digest.FromBytes(blob),
  5436  		Size:      int64(len(blob)),
  5437  	}
  5438  	index := []byte(`{"manifests":[]}`)
  5439  	indexDesc := ocispec.Descriptor{
  5440  		MediaType: ocispec.MediaTypeImageIndex,
  5441  		Digest:    digest.FromBytes(index),
  5442  		Size:      int64(len(index)),
  5443  	}
  5444  	var gotIndex []byte
  5445  	ref := "foobar"
  5446  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5447  		switch {
  5448  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+blobDesc.Digest.String():
  5449  			w.WriteHeader(http.StatusNotFound)
  5450  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
  5451  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
  5452  				t.Errorf("manifest not convertable: %s", accept)
  5453  				w.WriteHeader(http.StatusBadRequest)
  5454  				return
  5455  			}
  5456  			w.Header().Set("Content-Type", indexDesc.MediaType)
  5457  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  5458  			if _, err := w.Write(index); err != nil {
  5459  				t.Errorf("failed to write %q: %v", r.URL, err)
  5460  			}
  5461  		case r.Method == http.MethodPut &&
  5462  			r.URL.Path == "/v2/test/manifests/"+ref || r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
  5463  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
  5464  				w.WriteHeader(http.StatusBadRequest)
  5465  				break
  5466  			}
  5467  			buf := bytes.NewBuffer(nil)
  5468  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5469  				t.Errorf("fail to read: %v", err)
  5470  			}
  5471  			gotIndex = buf.Bytes()
  5472  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  5473  			w.WriteHeader(http.StatusCreated)
  5474  			return
  5475  		default:
  5476  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5477  			w.WriteHeader(http.StatusForbidden)
  5478  		}
  5479  	}))
  5480  	defer ts.Close()
  5481  	uri, err := url.Parse(ts.URL)
  5482  	if err != nil {
  5483  		t.Fatalf("invalid test http server: %v", err)
  5484  	}
  5485  
  5486  	repo, err := NewRepository(uri.Host + "/test")
  5487  	if err != nil {
  5488  		t.Fatalf("NewRepository() error = %v", err)
  5489  	}
  5490  	store := repo.Manifests()
  5491  	repo.PlainHTTP = true
  5492  	ctx := context.Background()
  5493  
  5494  	err = store.Tag(ctx, blobDesc, ref)
  5495  	if err == nil {
  5496  		t.Errorf("Repository.Tag() error = %v, wantErr %v", err, true)
  5497  	}
  5498  
  5499  	err = store.Tag(ctx, indexDesc, ref)
  5500  	if err != nil {
  5501  		t.Fatalf("Repository.Tag() error = %v", err)
  5502  	}
  5503  	if !bytes.Equal(gotIndex, index) {
  5504  		t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index)
  5505  	}
  5506  
  5507  	gotIndex = nil
  5508  	err = store.Tag(ctx, indexDesc, indexDesc.Digest.String())
  5509  	if err != nil {
  5510  		t.Fatalf("Repository.Tag() error = %v", err)
  5511  	}
  5512  	if !bytes.Equal(gotIndex, index) {
  5513  		t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index)
  5514  	}
  5515  }
  5516  
  5517  func Test_ManifestStore_PushReference(t *testing.T) {
  5518  	index := []byte(`{"manifests":[]}`)
  5519  	indexDesc := ocispec.Descriptor{
  5520  		MediaType: ocispec.MediaTypeImageIndex,
  5521  		Digest:    digest.FromBytes(index),
  5522  		Size:      int64(len(index)),
  5523  	}
  5524  	var gotIndex []byte
  5525  	ref := "foobar"
  5526  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5527  		switch {
  5528  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+ref:
  5529  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
  5530  				w.WriteHeader(http.StatusBadRequest)
  5531  				break
  5532  			}
  5533  			buf := bytes.NewBuffer(nil)
  5534  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5535  				t.Errorf("fail to read: %v", err)
  5536  			}
  5537  			gotIndex = buf.Bytes()
  5538  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  5539  			w.WriteHeader(http.StatusCreated)
  5540  			return
  5541  		default:
  5542  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5543  			w.WriteHeader(http.StatusForbidden)
  5544  		}
  5545  	}))
  5546  	defer ts.Close()
  5547  	uri, err := url.Parse(ts.URL)
  5548  	if err != nil {
  5549  		t.Fatalf("invalid test http server: %v", err)
  5550  	}
  5551  
  5552  	repo, err := NewRepository(uri.Host + "/test")
  5553  	if err != nil {
  5554  		t.Fatalf("NewRepository() error = %v", err)
  5555  	}
  5556  	store := repo.Manifests()
  5557  	repo.PlainHTTP = true
  5558  	ctx := context.Background()
  5559  	err = store.PushReference(ctx, indexDesc, bytes.NewReader(index), ref)
  5560  	if err != nil {
  5561  		t.Fatalf("Repository.PushReference() error = %v", err)
  5562  	}
  5563  	if !bytes.Equal(gotIndex, index) {
  5564  		t.Errorf("Repository.PushReference() = %v, want %v", gotIndex, index)
  5565  	}
  5566  }
  5567  
  5568  func Test_ManifestStore_PushReference_ReferrersAPIAvailable(t *testing.T) {
  5569  	// generate test content
  5570  	subject := []byte(`{"layers":[]}`)
  5571  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  5572  	artifact := spec.Artifact{
  5573  		MediaType: spec.MediaTypeArtifactManifest,
  5574  		Subject:   &subjectDesc,
  5575  	}
  5576  	artifactJSON, err := json.Marshal(artifact)
  5577  	if err != nil {
  5578  		t.Fatalf("failed to marshal manifest: %v", err)
  5579  	}
  5580  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  5581  	artifactRef := "foo"
  5582  
  5583  	manifest := ocispec.Manifest{
  5584  		MediaType: ocispec.MediaTypeImageManifest,
  5585  		Subject:   &subjectDesc,
  5586  	}
  5587  	manifestJSON, err := json.Marshal(manifest)
  5588  	if err != nil {
  5589  		t.Fatalf("failed to marshal manifest: %v", err)
  5590  	}
  5591  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  5592  	manifestRef := "bar"
  5593  
  5594  	index := ocispec.Index{
  5595  		MediaType: ocispec.MediaTypeImageIndex,
  5596  		Subject:   &subjectDesc,
  5597  	}
  5598  	indexJSON, err := json.Marshal(index)
  5599  	if err != nil {
  5600  		t.Fatalf("failed to marshal manifest: %v", err)
  5601  	}
  5602  	indexDesc := content.NewDescriptorFromBytes(index.MediaType, indexJSON)
  5603  	indexRef := "baz"
  5604  
  5605  	var gotManifest []byte
  5606  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5607  		switch {
  5608  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactRef:
  5609  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  5610  				w.WriteHeader(http.StatusBadRequest)
  5611  				break
  5612  			}
  5613  			buf := bytes.NewBuffer(nil)
  5614  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5615  				t.Errorf("fail to read: %v", err)
  5616  			}
  5617  			gotManifest = buf.Bytes()
  5618  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  5619  			w.Header().Set("OCI-Subject", subjectDesc.Digest.String())
  5620  			w.WriteHeader(http.StatusCreated)
  5621  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestRef:
  5622  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  5623  				w.WriteHeader(http.StatusBadRequest)
  5624  				break
  5625  			}
  5626  			buf := bytes.NewBuffer(nil)
  5627  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5628  				t.Errorf("fail to read: %v", err)
  5629  			}
  5630  			gotManifest = buf.Bytes()
  5631  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  5632  			w.Header().Set("OCI-Subject", subjectDesc.Digest.String())
  5633  			w.WriteHeader(http.StatusCreated)
  5634  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexRef:
  5635  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
  5636  				w.WriteHeader(http.StatusBadRequest)
  5637  				break
  5638  			}
  5639  			buf := bytes.NewBuffer(nil)
  5640  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5641  				t.Errorf("fail to read: %v", err)
  5642  			}
  5643  			gotManifest = buf.Bytes()
  5644  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  5645  			w.Header().Set("OCI-Subject", subjectDesc.Digest.String())
  5646  			w.WriteHeader(http.StatusCreated)
  5647  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  5648  			result := ocispec.Index{
  5649  				Versioned: specs.Versioned{
  5650  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  5651  				},
  5652  				MediaType: ocispec.MediaTypeImageIndex,
  5653  				Manifests: []ocispec.Descriptor{},
  5654  			}
  5655  			if err := json.NewEncoder(w).Encode(result); err != nil {
  5656  				t.Errorf("failed to write response: %v", err)
  5657  			}
  5658  		default:
  5659  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5660  			w.WriteHeader(http.StatusNotFound)
  5661  		}
  5662  	}))
  5663  	defer ts.Close()
  5664  	uri, err := url.Parse(ts.URL)
  5665  	if err != nil {
  5666  		t.Fatalf("invalid test http server: %v", err)
  5667  	}
  5668  	ctx := context.Background()
  5669  
  5670  	// test pushing artifact with subject
  5671  	repo, err := NewRepository(uri.Host + "/test")
  5672  	if err != nil {
  5673  		t.Fatalf("NewRepository() error = %v", err)
  5674  	}
  5675  	repo.PlainHTTP = true
  5676  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5677  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5678  	}
  5679  	err = repo.PushReference(ctx, artifactDesc, bytes.NewReader(artifactJSON), artifactRef)
  5680  	if err != nil {
  5681  		t.Fatalf("Manifests.Push() error = %v", err)
  5682  	}
  5683  	if !bytes.Equal(gotManifest, artifactJSON) {
  5684  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  5685  	}
  5686  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5687  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5688  	}
  5689  
  5690  	// test pushing image manifest with subject
  5691  	repo, err = NewRepository(uri.Host + "/test")
  5692  	if err != nil {
  5693  		t.Fatalf("NewRepository() error = %v", err)
  5694  	}
  5695  	repo.PlainHTTP = true
  5696  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5697  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5698  	}
  5699  	err = repo.PushReference(ctx, manifestDesc, bytes.NewReader(manifestJSON), manifestRef)
  5700  	if err != nil {
  5701  		t.Fatalf("Manifests.Push() error = %v", err)
  5702  	}
  5703  	if !bytes.Equal(gotManifest, manifestJSON) {
  5704  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  5705  	}
  5706  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5707  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5708  	}
  5709  
  5710  	// test pushing image index with subject
  5711  	err = repo.PushReference(ctx, indexDesc, bytes.NewReader(indexJSON), indexRef)
  5712  	if err != nil {
  5713  		t.Fatalf("Manifests.Push() error = %v", err)
  5714  	}
  5715  	if !bytes.Equal(gotManifest, indexJSON) {
  5716  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(indexJSON))
  5717  	}
  5718  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5719  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5720  	}
  5721  }
  5722  
  5723  func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) {
  5724  	// generate test content
  5725  	subject := []byte(`{"layers":[]}`)
  5726  	subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject)
  5727  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  5728  	artifact := spec.Artifact{
  5729  		MediaType:    spec.MediaTypeArtifactManifest,
  5730  		Subject:      &subjectDesc,
  5731  		ArtifactType: "application/vnd.test",
  5732  		Annotations:  map[string]string{"foo": "bar"},
  5733  	}
  5734  	artifactJSON, err := json.Marshal(artifact)
  5735  	if err != nil {
  5736  		t.Fatalf("failed to marshal manifest: %v", err)
  5737  	}
  5738  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  5739  	artifactDesc.ArtifactType = artifact.ArtifactType
  5740  	artifactDesc.Annotations = artifact.Annotations
  5741  	artifactRef := "foo"
  5742  
  5743  	// test pushing artifact with subject
  5744  	index_1 := ocispec.Index{
  5745  		Versioned: specs.Versioned{
  5746  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  5747  		},
  5748  		MediaType: ocispec.MediaTypeImageIndex,
  5749  		Manifests: []ocispec.Descriptor{
  5750  			artifactDesc,
  5751  		},
  5752  	}
  5753  	indexJSON_1, err := json.Marshal(index_1)
  5754  	if err != nil {
  5755  		t.Fatalf("failed to marshal manifest: %v", err)
  5756  	}
  5757  	indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1)
  5758  	var gotManifest []byte
  5759  	var gotReferrerIndex []byte
  5760  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5761  		switch {
  5762  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactRef:
  5763  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  5764  				w.WriteHeader(http.StatusBadRequest)
  5765  				break
  5766  			}
  5767  			buf := bytes.NewBuffer(nil)
  5768  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5769  				t.Errorf("fail to read: %v", err)
  5770  			}
  5771  			gotManifest = buf.Bytes()
  5772  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  5773  			w.WriteHeader(http.StatusCreated)
  5774  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  5775  			w.WriteHeader(http.StatusNotFound)
  5776  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  5777  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  5778  				w.WriteHeader(http.StatusBadRequest)
  5779  				break
  5780  			}
  5781  			buf := bytes.NewBuffer(nil)
  5782  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5783  				t.Errorf("fail to read: %v", err)
  5784  			}
  5785  			gotReferrerIndex = buf.Bytes()
  5786  			w.Header().Set("Docker-Content-Digest", indexDesc_1.Digest.String())
  5787  			w.WriteHeader(http.StatusCreated)
  5788  		default:
  5789  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5790  			w.WriteHeader(http.StatusNotFound)
  5791  		}
  5792  	}))
  5793  	defer ts.Close()
  5794  	uri, err := url.Parse(ts.URL)
  5795  	if err != nil {
  5796  		t.Fatalf("invalid test http server: %v", err)
  5797  	}
  5798  
  5799  	ctx := context.Background()
  5800  	repo, err := NewRepository(uri.Host + "/test")
  5801  	if err != nil {
  5802  		t.Fatalf("NewRepository() error = %v", err)
  5803  	}
  5804  	repo.PlainHTTP = true
  5805  
  5806  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5807  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5808  	}
  5809  	err = repo.PushReference(ctx, artifactDesc, bytes.NewReader(artifactJSON), artifactRef)
  5810  	if err != nil {
  5811  		t.Fatalf("Manifests.Push() error = %v", err)
  5812  	}
  5813  	if !bytes.Equal(gotManifest, artifactJSON) {
  5814  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  5815  	}
  5816  	if !bytes.Equal(gotReferrerIndex, indexJSON_1) {
  5817  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1))
  5818  	}
  5819  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5820  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5821  	}
  5822  
  5823  	// test pushing image manifest with subject, referrers list should be updated
  5824  	manifest := ocispec.Manifest{
  5825  		MediaType: ocispec.MediaTypeImageManifest,
  5826  		Config: ocispec.Descriptor{
  5827  			MediaType: "testconfig",
  5828  		},
  5829  		Subject:     &subjectDesc,
  5830  		Annotations: map[string]string{"foo": "bar"},
  5831  	}
  5832  	manifestJSON, err := json.Marshal(manifest)
  5833  	if err != nil {
  5834  		t.Fatalf("failed to marshal manifest: %v", err)
  5835  	}
  5836  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  5837  	manifestDesc.ArtifactType = manifest.Config.MediaType
  5838  	manifestDesc.Annotations = manifest.Annotations
  5839  	manifestRef := "bar"
  5840  
  5841  	index_2 := ocispec.Index{
  5842  		Versioned: specs.Versioned{
  5843  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  5844  		},
  5845  		MediaType: ocispec.MediaTypeImageIndex,
  5846  		Manifests: []ocispec.Descriptor{
  5847  			artifactDesc,
  5848  			manifestDesc,
  5849  		},
  5850  	}
  5851  	indexJSON_2, err := json.Marshal(index_2)
  5852  	if err != nil {
  5853  		t.Fatalf("failed to marshal manifest: %v", err)
  5854  	}
  5855  	indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2)
  5856  	var manifestDeleted bool
  5857  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5858  		switch {
  5859  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestRef:
  5860  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  5861  				w.WriteHeader(http.StatusBadRequest)
  5862  				break
  5863  			}
  5864  			buf := bytes.NewBuffer(nil)
  5865  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5866  				t.Errorf("fail to read: %v", err)
  5867  			}
  5868  			gotManifest = buf.Bytes()
  5869  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  5870  			w.WriteHeader(http.StatusCreated)
  5871  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  5872  			w.Write(indexJSON_1)
  5873  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  5874  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  5875  				w.WriteHeader(http.StatusBadRequest)
  5876  				break
  5877  			}
  5878  			buf := bytes.NewBuffer(nil)
  5879  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5880  				t.Errorf("fail to read: %v", err)
  5881  			}
  5882  			gotReferrerIndex = buf.Bytes()
  5883  			w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String())
  5884  			w.WriteHeader(http.StatusCreated)
  5885  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_1.Digest.String():
  5886  			manifestDeleted = true
  5887  			// no "Docker-Content-Digest" header for manifest deletion
  5888  			w.WriteHeader(http.StatusAccepted)
  5889  		default:
  5890  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5891  			w.WriteHeader(http.StatusNotFound)
  5892  		}
  5893  	}))
  5894  	defer ts.Close()
  5895  	uri, err = url.Parse(ts.URL)
  5896  	if err != nil {
  5897  		t.Fatalf("invalid test http server: %v", err)
  5898  	}
  5899  
  5900  	ctx = context.Background()
  5901  	repo, err = NewRepository(uri.Host + "/test")
  5902  	if err != nil {
  5903  		t.Fatalf("NewRepository() error = %v", err)
  5904  	}
  5905  	repo.PlainHTTP = true
  5906  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5907  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5908  	}
  5909  	err = repo.PushReference(ctx, manifestDesc, bytes.NewReader(manifestJSON), manifestRef)
  5910  	if err != nil {
  5911  		t.Fatalf("Manifests.PushReference() error = %v", err)
  5912  	}
  5913  	if !bytes.Equal(gotManifest, manifestJSON) {
  5914  		t.Errorf("Manifests.PushReference() = %v, want %v", string(gotManifest), string(manifestJSON))
  5915  	}
  5916  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  5917  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  5918  	}
  5919  	if !manifestDeleted {
  5920  		t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, true)
  5921  	}
  5922  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5923  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5924  	}
  5925  
  5926  	// test pushing image manifest with subject again, referrers list should not be changed
  5927  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5928  		switch {
  5929  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestRef:
  5930  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  5931  				w.WriteHeader(http.StatusBadRequest)
  5932  				break
  5933  			}
  5934  			buf := bytes.NewBuffer(nil)
  5935  			if _, err := buf.ReadFrom(r.Body); err != nil {
  5936  				t.Errorf("fail to read: %v", err)
  5937  			}
  5938  			gotManifest = buf.Bytes()
  5939  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  5940  			w.WriteHeader(http.StatusCreated)
  5941  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  5942  			w.Write(indexJSON_2)
  5943  		default:
  5944  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5945  			w.WriteHeader(http.StatusNotFound)
  5946  		}
  5947  	}))
  5948  	defer ts.Close()
  5949  	uri, err = url.Parse(ts.URL)
  5950  	if err != nil {
  5951  		t.Fatalf("invalid test http server: %v", err)
  5952  	}
  5953  
  5954  	ctx = context.Background()
  5955  	repo, err = NewRepository(uri.Host + "/test")
  5956  	if err != nil {
  5957  		t.Fatalf("NewRepository() error = %v", err)
  5958  	}
  5959  	repo.PlainHTTP = true
  5960  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5961  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5962  	}
  5963  	err = repo.PushReference(ctx, manifestDesc, bytes.NewReader(manifestJSON), manifestRef)
  5964  	if err != nil {
  5965  		t.Fatalf("Manifests.PushReference() error = %v", err)
  5966  	}
  5967  	if !bytes.Equal(gotManifest, manifestJSON) {
  5968  		t.Errorf("Manifests.PushReference() = %v, want %v", string(gotManifest), string(manifestJSON))
  5969  	}
  5970  	// referrers list should not be changed
  5971  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  5972  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  5973  	}
  5974  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5975  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5976  	}
  5977  
  5978  	// push image index with subject, referrer list should be updated
  5979  	indexManifest := ocispec.Index{
  5980  		MediaType:    ocispec.MediaTypeImageIndex,
  5981  		Subject:      &subjectDesc,
  5982  		ArtifactType: "test/index",
  5983  		Annotations:  map[string]string{"foo": "bar"},
  5984  	}
  5985  	indexManifestJSON, err := json.Marshal(indexManifest)
  5986  	if err != nil {
  5987  		t.Fatalf("failed to marshal manifest: %v", err)
  5988  	}
  5989  	indexManifestDesc := content.NewDescriptorFromBytes(indexManifest.MediaType, indexManifestJSON)
  5990  	indexManifestDesc.ArtifactType = indexManifest.ArtifactType
  5991  	indexManifestDesc.Annotations = indexManifest.Annotations
  5992  	indexManifestRef := "baz"
  5993  	index_3 := ocispec.Index{
  5994  		Versioned: specs.Versioned{
  5995  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  5996  		},
  5997  		MediaType: ocispec.MediaTypeImageIndex,
  5998  		Manifests: []ocispec.Descriptor{
  5999  			artifactDesc,
  6000  			manifestDesc,
  6001  			indexManifestDesc,
  6002  		},
  6003  	}
  6004  	indexJSON_3, err := json.Marshal(index_3)
  6005  	if err != nil {
  6006  		t.Fatalf("failed to marshal manifest: %v", err)
  6007  	}
  6008  	indexDesc_3 := content.NewDescriptorFromBytes(index_3.MediaType, indexJSON_3)
  6009  	manifestDeleted = false
  6010  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6011  		switch {
  6012  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexManifestRef:
  6013  			if contentType := r.Header.Get("Content-Type"); contentType != indexManifestDesc.MediaType {
  6014  				w.WriteHeader(http.StatusBadRequest)
  6015  				break
  6016  			}
  6017  			buf := bytes.NewBuffer(nil)
  6018  			if _, err := buf.ReadFrom(r.Body); err != nil {
  6019  				t.Errorf("fail to read: %v", err)
  6020  			}
  6021  			gotManifest = buf.Bytes()
  6022  			w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String())
  6023  			w.WriteHeader(http.StatusCreated)
  6024  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  6025  			w.Write(indexJSON_2)
  6026  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  6027  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  6028  				w.WriteHeader(http.StatusBadRequest)
  6029  				break
  6030  			}
  6031  			buf := bytes.NewBuffer(nil)
  6032  			if _, err := buf.ReadFrom(r.Body); err != nil {
  6033  				t.Errorf("fail to read: %v", err)
  6034  			}
  6035  			gotReferrerIndex = buf.Bytes()
  6036  			w.Header().Set("Docker-Content-Digest", indexDesc_3.Digest.String())
  6037  			w.WriteHeader(http.StatusCreated)
  6038  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_2.Digest.String():
  6039  			manifestDeleted = true
  6040  			// no "Docker-Content-Digest" header for manifest deletion
  6041  			w.WriteHeader(http.StatusAccepted)
  6042  		default:
  6043  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  6044  			w.WriteHeader(http.StatusNotFound)
  6045  		}
  6046  	}))
  6047  	defer ts.Close()
  6048  	uri, err = url.Parse(ts.URL)
  6049  	if err != nil {
  6050  		t.Fatalf("invalid test http server: %v", err)
  6051  	}
  6052  
  6053  	ctx = context.Background()
  6054  	repo, err = NewRepository(uri.Host + "/test")
  6055  	if err != nil {
  6056  		t.Fatalf("NewRepository() error = %v", err)
  6057  	}
  6058  	repo.PlainHTTP = true
  6059  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  6060  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  6061  	}
  6062  	err = repo.PushReference(ctx, indexManifestDesc, bytes.NewReader(indexManifestJSON), indexManifestRef)
  6063  	if err != nil {
  6064  		t.Fatalf("Manifests.PushReference() error = %v", err)
  6065  	}
  6066  	if !bytes.Equal(gotManifest, indexManifestJSON) {
  6067  		t.Errorf("Manifests.PushReference() = %v, want %v", string(gotManifest), string(indexManifestJSON))
  6068  	}
  6069  	if !bytes.Equal(gotReferrerIndex, indexJSON_3) {
  6070  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_3))
  6071  	}
  6072  	if !manifestDeleted {
  6073  		t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, true)
  6074  	}
  6075  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  6076  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  6077  	}
  6078  }
  6079  
  6080  func Test_ManifestStore_generateDescriptorWithVariousDockerContentDigestHeaders(t *testing.T) {
  6081  	reference := registry.Reference{
  6082  		Registry:   "eastern.haan.com",
  6083  		Reference:  "<calculate>",
  6084  		Repository: "from25to220ce",
  6085  	}
  6086  
  6087  	tests := getTestIOStructMapForGetDescriptorClass()
  6088  	for testName, dcdIOStruct := range tests {
  6089  		repo, err := NewRepository(fmt.Sprintf("%s/%s", reference.Repository, reference.Repository))
  6090  		if err != nil {
  6091  			t.Fatalf("failed to initialize repository")
  6092  		}
  6093  
  6094  		s := manifestStore{repo: repo}
  6095  
  6096  		for i, method := range []string{http.MethodGet, http.MethodHead} {
  6097  			reference.Reference = dcdIOStruct.clientSuppliedReference
  6098  
  6099  			resp := http.Response{
  6100  				Header: http.Header{
  6101  					"Content-Type":            []string{"application/vnd.docker.distribution.manifest.v2+json"},
  6102  					headerDockerContentDigest: []string{dcdIOStruct.serverCalculatedDigest.String()},
  6103  				},
  6104  			}
  6105  			if method == http.MethodGet {
  6106  				resp.Body = io.NopCloser(bytes.NewBufferString(theAmazingBanClan))
  6107  			}
  6108  			resp.Request = &http.Request{
  6109  				Method: method,
  6110  			}
  6111  
  6112  			errExpected := []bool{dcdIOStruct.errExpectedOnGET, dcdIOStruct.errExpectedOnHEAD}[i]
  6113  			_, err = s.generateDescriptor(&resp, reference, method)
  6114  			if !errExpected && err != nil {
  6115  				t.Errorf(
  6116  					"[Manifest.%v] %v; expected no error for request, but got err: %v",
  6117  					method, testName, err,
  6118  				)
  6119  			} else if errExpected && err == nil {
  6120  				t.Errorf(
  6121  					"[Manifest.%v] %v; expected an error for request, but got none",
  6122  					method, testName,
  6123  				)
  6124  			}
  6125  		}
  6126  	}
  6127  }
  6128  
  6129  type testTransport struct {
  6130  	proxyHost           string
  6131  	underlyingTransport http.RoundTripper
  6132  	mockHost            string
  6133  }
  6134  
  6135  func (t *testTransport) RoundTrip(originalReq *http.Request) (*http.Response, error) {
  6136  	req := originalReq.Clone(originalReq.Context())
  6137  	mockHostName, mockPort, err := net.SplitHostPort(t.mockHost)
  6138  	// when t.mockHost is as form host:port
  6139  	if err == nil && (req.URL.Hostname() != mockHostName || req.URL.Port() != mockPort) {
  6140  		return nil, errors.New("bad request")
  6141  	}
  6142  	// when t.mockHost does not have specified port, in this case,
  6143  	// err is not nil
  6144  	if err != nil && req.URL.Hostname() != t.mockHost {
  6145  		return nil, errors.New("bad request")
  6146  	}
  6147  	req.Host = t.proxyHost
  6148  	req.URL.Host = t.proxyHost
  6149  	resp, err := t.underlyingTransport.RoundTrip(req)
  6150  	if err != nil {
  6151  		return nil, err
  6152  	}
  6153  	resp.Request.Host = t.mockHost
  6154  	resp.Request.URL.Host = t.mockHost
  6155  	return resp, nil
  6156  }
  6157  
  6158  // Helper function to create a registry.BlobStore for
  6159  // Test_BlobStore_Push_Port443
  6160  func blobStore_Push_Port443_create_store(uri *url.URL, testRegistry string) (registry.BlobStore, error) {
  6161  	repo, err := NewRepository(testRegistry + "/test")
  6162  	repo.Client = &auth.Client{
  6163  		Client: &http.Client{
  6164  			Transport: &testTransport{
  6165  				proxyHost:           uri.Host,
  6166  				underlyingTransport: http.DefaultTransport,
  6167  				mockHost:            testRegistry,
  6168  			},
  6169  		},
  6170  		Cache: auth.NewCache(),
  6171  	}
  6172  	repo.PlainHTTP = true
  6173  	store := repo.Blobs()
  6174  	return store, err
  6175  }
  6176  
  6177  func Test_BlobStore_Push_Port443(t *testing.T) {
  6178  	blob := []byte("hello world")
  6179  	blobDesc := ocispec.Descriptor{
  6180  		MediaType: "test",
  6181  		Digest:    digest.FromBytes(blob),
  6182  		Size:      int64(len(blob)),
  6183  	}
  6184  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
  6185  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6186  		switch {
  6187  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
  6188  			w.Header().Set("Location", "http://registry.wabbit-networks.io/v2/test/blobs/uploads/"+uuid)
  6189  			w.WriteHeader(http.StatusAccepted)
  6190  			return
  6191  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
  6192  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
  6193  				w.WriteHeader(http.StatusBadRequest)
  6194  				break
  6195  			}
  6196  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
  6197  				w.WriteHeader(http.StatusBadRequest)
  6198  				break
  6199  			}
  6200  			buf := bytes.NewBuffer(nil)
  6201  			if _, err := buf.ReadFrom(r.Body); err != nil {
  6202  				t.Errorf("fail to read: %v", err)
  6203  			}
  6204  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  6205  			w.WriteHeader(http.StatusCreated)
  6206  			return
  6207  		default:
  6208  			w.WriteHeader(http.StatusForbidden)
  6209  		}
  6210  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  6211  	}))
  6212  	defer ts.Close()
  6213  	uri, err := url.Parse(ts.URL)
  6214  	if err != nil {
  6215  		t.Fatalf("invalid test http server: %v", err)
  6216  	}
  6217  
  6218  	// Test case with Host: "registry.wabbit-networks.io:443",
  6219  	// Location: "registry.wabbit-networks.io"
  6220  	testRegistry := "registry.wabbit-networks.io:443"
  6221  	store, err := blobStore_Push_Port443_create_store(uri, testRegistry)
  6222  	if err != nil {
  6223  		t.Fatalf("blobStore_Push_Port443_create_store() error = %v", err)
  6224  	}
  6225  	ctx := context.Background()
  6226  
  6227  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  6228  	if err != nil {
  6229  		t.Fatalf("Blobs.Push() error = %v", err)
  6230  	}
  6231  
  6232  	// Test case with Host: "registry.wabbit-networks.io",
  6233  	// Location: "registry.wabbit-networks.io"
  6234  	testRegistry = "registry.wabbit-networks.io"
  6235  	store, err = blobStore_Push_Port443_create_store(uri, testRegistry)
  6236  	if err != nil {
  6237  		t.Fatalf("blobStore_Push_Port443_create_store() error = %v", err)
  6238  	}
  6239  
  6240  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  6241  	if err != nil {
  6242  		t.Fatalf("Blobs.Push() error = %v", err)
  6243  	}
  6244  }
  6245  
  6246  // Helper function to create a registry.BlobStore for
  6247  // Test_BlobStore_Push_Port443_HTTPS
  6248  func blobStore_Push_Port443_HTTPS_create_store(uri *url.URL, testRegistry string) (registry.BlobStore, error) {
  6249  	repo, err := NewRepository(testRegistry + "/test")
  6250  	tlsConfig := &tls.Config{
  6251  		InsecureSkipVerify: true,
  6252  	}
  6253  	transport := &http.Transport{
  6254  		TLSClientConfig: tlsConfig,
  6255  	}
  6256  	repo.Client = &auth.Client{
  6257  		Client: &http.Client{
  6258  			Transport: &testTransport{
  6259  				proxyHost:           uri.Host,
  6260  				underlyingTransport: transport,
  6261  				mockHost:            testRegistry,
  6262  			},
  6263  		},
  6264  		Cache: auth.NewCache(),
  6265  	}
  6266  	repo.PlainHTTP = false
  6267  	store := repo.Blobs()
  6268  	return store, err
  6269  }
  6270  
  6271  func Test_BlobStore_Push_Port443_HTTPS(t *testing.T) {
  6272  	blob := []byte("hello world")
  6273  	blobDesc := ocispec.Descriptor{
  6274  		MediaType: "test",
  6275  		Digest:    digest.FromBytes(blob),
  6276  		Size:      int64(len(blob)),
  6277  	}
  6278  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
  6279  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6280  		switch {
  6281  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
  6282  			w.Header().Set("Location", "https://registry.wabbit-networks.io/v2/test/blobs/uploads/"+uuid)
  6283  			w.WriteHeader(http.StatusAccepted)
  6284  			return
  6285  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
  6286  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
  6287  				w.WriteHeader(http.StatusBadRequest)
  6288  				break
  6289  			}
  6290  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
  6291  				w.WriteHeader(http.StatusBadRequest)
  6292  				break
  6293  			}
  6294  			buf := bytes.NewBuffer(nil)
  6295  			if _, err := buf.ReadFrom(r.Body); err != nil {
  6296  				t.Errorf("fail to read: %v", err)
  6297  			}
  6298  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  6299  			w.WriteHeader(http.StatusCreated)
  6300  			return
  6301  		default:
  6302  			w.WriteHeader(http.StatusForbidden)
  6303  		}
  6304  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  6305  	}))
  6306  	defer ts.Close()
  6307  	uri, err := url.Parse(ts.URL)
  6308  	if err != nil {
  6309  		t.Fatalf("invalid test https server: %v", err)
  6310  	}
  6311  
  6312  	ctx := context.Background()
  6313  	// Test case with Host: "registry.wabbit-networks.io:443",
  6314  	// Location: "registry.wabbit-networks.io"
  6315  	testRegistry := "registry.wabbit-networks.io:443"
  6316  	store, err := blobStore_Push_Port443_HTTPS_create_store(uri, testRegistry)
  6317  	if err != nil {
  6318  		t.Fatalf("blobStore_Push_Port443_HTTPS_create_store() error = %v", err)
  6319  	}
  6320  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  6321  	if err != nil {
  6322  		t.Fatalf("Blobs.Push() error = %v", err)
  6323  	}
  6324  
  6325  	// Test case with Host: "registry.wabbit-networks.io",
  6326  	// Location: "registry.wabbit-networks.io"
  6327  	testRegistry = "registry.wabbit-networks.io"
  6328  	store, err = blobStore_Push_Port443_HTTPS_create_store(uri, testRegistry)
  6329  	if err != nil {
  6330  		t.Fatalf("blobStore_Push_Port443_HTTPS_create_store() error = %v", err)
  6331  	}
  6332  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  6333  	if err != nil {
  6334  		t.Fatalf("Blobs.Push() error = %v", err)
  6335  	}
  6336  
  6337  	ts = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6338  		switch {
  6339  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
  6340  			w.Header().Set("Location", "https://registry.wabbit-networks.io:443/v2/test/blobs/uploads/"+uuid)
  6341  			w.WriteHeader(http.StatusAccepted)
  6342  			return
  6343  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
  6344  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
  6345  				w.WriteHeader(http.StatusBadRequest)
  6346  				break
  6347  			}
  6348  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
  6349  				w.WriteHeader(http.StatusBadRequest)
  6350  				break
  6351  			}
  6352  			buf := bytes.NewBuffer(nil)
  6353  			if _, err := buf.ReadFrom(r.Body); err != nil {
  6354  				t.Errorf("fail to read: %v", err)
  6355  			}
  6356  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  6357  			w.WriteHeader(http.StatusCreated)
  6358  			return
  6359  		default:
  6360  			w.WriteHeader(http.StatusForbidden)
  6361  		}
  6362  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  6363  	}))
  6364  	defer ts.Close()
  6365  	uri, err = url.Parse(ts.URL)
  6366  	if err != nil {
  6367  		t.Fatalf("invalid test https server: %v", err)
  6368  	}
  6369  
  6370  	// Test case with Host: "registry.wabbit-networks.io:443",
  6371  	// Location: "registry.wabbit-networks.io:443"
  6372  	testRegistry = "registry.wabbit-networks.io:443"
  6373  	store, err = blobStore_Push_Port443_HTTPS_create_store(uri, testRegistry)
  6374  	if err != nil {
  6375  		t.Fatalf("blobStore_Push_Port443_HTTPS_create_store() error = %v", err)
  6376  	}
  6377  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  6378  	if err != nil {
  6379  		t.Fatalf("Blobs.Push() error = %v", err)
  6380  	}
  6381  
  6382  	// Test case with Host: "registry.wabbit-networks.io",
  6383  	// Location: "registry.wabbit-networks.io:443"
  6384  	testRegistry = "registry.wabbit-networks.io"
  6385  	store, err = blobStore_Push_Port443_HTTPS_create_store(uri, testRegistry)
  6386  	if err != nil {
  6387  		t.Fatalf("blobStore_Push_Port443_HTTPS_create_store() error = %v", err)
  6388  	}
  6389  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  6390  	if err != nil {
  6391  		t.Fatalf("Blobs.Push() error = %v", err)
  6392  	}
  6393  }
  6394  
  6395  // Testing `last` parameter for Tags list
  6396  func TestRepository_Tags_WithLastParam(t *testing.T) {
  6397  	tagSet := strings.Split("abcdefghijklmnopqrstuvwxyz", "")
  6398  	var offset int
  6399  	var ts *httptest.Server
  6400  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6401  		if r.Method != http.MethodGet || r.URL.Path != "/v2/test/tags/list" {
  6402  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  6403  			w.WriteHeader(http.StatusNotFound)
  6404  			return
  6405  		}
  6406  		q := r.URL.Query()
  6407  		n, err := strconv.Atoi(q.Get("n"))
  6408  		if err != nil || n != 4 {
  6409  			t.Errorf("bad page size: %s", q.Get("n"))
  6410  			w.WriteHeader(http.StatusBadRequest)
  6411  			return
  6412  		}
  6413  		last := q.Get("last")
  6414  		if last != "" {
  6415  			offset = indexOf(last, tagSet) + 1
  6416  		}
  6417  		var tags []string
  6418  		switch q.Get("test") {
  6419  		case "foo":
  6420  			tags = tagSet[offset : offset+n]
  6421  			offset += n
  6422  			w.Header().Set("Link", fmt.Sprintf(`<%s/v2/test/tags/list?n=4&last=v&test=bar>; rel="next"`, ts.URL))
  6423  		case "bar":
  6424  			tags = tagSet[offset : offset+n]
  6425  		default:
  6426  			tags = tagSet[offset : offset+n]
  6427  			offset += n
  6428  			w.Header().Set("Link", fmt.Sprintf(`<%s/v2/test/tags/list?n=4&last=r&test=foo>; rel="next"`, ts.URL))
  6429  		}
  6430  		result := struct {
  6431  			Tags []string `json:"tags"`
  6432  		}{
  6433  			Tags: tags,
  6434  		}
  6435  		if err := json.NewEncoder(w).Encode(result); err != nil {
  6436  			t.Errorf("failed to write response: %v", err)
  6437  		}
  6438  	}))
  6439  	defer ts.Close()
  6440  	uri, err := url.Parse(ts.URL)
  6441  	if err != nil {
  6442  		t.Fatalf("invalid test http server: %v", err)
  6443  	}
  6444  
  6445  	repo, err := NewRepository(uri.Host + "/test")
  6446  	if err != nil {
  6447  		t.Fatalf("NewRepository() error = %v", err)
  6448  	}
  6449  	repo.PlainHTTP = true
  6450  	repo.TagListPageSize = 4
  6451  	last := "n"
  6452  	startInd := indexOf(last, tagSet) + 1
  6453  
  6454  	ctx := context.Background()
  6455  	if err := repo.Tags(ctx, last, func(got []string) error {
  6456  		want := tagSet[startInd : startInd+repo.TagListPageSize]
  6457  		startInd += repo.TagListPageSize
  6458  		if !reflect.DeepEqual(got, want) {
  6459  			t.Errorf("Registry.Repositories() = %v, want %v", got, want)
  6460  		}
  6461  		return nil
  6462  	}); err != nil {
  6463  		t.Errorf("Repository.Tags() error = %v", err)
  6464  	}
  6465  }
  6466  
  6467  func TestRepository_ParseReference(t *testing.T) {
  6468  	type args struct {
  6469  		reference string
  6470  	}
  6471  	tests := []struct {
  6472  		name    string
  6473  		repoRef registry.Reference
  6474  		args    args
  6475  		want    registry.Reference
  6476  		wantErr error
  6477  	}{
  6478  		{
  6479  			name: "parse tag",
  6480  			repoRef: registry.Reference{
  6481  				Registry:   "registry.example.com",
  6482  				Repository: "hello-world",
  6483  			},
  6484  			args: args{
  6485  				reference: "foobar",
  6486  			},
  6487  			want: registry.Reference{
  6488  				Registry:   "registry.example.com",
  6489  				Repository: "hello-world",
  6490  				Reference:  "foobar",
  6491  			},
  6492  			wantErr: nil,
  6493  		},
  6494  		{
  6495  			name: "parse digest",
  6496  			repoRef: registry.Reference{
  6497  				Registry:   "registry.example.com",
  6498  				Repository: "hello-world",
  6499  			},
  6500  			args: args{
  6501  				reference: "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6502  			},
  6503  			want: registry.Reference{
  6504  				Registry:   "registry.example.com",
  6505  				Repository: "hello-world",
  6506  				Reference:  "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6507  			},
  6508  			wantErr: nil,
  6509  		},
  6510  		{
  6511  			name: "parse tag@digest",
  6512  			repoRef: registry.Reference{
  6513  				Registry:   "registry.example.com",
  6514  				Repository: "hello-world",
  6515  			},
  6516  			args: args{
  6517  				reference: "foobar@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6518  			},
  6519  			want: registry.Reference{
  6520  				Registry:   "registry.example.com",
  6521  				Repository: "hello-world",
  6522  				Reference:  "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6523  			},
  6524  			wantErr: nil,
  6525  		},
  6526  		{
  6527  			name: "parse FQDN tag",
  6528  			repoRef: registry.Reference{
  6529  				Registry:   "registry.example.com",
  6530  				Repository: "hello-world",
  6531  			},
  6532  			args: args{
  6533  				reference: "registry.example.com/hello-world:foobar",
  6534  			},
  6535  			want: registry.Reference{
  6536  				Registry:   "registry.example.com",
  6537  				Repository: "hello-world",
  6538  				Reference:  "foobar",
  6539  			},
  6540  			wantErr: nil,
  6541  		},
  6542  		{
  6543  			name: "parse FQDN digest",
  6544  			repoRef: registry.Reference{
  6545  				Registry:   "registry.example.com",
  6546  				Repository: "hello-world",
  6547  			},
  6548  			args: args{
  6549  				reference: "registry.example.com/hello-world@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6550  			},
  6551  			want: registry.Reference{
  6552  				Registry:   "registry.example.com",
  6553  				Repository: "hello-world",
  6554  				Reference:  "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6555  			},
  6556  			wantErr: nil,
  6557  		},
  6558  		{
  6559  			name: "parse FQDN tag@digest",
  6560  			repoRef: registry.Reference{
  6561  				Registry:   "registry.example.com",
  6562  				Repository: "hello-world",
  6563  			},
  6564  			args: args{
  6565  				reference: "registry.example.com/hello-world:foobar@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6566  			},
  6567  			want: registry.Reference{
  6568  				Registry:   "registry.example.com",
  6569  				Repository: "hello-world",
  6570  				Reference:  "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6571  			},
  6572  			wantErr: nil,
  6573  		},
  6574  		{
  6575  			name: "empty reference",
  6576  			repoRef: registry.Reference{
  6577  				Registry:   "registry.example.com",
  6578  				Repository: "hello-world",
  6579  			},
  6580  			args: args{
  6581  				reference: "",
  6582  			},
  6583  			want:    registry.Reference{},
  6584  			wantErr: errdef.ErrInvalidReference,
  6585  		},
  6586  		{
  6587  			name: "missing repository",
  6588  			repoRef: registry.Reference{
  6589  				Registry:   "registry.example.com",
  6590  				Repository: "hello-world",
  6591  			},
  6592  			args: args{
  6593  				reference: "myregistry.example.com:hello-world",
  6594  			},
  6595  			want:    registry.Reference{},
  6596  			wantErr: errdef.ErrInvalidReference,
  6597  		},
  6598  		{
  6599  			name: "missing reference",
  6600  			repoRef: registry.Reference{
  6601  				Registry:   "registry.example.com",
  6602  				Repository: "hello-world",
  6603  			},
  6604  			args: args{
  6605  				reference: "registry.example.com/hello-world",
  6606  			},
  6607  			want:    registry.Reference{},
  6608  			wantErr: errdef.ErrInvalidReference,
  6609  		},
  6610  		{
  6611  			name: "registry mismatch",
  6612  			repoRef: registry.Reference{
  6613  				Registry:   "registry.example.com",
  6614  				Repository: "hello-world",
  6615  			},
  6616  			args: args{
  6617  				reference: "myregistry.example.com/hello-world:foobar@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6618  			},
  6619  			want:    registry.Reference{},
  6620  			wantErr: errdef.ErrInvalidReference,
  6621  		},
  6622  		{
  6623  			name: "repository mismatch",
  6624  			repoRef: registry.Reference{
  6625  				Registry:   "registry.example.com",
  6626  				Repository: "hello-world",
  6627  			},
  6628  			args: args{
  6629  				reference: "registry.example.com/goodbye-world:foobar@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  6630  			},
  6631  			want:    registry.Reference{},
  6632  			wantErr: errdef.ErrInvalidReference,
  6633  		},
  6634  		{
  6635  			name: "digest posing as a tag",
  6636  			repoRef: registry.Reference{
  6637  				Registry:   "registry.example.com",
  6638  				Repository: "hello-world",
  6639  			},
  6640  			args: args{
  6641  				reference: "registry.example.com:5000/hello-world:sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  6642  			},
  6643  			want:    registry.Reference{},
  6644  			wantErr: errdef.ErrInvalidReference,
  6645  		},
  6646  		{
  6647  			name: "missing reference after the at sign",
  6648  			repoRef: registry.Reference{
  6649  				Registry:   "registry.example.com",
  6650  				Repository: "hello-world",
  6651  			},
  6652  			args: args{
  6653  				reference: "registry.example.com/hello-world@",
  6654  			},
  6655  			want:    registry.Reference{},
  6656  			wantErr: errdef.ErrInvalidReference,
  6657  		},
  6658  		{
  6659  			name: "missing reference after the colon",
  6660  			repoRef: registry.Reference{
  6661  				Registry: "localhost",
  6662  			},
  6663  			args: args{
  6664  				reference: "localhost:5000/hello:",
  6665  			},
  6666  			want:    registry.Reference{},
  6667  			wantErr: errdef.ErrInvalidReference,
  6668  		},
  6669  		{
  6670  			name:    "zero-size tag, zero-size digest",
  6671  			repoRef: registry.Reference{},
  6672  			args: args{
  6673  				reference: "localhost:5000/hello:@",
  6674  			},
  6675  			want:    registry.Reference{},
  6676  			wantErr: errdef.ErrInvalidReference,
  6677  		},
  6678  		{
  6679  			name:    "zero-size tag with valid digest",
  6680  			repoRef: registry.Reference{},
  6681  			args: args{
  6682  				reference: "localhost:5000/hello:@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  6683  			},
  6684  			want:    registry.Reference{},
  6685  			wantErr: errdef.ErrInvalidReference,
  6686  		},
  6687  		{
  6688  			name:    "valid tag with zero-size digest",
  6689  			repoRef: registry.Reference{},
  6690  			args: args{
  6691  				reference: "localhost:5000/hello:foobar@",
  6692  			},
  6693  			want:    registry.Reference{},
  6694  			wantErr: errdef.ErrInvalidReference,
  6695  		},
  6696  	}
  6697  	for _, tt := range tests {
  6698  		t.Run(tt.name, func(t *testing.T) {
  6699  			r := &Repository{
  6700  				Reference: tt.repoRef,
  6701  			}
  6702  			got, err := r.ParseReference(tt.args.reference)
  6703  			if !errors.Is(err, tt.wantErr) {
  6704  				t.Errorf("Repository.ParseReference() error = %v, wantErr %v", err, tt.wantErr)
  6705  				return
  6706  			}
  6707  			if !reflect.DeepEqual(got, tt.want) {
  6708  				t.Errorf("Repository.ParseReference() = %v, want %v", got, tt.want)
  6709  			}
  6710  		})
  6711  	}
  6712  }
  6713  
  6714  func TestRepository_SetReferrersCapability(t *testing.T) {
  6715  	repo, err := NewRepository("registry.example.com/test")
  6716  	if err != nil {
  6717  		t.Fatalf("NewRepository() error = %v", err)
  6718  	}
  6719  	// initial state
  6720  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  6721  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  6722  	}
  6723  
  6724  	// valid first time set
  6725  	if err := repo.SetReferrersCapability(true); err != nil {
  6726  		t.Errorf("Repository.SetReferrersCapability() error = %v", err)
  6727  	}
  6728  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  6729  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  6730  	}
  6731  
  6732  	// invalid second time set, state should be no changed
  6733  	if err := repo.SetReferrersCapability(false); !errors.Is(err, ErrReferrersCapabilityAlreadySet) {
  6734  		t.Errorf("Repository.SetReferrersCapability() error = %v, wantErr %v", err, ErrReferrersCapabilityAlreadySet)
  6735  	}
  6736  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  6737  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  6738  	}
  6739  }
  6740  
  6741  func Test_generateIndex(t *testing.T) {
  6742  	referrer_1 := spec.Artifact{
  6743  		MediaType:    spec.MediaTypeArtifactManifest,
  6744  		ArtifactType: "foo",
  6745  	}
  6746  	referrerJSON_1, err := json.Marshal(referrer_1)
  6747  	if err != nil {
  6748  		t.Fatal("failed to marshal manifest:", err)
  6749  	}
  6750  	referrer_2 := spec.Artifact{
  6751  		MediaType:    spec.MediaTypeArtifactManifest,
  6752  		ArtifactType: "bar",
  6753  	}
  6754  	referrerJSON_2, err := json.Marshal(referrer_2)
  6755  	if err != nil {
  6756  		t.Fatal("failed to marshal manifest:", err)
  6757  	}
  6758  	referrers := []ocispec.Descriptor{
  6759  		content.NewDescriptorFromBytes(referrer_1.MediaType, referrerJSON_1),
  6760  		content.NewDescriptorFromBytes(referrer_2.MediaType, referrerJSON_2),
  6761  	}
  6762  
  6763  	wantIndex := ocispec.Index{
  6764  		Versioned: specs.Versioned{
  6765  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  6766  		},
  6767  		MediaType: ocispec.MediaTypeImageIndex,
  6768  		Manifests: referrers,
  6769  	}
  6770  	wantIndexJSON, err := json.Marshal(wantIndex)
  6771  	if err != nil {
  6772  		t.Fatal("failed to marshal index:", err)
  6773  	}
  6774  	wantIndexDesc := content.NewDescriptorFromBytes(wantIndex.MediaType, wantIndexJSON)
  6775  
  6776  	wantEmptyIndex := ocispec.Index{
  6777  		Versioned: specs.Versioned{
  6778  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  6779  		},
  6780  		MediaType: ocispec.MediaTypeImageIndex,
  6781  		Manifests: []ocispec.Descriptor{},
  6782  	}
  6783  	wantEmptyIndexJSON, err := json.Marshal(wantEmptyIndex)
  6784  	if err != nil {
  6785  		t.Fatal("failed to marshal index:", err)
  6786  	}
  6787  	wantEmptyIndexDesc := content.NewDescriptorFromBytes(wantEmptyIndex.MediaType, wantEmptyIndexJSON)
  6788  
  6789  	tests := []struct {
  6790  		name      string
  6791  		manifests []ocispec.Descriptor
  6792  		wantDesc  ocispec.Descriptor
  6793  		wantBytes []byte
  6794  		wantErr   bool
  6795  	}{
  6796  		{
  6797  			name:      "non-empty referrers list",
  6798  			manifests: referrers,
  6799  			wantDesc:  wantIndexDesc,
  6800  			wantBytes: wantIndexJSON,
  6801  			wantErr:   false,
  6802  		},
  6803  		{
  6804  			name:      "nil referrers list",
  6805  			manifests: nil,
  6806  			wantDesc:  wantEmptyIndexDesc,
  6807  			wantBytes: wantEmptyIndexJSON,
  6808  			wantErr:   false,
  6809  		},
  6810  		{
  6811  			name:      "empty referrers list",
  6812  			manifests: nil,
  6813  			wantDesc:  wantEmptyIndexDesc,
  6814  			wantBytes: wantEmptyIndexJSON,
  6815  			wantErr:   false,
  6816  		},
  6817  	}
  6818  	for _, tt := range tests {
  6819  		t.Run(tt.name, func(t *testing.T) {
  6820  			got, got1, err := generateIndex(tt.manifests)
  6821  			if (err != nil) != tt.wantErr {
  6822  				t.Errorf("generateReferrersIndex() error = %v, wantErr %v", err, tt.wantErr)
  6823  				return
  6824  			}
  6825  			if !reflect.DeepEqual(got, tt.wantDesc) {
  6826  				t.Errorf("generateReferrersIndex() got = %v, want %v", got, tt.wantDesc)
  6827  			}
  6828  			if !reflect.DeepEqual(got1, tt.wantBytes) {
  6829  				t.Errorf("generateReferrersIndex() got1 = %v, want %v", got1, tt.wantBytes)
  6830  			}
  6831  		})
  6832  	}
  6833  }
  6834  
  6835  func TestRepository_pingReferrers(t *testing.T) {
  6836  	t.Run("referrers available", func(t *testing.T) {
  6837  		count := 0
  6838  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6839  			switch {
  6840  			case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  6841  				count++
  6842  				w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  6843  				w.WriteHeader(http.StatusOK)
  6844  			default:
  6845  				t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  6846  				w.WriteHeader(http.StatusNotFound)
  6847  			}
  6848  
  6849  		}))
  6850  		defer ts.Close()
  6851  		uri, err := url.Parse(ts.URL)
  6852  		if err != nil {
  6853  			t.Fatalf("invalid test http server: %v", err)
  6854  		}
  6855  
  6856  		ctx := context.Background()
  6857  		repo, err := NewRepository(uri.Host + "/test")
  6858  		if err != nil {
  6859  			t.Fatalf("NewRepository() error = %v", err)
  6860  		}
  6861  		repo.PlainHTTP = true
  6862  
  6863  		// 1st call
  6864  		if state := repo.loadReferrersState(); state != referrersStateUnknown {
  6865  			t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  6866  		}
  6867  		got, err := repo.pingReferrers(ctx)
  6868  		if err != nil {
  6869  			t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  6870  		}
  6871  		if got != true {
  6872  			t.Errorf("Repository.pingReferrers() = %v, want %v", got, true)
  6873  		}
  6874  		if state := repo.loadReferrersState(); state != referrersStateSupported {
  6875  			t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  6876  		}
  6877  		if count != 1 {
  6878  			t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  6879  		}
  6880  
  6881  		// 2nd call
  6882  		if state := repo.loadReferrersState(); state != referrersStateSupported {
  6883  			t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  6884  		}
  6885  		got, err = repo.pingReferrers(ctx)
  6886  		if err != nil {
  6887  			t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  6888  		}
  6889  		if got != true {
  6890  			t.Errorf("Repository.pingReferrers() = %v, want %v", got, true)
  6891  		}
  6892  		if state := repo.loadReferrersState(); state != referrersStateSupported {
  6893  			t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  6894  		}
  6895  		if count != 1 {
  6896  			t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  6897  		}
  6898  	})
  6899  
  6900  	t.Run("referrers unavailable", func(t *testing.T) {
  6901  		count := 0
  6902  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6903  			switch {
  6904  			case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  6905  				count++
  6906  				w.WriteHeader(http.StatusNotFound)
  6907  			default:
  6908  				t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  6909  				w.WriteHeader(http.StatusNotFound)
  6910  			}
  6911  
  6912  		}))
  6913  		defer ts.Close()
  6914  		uri, err := url.Parse(ts.URL)
  6915  		if err != nil {
  6916  			t.Fatalf("invalid test http server: %v", err)
  6917  		}
  6918  
  6919  		ctx := context.Background()
  6920  		repo, err := NewRepository(uri.Host + "/test")
  6921  		if err != nil {
  6922  			t.Fatalf("NewRepository() error = %v", err)
  6923  		}
  6924  		repo.PlainHTTP = true
  6925  
  6926  		// 1st call
  6927  		if state := repo.loadReferrersState(); state != referrersStateUnknown {
  6928  			t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  6929  		}
  6930  		got, err := repo.pingReferrers(ctx)
  6931  		if err != nil {
  6932  			t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  6933  		}
  6934  		if got != false {
  6935  			t.Errorf("Repository.pingReferrers() = %v, want %v", got, false)
  6936  		}
  6937  		if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  6938  			t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  6939  		}
  6940  		if count != 1 {
  6941  			t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  6942  		}
  6943  
  6944  		// 2nd call
  6945  		if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  6946  			t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  6947  		}
  6948  		got, err = repo.pingReferrers(ctx)
  6949  		if err != nil {
  6950  			t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  6951  		}
  6952  		if got != false {
  6953  			t.Errorf("Repository.pingReferrers() = %v, want %v", got, false)
  6954  		}
  6955  		if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  6956  			t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  6957  		}
  6958  		if count != 1 {
  6959  			t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  6960  		}
  6961  	})
  6962  
  6963  	t.Run("referrers unavailable incorrect content type", func(t *testing.T) {
  6964  		count := 0
  6965  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6966  			switch {
  6967  			case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  6968  				count++
  6969  				w.Header().Set("Content-Type", "text/html") // can be anything except an OCI image index
  6970  				w.WriteHeader(http.StatusOK)
  6971  			default:
  6972  				t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  6973  				w.WriteHeader(http.StatusNotFound)
  6974  			}
  6975  
  6976  		}))
  6977  		defer ts.Close()
  6978  		uri, err := url.Parse(ts.URL)
  6979  		if err != nil {
  6980  			t.Fatalf("invalid test http server: %v", err)
  6981  		}
  6982  
  6983  		ctx := context.Background()
  6984  		repo, err := NewRepository(uri.Host + "/test")
  6985  		if err != nil {
  6986  			t.Fatalf("NewRepository() error = %v", err)
  6987  		}
  6988  		repo.PlainHTTP = true
  6989  
  6990  		got, err := repo.pingReferrers(ctx)
  6991  		if err != nil {
  6992  			t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  6993  		}
  6994  		if got != false {
  6995  			t.Errorf("Repository.pingReferrers() = %v, want %v", got, false)
  6996  		}
  6997  		if count != 1 {
  6998  			t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  6999  		}
  7000  	})
  7001  }
  7002  
  7003  func TestRepository_pingReferrers_RepositoryNotFound(t *testing.T) {
  7004  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  7005  		if r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest {
  7006  			w.WriteHeader(http.StatusNotFound)
  7007  			w.Write([]byte(`{ "errors": [ { "code": "NAME_UNKNOWN", "message": "repository name not known to registry" } ] }`))
  7008  			return
  7009  		}
  7010  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  7011  		w.WriteHeader(http.StatusNotFound)
  7012  	}))
  7013  	defer ts.Close()
  7014  	uri, err := url.Parse(ts.URL)
  7015  	if err != nil {
  7016  		t.Fatalf("invalid test http server: %v", err)
  7017  	}
  7018  	ctx := context.Background()
  7019  
  7020  	// test referrers state unknown
  7021  	repo, err := NewRepository(uri.Host + "/test")
  7022  	if err != nil {
  7023  		t.Fatalf("NewRepository() error = %v", err)
  7024  	}
  7025  	repo.PlainHTTP = true
  7026  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  7027  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  7028  	}
  7029  	if _, err = repo.pingReferrers(ctx); err == nil {
  7030  		t.Fatalf("Repository.pingReferrers() error = %v, wantErr %v", err, true)
  7031  	}
  7032  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  7033  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  7034  	}
  7035  
  7036  	// test referrers state supported
  7037  	repo, err = NewRepository(uri.Host + "/test")
  7038  	if err != nil {
  7039  		t.Fatalf("NewRepository() error = %v", err)
  7040  	}
  7041  	repo.PlainHTTP = true
  7042  	repo.SetReferrersCapability(true)
  7043  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  7044  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  7045  	}
  7046  	got, err := repo.pingReferrers(ctx)
  7047  	if err != nil {
  7048  		t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  7049  	}
  7050  	if got != true {
  7051  		t.Errorf("Repository.pingReferrers() = %v, want %v", got, true)
  7052  	}
  7053  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  7054  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  7055  	}
  7056  
  7057  	// test referrers state unsupported
  7058  	repo, err = NewRepository(uri.Host + "/test")
  7059  	if err != nil {
  7060  		t.Fatalf("NewRepository() error = %v", err)
  7061  	}
  7062  	repo.PlainHTTP = true
  7063  	repo.SetReferrersCapability(false)
  7064  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  7065  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  7066  	}
  7067  	got, err = repo.pingReferrers(ctx)
  7068  	if err != nil {
  7069  		t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  7070  	}
  7071  	if got != false {
  7072  		t.Errorf("Repository.pingReferrers() = %v, want %v", got, false)
  7073  	}
  7074  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  7075  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  7076  	}
  7077  }
  7078  
  7079  func TestRepository_pingReferrers_Concurrent(t *testing.T) {
  7080  	// referrers available
  7081  	var count int32
  7082  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  7083  		switch {
  7084  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  7085  			atomic.AddInt32(&count, 1)
  7086  			w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
  7087  			w.WriteHeader(http.StatusOK)
  7088  		default:
  7089  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  7090  			w.WriteHeader(http.StatusNotFound)
  7091  		}
  7092  
  7093  	}))
  7094  	defer ts.Close()
  7095  	uri, err := url.Parse(ts.URL)
  7096  	if err != nil {
  7097  		t.Fatalf("invalid test http server: %v", err)
  7098  	}
  7099  
  7100  	ctx := context.Background()
  7101  	repo, err := NewRepository(uri.Host + "/test")
  7102  	if err != nil {
  7103  		t.Fatalf("NewRepository() error = %v", err)
  7104  	}
  7105  	repo.PlainHTTP = true
  7106  
  7107  	concurrency := 64
  7108  	eg, egCtx := errgroup.WithContext(ctx)
  7109  
  7110  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  7111  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  7112  	}
  7113  	for i := 0; i < concurrency; i++ {
  7114  		eg.Go(func() func() error {
  7115  			return func() error {
  7116  				got, err := repo.pingReferrers(egCtx)
  7117  				if err != nil {
  7118  					t.Fatalf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  7119  				}
  7120  				if got != true {
  7121  					t.Errorf("Repository.pingReferrers() = %v, want %v", got, true)
  7122  				}
  7123  				return nil
  7124  			}
  7125  		}())
  7126  	}
  7127  	if err := eg.Wait(); err != nil {
  7128  		t.Fatal(err)
  7129  	}
  7130  
  7131  	if got := atomic.LoadInt32(&count); got != 1 {
  7132  		t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  7133  	}
  7134  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  7135  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  7136  	}
  7137  }
  7138  
  7139  func TestRepository_do(t *testing.T) {
  7140  	data := []byte(`hello world!`)
  7141  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  7142  		if r.Method != http.MethodGet || r.URL.Path != "/test" {
  7143  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  7144  			w.WriteHeader(http.StatusNotFound)
  7145  			return
  7146  		}
  7147  		w.Header().Add("Warning", `299 - "Test 1: Good warning."`)
  7148  		w.Header().Add("Warning", `199 - "Test 2: Warning with a non-299 code."`)
  7149  		w.Header().Add("Warning", `299 - "Test 3: Good warning."`)
  7150  		w.Header().Add("Warning", `299 myregistry.example.com "Test 4: Warning with a non-unknown agent"`)
  7151  		w.Header().Add("Warning", `299 - "Test 5: Warning with a date." "Sat, 25 Aug 2012 23:34:45 GMT"`)
  7152  		w.Header().Add("wArnIng", `299 - "Test 6: Good warning."`)
  7153  		w.Write(data)
  7154  	}))
  7155  	defer ts.Close()
  7156  	uri, err := url.Parse(ts.URL)
  7157  	if err != nil {
  7158  		t.Fatalf("invalid test http server: %v", err)
  7159  	}
  7160  	testURL := ts.URL + "/test"
  7161  
  7162  	// test do() without HandleWarning
  7163  	repo, err := NewRepository(uri.Host + "/test")
  7164  	if err != nil {
  7165  		t.Fatal("NewRepository() error =", err)
  7166  	}
  7167  	req, err := http.NewRequest(http.MethodGet, testURL, nil)
  7168  	if err != nil {
  7169  		t.Fatal("failed to create test request:", err)
  7170  	}
  7171  	resp, err := repo.do(req)
  7172  	if err != nil {
  7173  		t.Fatal("Repository.do() error =", err)
  7174  	}
  7175  	if resp.StatusCode != http.StatusOK {
  7176  		t.Errorf("Repository.do() status code = %v, want %v", resp.StatusCode, http.StatusOK)
  7177  	}
  7178  	if got := len(resp.Header["Warning"]); got != 6 {
  7179  		t.Errorf("Repository.do() warning header len = %v, want %v", got, 6)
  7180  	}
  7181  	got, err := io.ReadAll(resp.Body)
  7182  	if err != nil {
  7183  		t.Fatal("io.ReadAll() error =", err)
  7184  	}
  7185  	resp.Body.Close()
  7186  	if !bytes.Equal(got, data) {
  7187  		t.Errorf("Repository.do() = %v, want %v", got, data)
  7188  	}
  7189  
  7190  	// test do() with HandleWarning
  7191  	repo, err = NewRepository(uri.Host + "/test")
  7192  	if err != nil {
  7193  		t.Fatal("NewRepository() error =", err)
  7194  	}
  7195  	var gotWarnings []Warning
  7196  	repo.HandleWarning = func(warning Warning) {
  7197  		gotWarnings = append(gotWarnings, warning)
  7198  	}
  7199  
  7200  	req, err = http.NewRequest(http.MethodGet, testURL, nil)
  7201  	if err != nil {
  7202  		t.Fatal("failed to create test request:", err)
  7203  	}
  7204  	resp, err = repo.do(req)
  7205  	if err != nil {
  7206  		t.Fatal("Repository.do() error =", err)
  7207  	}
  7208  	if resp.StatusCode != http.StatusOK {
  7209  		t.Errorf("Repository.do() status code = %v, want %v", resp.StatusCode, http.StatusOK)
  7210  	}
  7211  	if got := len(resp.Header["Warning"]); got != 6 {
  7212  		t.Errorf("Repository.do() warning header len = %v, want %v", got, 6)
  7213  	}
  7214  	got, err = io.ReadAll(resp.Body)
  7215  	if err != nil {
  7216  		t.Errorf("Repository.do() = %v, want %v", got, data)
  7217  	}
  7218  	resp.Body.Close()
  7219  	if !bytes.Equal(got, data) {
  7220  		t.Errorf("Repository.do() = %v, want %v", got, data)
  7221  	}
  7222  
  7223  	wantWarnings := []Warning{
  7224  		{
  7225  			WarningValue: WarningValue{
  7226  				Code:  299,
  7227  				Agent: "-",
  7228  				Text:  "Test 1: Good warning.",
  7229  			},
  7230  		},
  7231  		{
  7232  			WarningValue: WarningValue{
  7233  				Code:  299,
  7234  				Agent: "-",
  7235  				Text:  "Test 3: Good warning.",
  7236  			},
  7237  		},
  7238  		{
  7239  			WarningValue: WarningValue{
  7240  				Code:  299,
  7241  				Agent: "-",
  7242  				Text:  "Test 6: Good warning.",
  7243  			},
  7244  		},
  7245  	}
  7246  	if !reflect.DeepEqual(gotWarnings, wantWarnings) {
  7247  		t.Errorf("Repository.do() = %v, want %v", gotWarnings, wantWarnings)
  7248  	}
  7249  }
  7250  
  7251  func TestRepository_clone(t *testing.T) {
  7252  	repo, err := NewRepository("localhost:1234/repo/image")
  7253  	if err != nil {
  7254  		t.Fatalf("invalid repository: %v", err)
  7255  	}
  7256  
  7257  	crepo := repo.clone()
  7258  
  7259  	if repo.Reference != crepo.Reference {
  7260  		t.Fatal("references should be the same")
  7261  	}
  7262  
  7263  	if !reflect.DeepEqual(&repo.referrersPingLock, &crepo.referrersPingLock) {
  7264  		t.Fatal("referrersPingLock should be different")
  7265  	}
  7266  
  7267  	if !reflect.DeepEqual(&repo.referrersMergePool, &crepo.referrersMergePool) {
  7268  		t.Fatal("referrersMergePool should be different")
  7269  	}
  7270  }