github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/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/opcr-io/oras-go/v2/content"
    37  	"github.com/opcr-io/oras-go/v2/errdef"
    38  	"github.com/opcr-io/oras-go/v2/internal/interfaces"
    39  	"github.com/opcr-io/oras-go/v2/registry"
    40  	"github.com/opcr-io/oras-go/v2/registry/remote/auth"
    41  	"github.com/opencontainers/go-digest"
    42  	specs "github.com/opencontainers/image-spec/specs-go"
    43  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    44  	"golang.org/x/sync/errgroup"
    45  )
    46  
    47  type testIOStruct struct {
    48  	isTag                   bool
    49  	clientSuppliedReference string
    50  	serverCalculatedDigest  digest.Digest // for non-HEAD (body-containing) requests only
    51  	errExpectedOnHEAD       bool
    52  	errExpectedOnGET        bool
    53  }
    54  
    55  const theAmazingBanClan = "Ban Gu, Ban Chao, Ban Zhao"
    56  const theAmazingBanDigest = "b526a4f2be963a2f9b0990c001255669eab8a254ab1a6e3f84f1820212ac7078"
    57  
    58  // The following truth table aims to cover the expected GET/HEAD request outcome
    59  // for all possible permutations of the client/server "containing a digest", for
    60  // both Manifests and Blobs.  Where the results between the two differ, the index
    61  // of the first column has an exclamation mark.
    62  //
    63  // The client is said to "contain a digest" if the user-supplied reference string
    64  // is of the form that contains a digest rather than a tag.  The server, on the
    65  // other hand, is said to "contain a digest" if the server responded with the
    66  // special header `Docker-Content-Digest`.
    67  //
    68  // In this table, anything denoted with an asterisk indicates that the true
    69  // response should actually be the opposite of what's expected; for example,
    70  // `*PASS` means we will get a `PASS`, even though the true answer would be its
    71  // diametric opposite--a `FAIL`. This may seem odd, and deserves an explanation.
    72  // This function has blind-spots, and while it can expend power to gain sight,
    73  // i.e., perform the expensive validation, we chose not to.  The reason is two-
    74  // fold: a) we "know" that even if we say "!PASS", it will eventually fail later
    75  // when checks are performed, and with that assumption, we have the luxury for
    76  // the second point, which is b) performance.
    77  //
    78  //	 _______________________________________________________________________________________________________________
    79  //	| ID | CLIENT          | SERVER           | Manifest.GET          | Blob.GET  | Manifest.HEAD       | Blob.HEAD |
    80  //	|----+-----------------+------------------+-----------------------+-----------+---------------------+-----------+
    81  //	| 1  | tag             | missing          | CALCULATE,PASS        | n/a       | FAIL                | n/a       |
    82  //	| 2  | tag             | presentCorrect   | TRUST,PASS            | n/a       | TRUST,PASS          | n/a       |
    83  //	| 3  | tag             | presentIncorrect | TRUST,*PASS           | n/a       | TRUST,*PASS         | n/a       |
    84  //	| 4  | correctDigest   | missing          | TRUST,PASS            | PASS      | TRUST,PASS          | PASS      |
    85  //	| 5  | correctDigest   | presentCorrect   | TRUST,COMPARE,PASS    | PASS      | TRUST,COMPARE,PASS  | PASS      |
    86  //	| 6  | correctDigest   | presentIncorrect | TRUST,COMPARE,FAIL    | FAIL      | TRUST,COMPARE,FAIL  | FAIL      |
    87  //	 ---------------------------------------------------------------------------------------------------------------
    88  func getTestIOStructMapForGetDescriptorClass() map[string]testIOStruct {
    89  	correctDigest := fmt.Sprintf("sha256:%v", theAmazingBanDigest)
    90  	incorrectDigest := fmt.Sprintf("sha256:%v", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    91  
    92  	return map[string]testIOStruct{
    93  		"1. Client:Tag & Server:DigestMissing": {
    94  			isTag:             true,
    95  			errExpectedOnHEAD: true,
    96  		},
    97  		"2. Client:Tag & Server:DigestValid": {
    98  			isTag:                  true,
    99  			serverCalculatedDigest: digest.Digest(correctDigest),
   100  		},
   101  		"3. Client:Tag & Server:DigestWrongButSyntacticallyValid": {
   102  			isTag:                  true,
   103  			serverCalculatedDigest: digest.Digest(incorrectDigest),
   104  		},
   105  		"4. Client:DigestValid & Server:DigestMissing": {
   106  			clientSuppliedReference: correctDigest,
   107  		},
   108  		"5. Client:DigestValid & Server:DigestValid": {
   109  			clientSuppliedReference: correctDigest,
   110  			serverCalculatedDigest:  digest.Digest(correctDigest),
   111  		},
   112  		"6. Client:DigestValid & Server:DigestWrongButSyntacticallyValid": {
   113  			clientSuppliedReference: correctDigest,
   114  			serverCalculatedDigest:  digest.Digest(incorrectDigest),
   115  			errExpectedOnHEAD:       true,
   116  			errExpectedOnGET:        true,
   117  		},
   118  	}
   119  }
   120  
   121  func TestRepository_Fetch(t *testing.T) {
   122  	blob := []byte("hello world")
   123  	blobDesc := ocispec.Descriptor{
   124  		MediaType: "test",
   125  		Digest:    digest.FromBytes(blob),
   126  		Size:      int64(len(blob)),
   127  	}
   128  	index := []byte(`{"manifests":[]}`)
   129  	indexDesc := ocispec.Descriptor{
   130  		MediaType: ocispec.MediaTypeImageIndex,
   131  		Digest:    digest.FromBytes(index),
   132  		Size:      int64(len(index)),
   133  	}
   134  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   135  		if r.Method != http.MethodGet {
   136  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   137  			w.WriteHeader(http.StatusMethodNotAllowed)
   138  			return
   139  		}
   140  		switch r.URL.Path {
   141  		case "/v2/test/blobs/" + blobDesc.Digest.String():
   142  			w.Header().Set("Content-Type", "application/octet-stream")
   143  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   144  			if _, err := w.Write(blob); err != nil {
   145  				t.Errorf("failed to write %q: %v", r.URL, err)
   146  			}
   147  		case "/v2/test/manifests/" + indexDesc.Digest.String():
   148  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   149  				t.Errorf("manifest not convertable: %s", accept)
   150  				w.WriteHeader(http.StatusBadRequest)
   151  				return
   152  			}
   153  			w.Header().Set("Content-Type", indexDesc.MediaType)
   154  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   155  			if _, err := w.Write(index); err != nil {
   156  				t.Errorf("failed to write %q: %v", r.URL, err)
   157  			}
   158  		default:
   159  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   160  			w.WriteHeader(http.StatusNotFound)
   161  		}
   162  	}))
   163  	defer ts.Close()
   164  	uri, err := url.Parse(ts.URL)
   165  	if err != nil {
   166  		t.Fatalf("invalid test http server: %v", err)
   167  	}
   168  
   169  	repo, err := NewRepository(uri.Host + "/test")
   170  	if err != nil {
   171  		t.Fatalf("NewRepository() error = %v", err)
   172  	}
   173  	repo.PlainHTTP = true
   174  	ctx := context.Background()
   175  
   176  	rc, err := repo.Fetch(ctx, blobDesc)
   177  	if err != nil {
   178  		t.Fatalf("Repository.Fetch() error = %v", err)
   179  	}
   180  	buf := bytes.NewBuffer(nil)
   181  	if _, err := buf.ReadFrom(rc); err != nil {
   182  		t.Errorf("fail to read: %v", err)
   183  	}
   184  	if err := rc.Close(); err != nil {
   185  		t.Errorf("fail to close: %v", err)
   186  	}
   187  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
   188  		t.Errorf("Repository.Fetch() = %v, want %v", got, blob)
   189  	}
   190  
   191  	rc, err = repo.Fetch(ctx, indexDesc)
   192  	if err != nil {
   193  		t.Fatalf("Repository.Fetch() error = %v", err)
   194  	}
   195  	buf.Reset()
   196  	if _, err := buf.ReadFrom(rc); err != nil {
   197  		t.Errorf("fail to read: %v", err)
   198  	}
   199  	if err := rc.Close(); err != nil {
   200  		t.Errorf("fail to close: %v", err)
   201  	}
   202  	if got := buf.Bytes(); !bytes.Equal(got, index) {
   203  		t.Errorf("Repository.Fetch() = %v, want %v", got, index)
   204  	}
   205  }
   206  
   207  func TestRepository_Push(t *testing.T) {
   208  	blob := []byte("hello world")
   209  	blobDesc := ocispec.Descriptor{
   210  		MediaType: "test",
   211  		Digest:    digest.FromBytes(blob),
   212  		Size:      int64(len(blob)),
   213  	}
   214  	var gotBlob []byte
   215  	index := []byte(`{"manifests":[]}`)
   216  	indexDesc := ocispec.Descriptor{
   217  		MediaType: ocispec.MediaTypeImageIndex,
   218  		Digest:    digest.FromBytes(index),
   219  		Size:      int64(len(index)),
   220  	}
   221  	var gotIndex []byte
   222  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
   223  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   224  		switch {
   225  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
   226  			w.Header().Set("Location", "/v2/test/blobs/uploads/"+uuid)
   227  			w.WriteHeader(http.StatusAccepted)
   228  			return
   229  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
   230  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
   231  				w.WriteHeader(http.StatusBadRequest)
   232  				break
   233  			}
   234  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
   235  				w.WriteHeader(http.StatusBadRequest)
   236  				break
   237  			}
   238  			buf := bytes.NewBuffer(nil)
   239  			if _, err := buf.ReadFrom(r.Body); err != nil {
   240  				t.Errorf("fail to read: %v", err)
   241  			}
   242  			gotBlob = buf.Bytes()
   243  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   244  			w.WriteHeader(http.StatusCreated)
   245  			return
   246  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
   247  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
   248  				w.WriteHeader(http.StatusBadRequest)
   249  				break
   250  			}
   251  			buf := bytes.NewBuffer(nil)
   252  			if _, err := buf.ReadFrom(r.Body); err != nil {
   253  				t.Errorf("fail to read: %v", err)
   254  			}
   255  			gotIndex = buf.Bytes()
   256  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   257  			w.WriteHeader(http.StatusCreated)
   258  			return
   259  		default:
   260  			w.WriteHeader(http.StatusForbidden)
   261  		}
   262  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   263  	}))
   264  	defer ts.Close()
   265  	uri, err := url.Parse(ts.URL)
   266  	if err != nil {
   267  		t.Fatalf("invalid test http server: %v", err)
   268  	}
   269  
   270  	repo, err := NewRepository(uri.Host + "/test")
   271  	if err != nil {
   272  		t.Fatalf("NewRepository() error = %v", err)
   273  	}
   274  	repo.PlainHTTP = true
   275  	ctx := context.Background()
   276  
   277  	err = repo.Push(ctx, blobDesc, bytes.NewReader(blob))
   278  	if err != nil {
   279  		t.Fatalf("Repository.Push() error = %v", err)
   280  	}
   281  	if !bytes.Equal(gotBlob, blob) {
   282  		t.Errorf("Repository.Push() = %v, want %v", gotBlob, blob)
   283  	}
   284  
   285  	err = repo.Push(ctx, indexDesc, bytes.NewReader(index))
   286  	if err != nil {
   287  		t.Fatalf("Repository.Push() error = %v", err)
   288  	}
   289  	if !bytes.Equal(gotIndex, index) {
   290  		t.Errorf("Repository.Push() = %v, want %v", gotIndex, index)
   291  	}
   292  }
   293  
   294  func TestRepository_Exists(t *testing.T) {
   295  	blob := []byte("hello world")
   296  	blobDesc := ocispec.Descriptor{
   297  		MediaType: "test",
   298  		Digest:    digest.FromBytes(blob),
   299  		Size:      int64(len(blob)),
   300  	}
   301  	index := []byte(`{"manifests":[]}`)
   302  	indexDesc := ocispec.Descriptor{
   303  		MediaType: ocispec.MediaTypeImageIndex,
   304  		Digest:    digest.FromBytes(index),
   305  		Size:      int64(len(index)),
   306  	}
   307  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   308  		if r.Method != http.MethodHead {
   309  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   310  			w.WriteHeader(http.StatusMethodNotAllowed)
   311  			return
   312  		}
   313  		switch r.URL.Path {
   314  		case "/v2/test/blobs/" + blobDesc.Digest.String():
   315  			w.Header().Set("Content-Type", "application/octet-stream")
   316  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   317  			w.Header().Set("Content-Length", strconv.Itoa(int(blobDesc.Size)))
   318  		case "/v2/test/manifests/" + indexDesc.Digest.String():
   319  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   320  				t.Errorf("manifest not convertable: %s", accept)
   321  				w.WriteHeader(http.StatusBadRequest)
   322  				return
   323  			}
   324  			w.Header().Set("Content-Type", indexDesc.MediaType)
   325  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   326  			w.Header().Set("Content-Length", strconv.Itoa(int(indexDesc.Size)))
   327  		default:
   328  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   329  			w.WriteHeader(http.StatusNotFound)
   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  
   338  	repo, err := NewRepository(uri.Host + "/test")
   339  	if err != nil {
   340  		t.Fatalf("NewRepository() error = %v", err)
   341  	}
   342  	repo.PlainHTTP = true
   343  	ctx := context.Background()
   344  
   345  	exists, err := repo.Exists(ctx, blobDesc)
   346  	if err != nil {
   347  		t.Fatalf("Repository.Exists() error = %v", err)
   348  	}
   349  	if !exists {
   350  		t.Errorf("Repository.Exists() = %v, want %v", exists, true)
   351  	}
   352  
   353  	exists, err = repo.Exists(ctx, indexDesc)
   354  	if err != nil {
   355  		t.Fatalf("Repository.Exists() error = %v", err)
   356  	}
   357  	if !exists {
   358  		t.Errorf("Repository.Exists() = %v, want %v", exists, true)
   359  	}
   360  }
   361  
   362  func TestRepository_Delete(t *testing.T) {
   363  	blob := []byte("hello world")
   364  	blobDesc := ocispec.Descriptor{
   365  		MediaType: "test",
   366  		Digest:    digest.FromBytes(blob),
   367  		Size:      int64(len(blob)),
   368  	}
   369  	blobDeleted := false
   370  	index := []byte(`{"manifests":[]}`)
   371  	indexDesc := ocispec.Descriptor{
   372  		MediaType: ocispec.MediaTypeImageIndex,
   373  		Digest:    digest.FromBytes(index),
   374  		Size:      int64(len(index)),
   375  	}
   376  	indexDeleted := false
   377  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   378  		if r.Method != http.MethodDelete {
   379  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   380  			w.WriteHeader(http.StatusMethodNotAllowed)
   381  			return
   382  		}
   383  		switch r.URL.Path {
   384  		case "/v2/test/blobs/" + blobDesc.Digest.String():
   385  			blobDeleted = true
   386  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
   387  			w.WriteHeader(http.StatusAccepted)
   388  		case "/v2/test/manifests/" + indexDesc.Digest.String():
   389  			indexDeleted = true
   390  			// no "Docker-Content-Digest" header for manifest deletion
   391  			w.WriteHeader(http.StatusAccepted)
   392  		default:
   393  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   394  			w.WriteHeader(http.StatusNotFound)
   395  		}
   396  	}))
   397  	defer ts.Close()
   398  	uri, err := url.Parse(ts.URL)
   399  	if err != nil {
   400  		t.Fatalf("invalid test http server: %v", err)
   401  	}
   402  
   403  	repo, err := NewRepository(uri.Host + "/test")
   404  	if err != nil {
   405  		t.Fatalf("NewRepository() error = %v", err)
   406  	}
   407  	repo.PlainHTTP = true
   408  	ctx := context.Background()
   409  
   410  	err = repo.Delete(ctx, blobDesc)
   411  	if err != nil {
   412  		t.Fatalf("Repository.Delete() error = %v", err)
   413  	}
   414  	if !blobDeleted {
   415  		t.Errorf("Repository.Delete() = %v, want %v", blobDeleted, true)
   416  	}
   417  
   418  	err = repo.Delete(ctx, indexDesc)
   419  	if err != nil {
   420  		t.Fatalf("Repository.Delete() error = %v", err)
   421  	}
   422  	if !indexDeleted {
   423  		t.Errorf("Repository.Delete() = %v, want %v", indexDeleted, true)
   424  	}
   425  }
   426  
   427  func TestRepository_Resolve(t *testing.T) {
   428  	blob := []byte("hello world")
   429  	blobDesc := ocispec.Descriptor{
   430  		MediaType: "test",
   431  		Digest:    digest.FromBytes(blob),
   432  		Size:      int64(len(blob)),
   433  	}
   434  	index := []byte(`{"manifests":[]}`)
   435  	indexDesc := ocispec.Descriptor{
   436  		MediaType: ocispec.MediaTypeImageIndex,
   437  		Digest:    digest.FromBytes(index),
   438  		Size:      int64(len(index)),
   439  	}
   440  	ref := "foobar"
   441  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   442  		if r.Method != http.MethodHead {
   443  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   444  			w.WriteHeader(http.StatusMethodNotAllowed)
   445  			return
   446  		}
   447  		switch r.URL.Path {
   448  		case "/v2/test/manifests/" + blobDesc.Digest.String():
   449  			w.WriteHeader(http.StatusNotFound)
   450  		case "/v2/test/manifests/" + indexDesc.Digest.String(),
   451  			"/v2/test/manifests/" + ref:
   452  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   453  				t.Errorf("manifest not convertable: %s", accept)
   454  				w.WriteHeader(http.StatusBadRequest)
   455  				return
   456  			}
   457  			w.Header().Set("Content-Type", indexDesc.MediaType)
   458  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   459  			w.Header().Set("Content-Length", strconv.Itoa(int(indexDesc.Size)))
   460  		default:
   461  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   462  			w.WriteHeader(http.StatusNotFound)
   463  		}
   464  	}))
   465  	defer ts.Close()
   466  	uri, err := url.Parse(ts.URL)
   467  	if err != nil {
   468  		t.Fatalf("invalid test http server: %v", err)
   469  	}
   470  
   471  	repoName := uri.Host + "/test"
   472  	repo, err := NewRepository(repoName)
   473  	if err != nil {
   474  		t.Fatalf("NewRepository() error = %v", err)
   475  	}
   476  	repo.PlainHTTP = true
   477  	ctx := context.Background()
   478  
   479  	_, err = repo.Resolve(ctx, blobDesc.Digest.String())
   480  	if !errors.Is(err, errdef.ErrNotFound) {
   481  		t.Errorf("Repository.Resolve() error = %v, wantErr %v", err, errdef.ErrNotFound)
   482  	}
   483  
   484  	got, err := repo.Resolve(ctx, indexDesc.Digest.String())
   485  	if err != nil {
   486  		t.Fatalf("Repository.Resolve() error = %v", err)
   487  	}
   488  	if !reflect.DeepEqual(got, indexDesc) {
   489  		t.Errorf("Repository.Resolve() = %v, want %v", got, indexDesc)
   490  	}
   491  
   492  	got, err = repo.Resolve(ctx, ref)
   493  	if err != nil {
   494  		t.Fatalf("Repository.Resolve() error = %v", err)
   495  	}
   496  	if !reflect.DeepEqual(got, indexDesc) {
   497  		t.Errorf("Repository.Resolve() = %v, want %v", got, indexDesc)
   498  	}
   499  
   500  	tagDigestRef := "whatever" + "@" + indexDesc.Digest.String()
   501  	got, err = repo.Resolve(ctx, tagDigestRef)
   502  	if err != nil {
   503  		t.Fatalf("Repository.Resolve() error = %v", err)
   504  	}
   505  	if !reflect.DeepEqual(got, indexDesc) {
   506  		t.Errorf("Repository.Resolve() = %v, want %v", got, indexDesc)
   507  	}
   508  
   509  	fqdnRef := repoName + ":" + tagDigestRef
   510  	got, err = repo.Resolve(ctx, fqdnRef)
   511  	if err != nil {
   512  		t.Fatalf("Repository.Resolve() error = %v", err)
   513  	}
   514  	if !reflect.DeepEqual(got, indexDesc) {
   515  		t.Errorf("Repository.Resolve() = %v, want %v", got, indexDesc)
   516  	}
   517  }
   518  
   519  func TestRepository_Tag(t *testing.T) {
   520  	blob := []byte("hello world")
   521  	blobDesc := ocispec.Descriptor{
   522  		MediaType: "test",
   523  		Digest:    digest.FromBytes(blob),
   524  		Size:      int64(len(blob)),
   525  	}
   526  	index := []byte(`{"manifests":[]}`)
   527  	indexDesc := ocispec.Descriptor{
   528  		MediaType: ocispec.MediaTypeImageIndex,
   529  		Digest:    digest.FromBytes(index),
   530  		Size:      int64(len(index)),
   531  	}
   532  	var gotIndex []byte
   533  	ref := "foobar"
   534  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   535  		switch {
   536  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+blobDesc.Digest.String():
   537  			w.WriteHeader(http.StatusNotFound)
   538  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
   539  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   540  				t.Errorf("manifest not convertable: %s", accept)
   541  				w.WriteHeader(http.StatusBadRequest)
   542  				return
   543  			}
   544  			w.Header().Set("Content-Type", indexDesc.MediaType)
   545  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   546  			if _, err := w.Write(index); err != nil {
   547  				t.Errorf("failed to write %q: %v", r.URL, err)
   548  			}
   549  		case r.Method == http.MethodPut &&
   550  			r.URL.Path == "/v2/test/manifests/"+ref || r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
   551  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
   552  				w.WriteHeader(http.StatusBadRequest)
   553  				break
   554  			}
   555  			buf := bytes.NewBuffer(nil)
   556  			if _, err := buf.ReadFrom(r.Body); err != nil {
   557  				t.Errorf("fail to read: %v", err)
   558  			}
   559  			gotIndex = buf.Bytes()
   560  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   561  			w.WriteHeader(http.StatusCreated)
   562  			return
   563  		default:
   564  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   565  			w.WriteHeader(http.StatusForbidden)
   566  		}
   567  	}))
   568  	defer ts.Close()
   569  	uri, err := url.Parse(ts.URL)
   570  	if err != nil {
   571  		t.Fatalf("invalid test http server: %v", err)
   572  	}
   573  
   574  	repo, err := NewRepository(uri.Host + "/test")
   575  	if err != nil {
   576  		t.Fatalf("NewRepository() error = %v", err)
   577  	}
   578  	repo.PlainHTTP = true
   579  	ctx := context.Background()
   580  
   581  	err = repo.Tag(ctx, blobDesc, ref)
   582  	if err == nil {
   583  		t.Errorf("Repository.Tag() error = %v, wantErr %v", err, true)
   584  	}
   585  
   586  	err = repo.Tag(ctx, indexDesc, ref)
   587  	if err != nil {
   588  		t.Fatalf("Repository.Tag() error = %v", err)
   589  	}
   590  	if !bytes.Equal(gotIndex, index) {
   591  		t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index)
   592  	}
   593  
   594  	gotIndex = nil
   595  	err = repo.Tag(ctx, indexDesc, indexDesc.Digest.String())
   596  	if err != nil {
   597  		t.Fatalf("Repository.Tag() error = %v", err)
   598  	}
   599  	if !bytes.Equal(gotIndex, index) {
   600  		t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index)
   601  	}
   602  }
   603  
   604  func TestRepository_PushReference(t *testing.T) {
   605  	index := []byte(`{"manifests":[]}`)
   606  	indexDesc := ocispec.Descriptor{
   607  		MediaType: ocispec.MediaTypeImageIndex,
   608  		Digest:    digest.FromBytes(index),
   609  		Size:      int64(len(index)),
   610  	}
   611  	var gotIndex []byte
   612  	ref := "foobar"
   613  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   614  		switch {
   615  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+ref:
   616  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
   617  				w.WriteHeader(http.StatusBadRequest)
   618  				break
   619  			}
   620  			buf := bytes.NewBuffer(nil)
   621  			if _, err := buf.ReadFrom(r.Body); err != nil {
   622  				t.Errorf("fail to read: %v", err)
   623  			}
   624  			gotIndex = buf.Bytes()
   625  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   626  			w.WriteHeader(http.StatusCreated)
   627  			return
   628  		default:
   629  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   630  			w.WriteHeader(http.StatusForbidden)
   631  		}
   632  	}))
   633  	defer ts.Close()
   634  	uri, err := url.Parse(ts.URL)
   635  	if err != nil {
   636  		t.Fatalf("invalid test http server: %v", err)
   637  	}
   638  
   639  	repo, err := NewRepository(uri.Host + "/test")
   640  	if err != nil {
   641  		t.Fatalf("NewRepository() error = %v", err)
   642  	}
   643  	repo.PlainHTTP = true
   644  	ctx := context.Background()
   645  	err = repo.PushReference(ctx, indexDesc, bytes.NewReader(index), ref)
   646  	if err != nil {
   647  		t.Fatalf("Repository.PushReference() error = %v", err)
   648  	}
   649  	if !bytes.Equal(gotIndex, index) {
   650  		t.Errorf("Repository.PushReference() = %v, want %v", gotIndex, index)
   651  	}
   652  }
   653  
   654  func TestRepository_FetchReference(t *testing.T) {
   655  	blob := []byte("hello world")
   656  	blobDesc := ocispec.Descriptor{
   657  		MediaType: "test",
   658  		Digest:    digest.FromBytes(blob),
   659  		Size:      int64(len(blob)),
   660  	}
   661  	index := []byte(`{"manifests":[]}`)
   662  	indexDesc := ocispec.Descriptor{
   663  		MediaType: ocispec.MediaTypeImageIndex,
   664  		Digest:    digest.FromBytes(index),
   665  		Size:      int64(len(index)),
   666  	}
   667  	ref := "foobar"
   668  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   669  		if r.Method != http.MethodGet {
   670  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   671  			w.WriteHeader(http.StatusMethodNotAllowed)
   672  			return
   673  		}
   674  		switch r.URL.Path {
   675  		case "/v2/test/manifests/" + blobDesc.Digest.String():
   676  			w.WriteHeader(http.StatusNotFound)
   677  		case "/v2/test/manifests/" + indexDesc.Digest.String(),
   678  			"/v2/test/manifests/" + ref:
   679  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
   680  				t.Errorf("manifest not convertable: %s", accept)
   681  				w.WriteHeader(http.StatusBadRequest)
   682  				return
   683  			}
   684  			w.Header().Set("Content-Type", indexDesc.MediaType)
   685  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
   686  			if _, err := w.Write(index); err != nil {
   687  				t.Errorf("failed to write %q: %v", r.URL, err)
   688  			}
   689  		default:
   690  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   691  			w.WriteHeader(http.StatusNotFound)
   692  		}
   693  	}))
   694  	defer ts.Close()
   695  	uri, err := url.Parse(ts.URL)
   696  	if err != nil {
   697  		t.Fatalf("invalid test http server: %v", err)
   698  	}
   699  
   700  	repoName := uri.Host + "/test"
   701  	repo, err := NewRepository(repoName)
   702  	if err != nil {
   703  		t.Fatalf("NewRepository() error = %v", err)
   704  	}
   705  	repo.PlainHTTP = true
   706  	ctx := context.Background()
   707  
   708  	// test with blob digest
   709  	_, _, err = repo.FetchReference(ctx, blobDesc.Digest.String())
   710  	if !errors.Is(err, errdef.ErrNotFound) {
   711  		t.Errorf("Repository.FetchReference() error = %v, wantErr %v", err, errdef.ErrNotFound)
   712  	}
   713  
   714  	// test with manifest digest
   715  	gotDesc, rc, err := repo.FetchReference(ctx, indexDesc.Digest.String())
   716  	if err != nil {
   717  		t.Fatalf("Repository.FetchReference() error = %v", err)
   718  	}
   719  	if !reflect.DeepEqual(gotDesc, indexDesc) {
   720  		t.Errorf("Repository.FetchReference() = %v, want %v", gotDesc, indexDesc)
   721  	}
   722  	buf := bytes.NewBuffer(nil)
   723  	if _, err := buf.ReadFrom(rc); err != nil {
   724  		t.Errorf("fail to read: %v", err)
   725  	}
   726  	if err := rc.Close(); err != nil {
   727  		t.Errorf("fail to close: %v", err)
   728  	}
   729  	if got := buf.Bytes(); !bytes.Equal(got, index) {
   730  		t.Errorf("Repository.FetchReference() = %v, want %v", got, index)
   731  	}
   732  
   733  	// test with manifest tag
   734  	gotDesc, rc, err = repo.FetchReference(ctx, ref)
   735  	if err != nil {
   736  		t.Fatalf("Repository.FetchReference() error = %v", err)
   737  	}
   738  	if !reflect.DeepEqual(gotDesc, indexDesc) {
   739  		t.Errorf("Repository.FetchReference() = %v, want %v", gotDesc, indexDesc)
   740  	}
   741  	buf.Reset()
   742  	if _, err := buf.ReadFrom(rc); err != nil {
   743  		t.Errorf("fail to read: %v", err)
   744  	}
   745  	if err := rc.Close(); err != nil {
   746  		t.Errorf("fail to close: %v", err)
   747  	}
   748  	if got := buf.Bytes(); !bytes.Equal(got, index) {
   749  		t.Errorf("Repository.FetchReference() = %v, want %v", got, index)
   750  	}
   751  
   752  	// test with manifest tag@digest
   753  	tagDigestRef := "whatever" + "@" + indexDesc.Digest.String()
   754  	gotDesc, rc, err = repo.FetchReference(ctx, tagDigestRef)
   755  	if err != nil {
   756  		t.Fatalf("Repository.FetchReference() error = %v", err)
   757  	}
   758  	if !reflect.DeepEqual(gotDesc, indexDesc) {
   759  		t.Errorf("Repository.FetchReference() = %v, want %v", gotDesc, indexDesc)
   760  	}
   761  	buf.Reset()
   762  	if _, err := buf.ReadFrom(rc); err != nil {
   763  		t.Errorf("fail to read: %v", err)
   764  	}
   765  	if err := rc.Close(); err != nil {
   766  		t.Errorf("fail to close: %v", err)
   767  	}
   768  	if got := buf.Bytes(); !bytes.Equal(got, index) {
   769  		t.Errorf("Repository.FetchReference() = %v, want %v", got, index)
   770  	}
   771  
   772  	// test with manifest FQDN
   773  	fqdnRef := repoName + ":" + tagDigestRef
   774  	gotDesc, rc, err = repo.FetchReference(ctx, fqdnRef)
   775  	if err != nil {
   776  		t.Fatalf("Repository.FetchReference() error = %v", err)
   777  	}
   778  	if !reflect.DeepEqual(gotDesc, indexDesc) {
   779  		t.Errorf("Repository.FetchReference() = %v, want %v", gotDesc, indexDesc)
   780  	}
   781  	buf.Reset()
   782  	if _, err := buf.ReadFrom(rc); err != nil {
   783  		t.Errorf("fail to read: %v", err)
   784  	}
   785  	if err := rc.Close(); err != nil {
   786  		t.Errorf("fail to close: %v", err)
   787  	}
   788  	if got := buf.Bytes(); !bytes.Equal(got, index) {
   789  		t.Errorf("Repository.FetchReference() = %v, want %v", got, index)
   790  	}
   791  }
   792  
   793  func TestRepository_Tags(t *testing.T) {
   794  	tagSet := [][]string{
   795  		{"the", "quick", "brown", "fox"},
   796  		{"jumps", "over", "the", "lazy"},
   797  		{"dog"},
   798  	}
   799  	var ts *httptest.Server
   800  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   801  		if r.Method != http.MethodGet || r.URL.Path != "/v2/test/tags/list" {
   802  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   803  			w.WriteHeader(http.StatusNotFound)
   804  			return
   805  		}
   806  		q := r.URL.Query()
   807  		n, err := strconv.Atoi(q.Get("n"))
   808  		if err != nil || n != 4 {
   809  			t.Errorf("bad page size: %s", q.Get("n"))
   810  			w.WriteHeader(http.StatusBadRequest)
   811  			return
   812  		}
   813  		var tags []string
   814  		switch q.Get("test") {
   815  		case "foo":
   816  			tags = tagSet[1]
   817  			w.Header().Set("Link", fmt.Sprintf(`<%s/v2/test/tags/list?n=4&test=bar>; rel="next"`, ts.URL))
   818  		case "bar":
   819  			tags = tagSet[2]
   820  		default:
   821  			tags = tagSet[0]
   822  			w.Header().Set("Link", `</v2/test/tags/list?n=4&test=foo>; rel="next"`)
   823  		}
   824  		result := struct {
   825  			Tags []string `json:"tags"`
   826  		}{
   827  			Tags: tags,
   828  		}
   829  		if err := json.NewEncoder(w).Encode(result); err != nil {
   830  			t.Errorf("failed to write response: %v", err)
   831  		}
   832  	}))
   833  	defer ts.Close()
   834  	uri, err := url.Parse(ts.URL)
   835  	if err != nil {
   836  		t.Fatalf("invalid test http server: %v", err)
   837  	}
   838  
   839  	repo, err := NewRepository(uri.Host + "/test")
   840  	if err != nil {
   841  		t.Fatalf("NewRepository() error = %v", err)
   842  	}
   843  	repo.PlainHTTP = true
   844  	repo.TagListPageSize = 4
   845  
   846  	ctx := context.Background()
   847  	index := 0
   848  	if err := repo.Tags(ctx, "", func(got []string) error {
   849  		if index > 2 {
   850  			t.Fatalf("out of index bound: %d", index)
   851  		}
   852  		tags := tagSet[index]
   853  		index++
   854  		if !reflect.DeepEqual(got, tags) {
   855  			t.Errorf("Repository.Tags() = %v, want %v", got, tags)
   856  		}
   857  		return nil
   858  	}); err != nil {
   859  		t.Errorf("Repository.Tags() error = %v", err)
   860  	}
   861  }
   862  
   863  func TestRepository_Predecessors(t *testing.T) {
   864  	manifest := []byte(`{"layers":[]}`)
   865  	manifestDesc := ocispec.Descriptor{
   866  		MediaType: ocispec.MediaTypeImageManifest,
   867  		Digest:    digest.FromBytes(manifest),
   868  		Size:      int64(len(manifest)),
   869  	}
   870  	referrerSet := [][]ocispec.Descriptor{
   871  		{
   872  			{
   873  				MediaType:    ocispec.MediaTypeArtifactManifest,
   874  				Size:         1,
   875  				Digest:       digest.FromString("1"),
   876  				ArtifactType: "application/vnd.test",
   877  			},
   878  			{
   879  				MediaType:    ocispec.MediaTypeArtifactManifest,
   880  				Size:         2,
   881  				Digest:       digest.FromString("2"),
   882  				ArtifactType: "application/vnd.test",
   883  			},
   884  		},
   885  		{
   886  			{
   887  				MediaType:    ocispec.MediaTypeArtifactManifest,
   888  				Size:         3,
   889  				Digest:       digest.FromString("3"),
   890  				ArtifactType: "application/vnd.test",
   891  			},
   892  			{
   893  				MediaType:    ocispec.MediaTypeArtifactManifest,
   894  				Size:         4,
   895  				Digest:       digest.FromString("4"),
   896  				ArtifactType: "application/vnd.test",
   897  			},
   898  		},
   899  		{
   900  			{
   901  				MediaType:    ocispec.MediaTypeArtifactManifest,
   902  				Size:         5,
   903  				Digest:       digest.FromString("5"),
   904  				ArtifactType: "application/vnd.test",
   905  			},
   906  		},
   907  	}
   908  	var ts *httptest.Server
   909  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   910  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
   911  		if r.Method != http.MethodGet || r.URL.Path != path {
   912  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
   913  			w.WriteHeader(http.StatusNotFound)
   914  			return
   915  		}
   916  		q := r.URL.Query()
   917  		n, err := strconv.Atoi(q.Get("n"))
   918  		if err != nil || n != 2 {
   919  			t.Errorf("bad page size: %s", q.Get("n"))
   920  			w.WriteHeader(http.StatusBadRequest)
   921  			return
   922  		}
   923  		var referrers []ocispec.Descriptor
   924  		switch q.Get("test") {
   925  		case "foo":
   926  			referrers = referrerSet[1]
   927  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
   928  		case "bar":
   929  			referrers = referrerSet[2]
   930  		default:
   931  			referrers = referrerSet[0]
   932  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
   933  		}
   934  		result := ocispec.Index{
   935  			Versioned: specs.Versioned{
   936  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
   937  			},
   938  			MediaType: ocispec.MediaTypeImageIndex,
   939  			Manifests: referrers,
   940  		}
   941  		if err := json.NewEncoder(w).Encode(result); err != nil {
   942  			t.Errorf("failed to write response: %v", err)
   943  		}
   944  	}))
   945  	defer ts.Close()
   946  	uri, err := url.Parse(ts.URL)
   947  	if err != nil {
   948  		t.Fatalf("invalid test http server: %v", err)
   949  	}
   950  
   951  	repo, err := NewRepository(uri.Host + "/test")
   952  	if err != nil {
   953  		t.Fatalf("NewRepository() error = %v", err)
   954  	}
   955  	repo.PlainHTTP = true
   956  	repo.ReferrerListPageSize = 2
   957  
   958  	ctx := context.Background()
   959  	got, err := repo.Predecessors(ctx, manifestDesc)
   960  	if err != nil {
   961  		t.Fatalf("Repository.Predecessors() error = %v", err)
   962  	}
   963  	var want []ocispec.Descriptor
   964  	for _, referrers := range referrerSet {
   965  		want = append(want, referrers...)
   966  	}
   967  	if !reflect.DeepEqual(got, want) {
   968  		t.Errorf("Repository.Predecessors() = %v, want %v", got, want)
   969  	}
   970  }
   971  
   972  func TestRepository_Referrers(t *testing.T) {
   973  	manifest := []byte(`{"layers":[]}`)
   974  	manifestDesc := ocispec.Descriptor{
   975  		MediaType: ocispec.MediaTypeImageManifest,
   976  		Digest:    digest.FromBytes(manifest),
   977  		Size:      int64(len(manifest)),
   978  	}
   979  	referrerSet := [][]ocispec.Descriptor{
   980  		{
   981  			{
   982  				MediaType:    ocispec.MediaTypeArtifactManifest,
   983  				Size:         1,
   984  				Digest:       digest.FromString("1"),
   985  				ArtifactType: "application/vnd.test",
   986  			},
   987  			{
   988  				MediaType:    ocispec.MediaTypeArtifactManifest,
   989  				Size:         2,
   990  				Digest:       digest.FromString("2"),
   991  				ArtifactType: "application/vnd.test",
   992  			},
   993  		},
   994  		{
   995  			{
   996  				MediaType:    ocispec.MediaTypeArtifactManifest,
   997  				Size:         3,
   998  				Digest:       digest.FromString("3"),
   999  				ArtifactType: "application/vnd.test",
  1000  			},
  1001  			{
  1002  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1003  				Size:         4,
  1004  				Digest:       digest.FromString("4"),
  1005  				ArtifactType: "application/vnd.test",
  1006  			},
  1007  		},
  1008  		{
  1009  			{
  1010  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1011  				Size:         5,
  1012  				Digest:       digest.FromString("5"),
  1013  				ArtifactType: "application/vnd.test",
  1014  			},
  1015  		},
  1016  	}
  1017  	var ts *httptest.Server
  1018  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1019  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1020  		if r.Method != http.MethodGet || r.URL.Path != path {
  1021  			referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1022  			if r.URL.Path != "/v2/test/manifests/"+referrersTag {
  1023  				t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1024  			}
  1025  			w.WriteHeader(http.StatusNotFound)
  1026  			return
  1027  		}
  1028  		q := r.URL.Query()
  1029  		n, err := strconv.Atoi(q.Get("n"))
  1030  		if err != nil || n != 2 {
  1031  			t.Errorf("bad page size: %s", q.Get("n"))
  1032  			w.WriteHeader(http.StatusBadRequest)
  1033  			return
  1034  		}
  1035  		var referrers []ocispec.Descriptor
  1036  		switch q.Get("test") {
  1037  		case "foo":
  1038  			referrers = referrerSet[1]
  1039  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  1040  		case "bar":
  1041  			referrers = referrerSet[2]
  1042  		default:
  1043  			referrers = referrerSet[0]
  1044  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  1045  		}
  1046  		result := ocispec.Index{
  1047  			Versioned: specs.Versioned{
  1048  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1049  			},
  1050  			MediaType: ocispec.MediaTypeImageIndex,
  1051  			Manifests: referrers,
  1052  		}
  1053  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1054  			t.Errorf("failed to write response: %v", err)
  1055  		}
  1056  	}))
  1057  	defer ts.Close()
  1058  	uri, err := url.Parse(ts.URL)
  1059  	if err != nil {
  1060  		t.Fatalf("invalid test http server: %v", err)
  1061  	}
  1062  
  1063  	ctx := context.Background()
  1064  
  1065  	// test auto detect
  1066  	// remote server supports Referrers, should be no error
  1067  	repo, err := NewRepository(uri.Host + "/test")
  1068  	if err != nil {
  1069  		t.Fatalf("NewRepository() error = %v", err)
  1070  	}
  1071  	repo.PlainHTTP = true
  1072  	repo.ReferrerListPageSize = 2
  1073  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1074  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1075  	}
  1076  	index := 0
  1077  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1078  		if index >= len(referrerSet) {
  1079  			t.Fatalf("out of index bound: %d", index)
  1080  		}
  1081  		referrers := referrerSet[index]
  1082  		index++
  1083  		if !reflect.DeepEqual(got, referrers) {
  1084  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1085  		}
  1086  		return nil
  1087  	}); err != nil {
  1088  		t.Errorf("Repository.Referrers() error = %v", err)
  1089  	}
  1090  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1091  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1092  	}
  1093  
  1094  	// test force attempt Referrers
  1095  	// remote server supports Referrers, should be no error
  1096  	repo, err = NewRepository(uri.Host + "/test")
  1097  	if err != nil {
  1098  		t.Fatalf("NewRepository() error = %v", err)
  1099  	}
  1100  	repo.PlainHTTP = true
  1101  	repo.ReferrerListPageSize = 2
  1102  	repo.SetReferrersCapability(true)
  1103  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1104  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1105  	}
  1106  	index = 0
  1107  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1108  		if index >= len(referrerSet) {
  1109  			t.Fatalf("out of index bound: %d", index)
  1110  		}
  1111  		referrers := referrerSet[index]
  1112  		index++
  1113  		if !reflect.DeepEqual(got, referrers) {
  1114  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1115  		}
  1116  		return nil
  1117  	}); err != nil {
  1118  		t.Errorf("Repository.Referrers() error = %v", err)
  1119  	}
  1120  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1121  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1122  	}
  1123  
  1124  	// test force attempt tag schema
  1125  	// request tag schema but got 404, should be no error
  1126  	repo, err = NewRepository(uri.Host + "/test")
  1127  	if err != nil {
  1128  		t.Fatalf("NewRepository() error = %v", err)
  1129  	}
  1130  	repo.PlainHTTP = true
  1131  	repo.ReferrerListPageSize = 2
  1132  	repo.SetReferrersCapability(false)
  1133  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1134  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1135  	}
  1136  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1137  		return nil
  1138  	}); err != nil {
  1139  		t.Errorf("Repository.Referrers() error = %v", err)
  1140  	}
  1141  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1142  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1143  	}
  1144  }
  1145  
  1146  func TestRepository_Referrers_TagSchemaFallback(t *testing.T) {
  1147  	manifest := []byte(`{"layers":[]}`)
  1148  	manifestDesc := ocispec.Descriptor{
  1149  		MediaType: ocispec.MediaTypeImageManifest,
  1150  		Digest:    digest.FromBytes(manifest),
  1151  		Size:      int64(len(manifest)),
  1152  	}
  1153  
  1154  	referrers := []ocispec.Descriptor{
  1155  		{
  1156  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1157  			Size:         1,
  1158  			Digest:       digest.FromString("1"),
  1159  			ArtifactType: "application/vnd.test",
  1160  		},
  1161  		{
  1162  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1163  			Size:         2,
  1164  			Digest:       digest.FromString("2"),
  1165  			ArtifactType: "application/vnd.test",
  1166  		},
  1167  		{
  1168  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1169  			Size:         3,
  1170  			Digest:       digest.FromString("3"),
  1171  			ArtifactType: "application/vnd.test",
  1172  		},
  1173  		{
  1174  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1175  			Size:         4,
  1176  			Digest:       digest.FromString("4"),
  1177  			ArtifactType: "application/vnd.test",
  1178  		},
  1179  		{
  1180  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1181  			Size:         5,
  1182  			Digest:       digest.FromString("5"),
  1183  			ArtifactType: "application/vnd.test",
  1184  		},
  1185  	}
  1186  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1187  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1188  		path := "/v2/test/manifests/" + referrersTag
  1189  		if r.Method != http.MethodGet || r.URL.Path != path {
  1190  			if r.URL.Path != "/v2/test/referrers/"+manifestDesc.Digest.String() {
  1191  				t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1192  			}
  1193  			w.WriteHeader(http.StatusNotFound)
  1194  			return
  1195  		}
  1196  
  1197  		result := ocispec.Index{
  1198  			Versioned: specs.Versioned{
  1199  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1200  			},
  1201  			MediaType: ocispec.MediaTypeImageIndex,
  1202  			Manifests: referrers,
  1203  		}
  1204  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1205  			t.Errorf("failed to write response: %v", err)
  1206  		}
  1207  	}))
  1208  	defer ts.Close()
  1209  	uri, err := url.Parse(ts.URL)
  1210  	if err != nil {
  1211  		t.Fatalf("invalid test http server: %v", err)
  1212  	}
  1213  	ctx := context.Background()
  1214  
  1215  	// test auto detect
  1216  	// remote server does not support Referrers, should fallback to tag schema
  1217  	repo, err := NewRepository(uri.Host + "/test")
  1218  	if err != nil {
  1219  		t.Fatalf("NewRepository() error = %v", err)
  1220  	}
  1221  	repo.PlainHTTP = true
  1222  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1223  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1224  	}
  1225  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1226  		if !reflect.DeepEqual(got, referrers) {
  1227  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1228  		}
  1229  		return nil
  1230  	}); err != nil {
  1231  		t.Errorf("Repository.Referrers() error = %v", err)
  1232  	}
  1233  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1234  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1235  	}
  1236  
  1237  	// test force attempt Referrers
  1238  	// remote server does not support Referrers, should return error
  1239  	repo, err = NewRepository(uri.Host + "/test")
  1240  	if err != nil {
  1241  		t.Fatalf("NewRepository() error = %v", err)
  1242  	}
  1243  	repo.PlainHTTP = true
  1244  	repo.SetReferrersCapability(true)
  1245  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1246  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1247  	}
  1248  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1249  		return nil
  1250  	}); err == nil {
  1251  		t.Errorf("Repository.Referrers() error = %v, wantErr %v", err, true)
  1252  	}
  1253  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1254  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1255  	}
  1256  
  1257  	// test force attempt tag schema
  1258  	// should request tag schema
  1259  	repo, err = NewRepository(uri.Host + "/test")
  1260  	if err != nil {
  1261  		t.Fatalf("NewRepository() error = %v", err)
  1262  	}
  1263  	repo.PlainHTTP = true
  1264  	repo.SetReferrersCapability(false)
  1265  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1266  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1267  	}
  1268  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1269  		if !reflect.DeepEqual(got, referrers) {
  1270  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1271  		}
  1272  		return nil
  1273  	}); err != nil {
  1274  		t.Errorf("Repository.Referrers() error = %v", err)
  1275  	}
  1276  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1277  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1278  	}
  1279  }
  1280  
  1281  func TestRepository_Referrers_TagSchemaFallback_NotFound(t *testing.T) {
  1282  	manifest := []byte(`{"layers":[]}`)
  1283  	manifestDesc := ocispec.Descriptor{
  1284  		MediaType: ocispec.MediaTypeImageManifest,
  1285  		Digest:    digest.FromBytes(manifest),
  1286  		Size:      int64(len(manifest)),
  1287  	}
  1288  
  1289  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1290  		referrersUrl := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1291  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1292  		tagSchemaUrl := "/v2/test/manifests/" + referrersTag
  1293  		if r.Method == http.MethodGet ||
  1294  			r.URL.Path == referrersUrl ||
  1295  			r.URL.Path == tagSchemaUrl {
  1296  			w.WriteHeader(http.StatusNotFound)
  1297  			return
  1298  		}
  1299  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1300  		w.WriteHeader(http.StatusNotFound)
  1301  	}))
  1302  	defer ts.Close()
  1303  	uri, err := url.Parse(ts.URL)
  1304  	if err != nil {
  1305  		t.Fatalf("invalid test http server: %v", err)
  1306  	}
  1307  	ctx := context.Background()
  1308  
  1309  	// test auto detect
  1310  	// tag schema referrers not found, should be no error
  1311  	repo, err := NewRepository(uri.Host + "/test")
  1312  	if err != nil {
  1313  		t.Fatalf("NewRepository() error = %v", err)
  1314  	}
  1315  	repo.PlainHTTP = true
  1316  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1317  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1318  	}
  1319  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1320  		return nil
  1321  	}); err != nil {
  1322  		t.Errorf("Repository.Referrers() error = %v", err)
  1323  	}
  1324  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1325  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1326  	}
  1327  
  1328  	// test force attempt tag schema
  1329  	// tag schema referrers not found, should be no error
  1330  	repo, err = NewRepository(uri.Host + "/test")
  1331  	if err != nil {
  1332  		t.Fatalf("NewRepository() error = %v", err)
  1333  	}
  1334  	repo.PlainHTTP = true
  1335  	repo.SetReferrersCapability(false)
  1336  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1337  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1338  	}
  1339  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1340  		return nil
  1341  	}); err != nil {
  1342  		t.Errorf("Repository.Referrers() error = %v", err)
  1343  	}
  1344  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1345  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1346  	}
  1347  }
  1348  
  1349  func TestRepository_Referrers_BadRequest(t *testing.T) {
  1350  	manifest := []byte(`{"layers":[]}`)
  1351  	manifestDesc := ocispec.Descriptor{
  1352  		MediaType: ocispec.MediaTypeImageManifest,
  1353  		Digest:    digest.FromBytes(manifest),
  1354  		Size:      int64(len(manifest)),
  1355  	}
  1356  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1357  		referrersUrl := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1358  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1359  		tagSchemaUrl := "/v2/test/manifests/" + referrersTag
  1360  		if r.Method == http.MethodGet ||
  1361  			r.URL.Path == referrersUrl ||
  1362  			r.URL.Path == tagSchemaUrl {
  1363  			w.WriteHeader(http.StatusBadRequest)
  1364  			return
  1365  		}
  1366  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1367  		w.WriteHeader(http.StatusNotFound)
  1368  	}))
  1369  	defer ts.Close()
  1370  	uri, err := url.Parse(ts.URL)
  1371  	if err != nil {
  1372  		t.Fatalf("invalid test http server: %v", err)
  1373  	}
  1374  	ctx := context.Background()
  1375  
  1376  	// test auto detect
  1377  	// Referrers returns error
  1378  	repo, err := NewRepository(uri.Host + "/test")
  1379  	if err != nil {
  1380  		t.Fatalf("NewRepository() error = %v", err)
  1381  	}
  1382  	repo.PlainHTTP = true
  1383  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1384  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1385  	}
  1386  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1387  		return nil
  1388  	}); err == nil {
  1389  		t.Errorf("Repository.Referrers() error = nil, wantErr %v", true)
  1390  	}
  1391  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1392  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1393  	}
  1394  
  1395  	// test force attempt Referrers
  1396  	// Referrers returns error
  1397  	repo, err = NewRepository(uri.Host + "/test")
  1398  	if err != nil {
  1399  		t.Fatalf("NewRepository() error = %v", err)
  1400  	}
  1401  	repo.PlainHTTP = true
  1402  	repo.SetReferrersCapability(true)
  1403  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1404  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1405  	}
  1406  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1407  		return nil
  1408  	}); err == nil {
  1409  		t.Errorf("Repository.Referrers() error = nil, wantErr %v", true)
  1410  	}
  1411  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1412  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1413  	}
  1414  
  1415  	// test force attempt tag schema
  1416  	// Referrers returns error
  1417  	repo, err = NewRepository(uri.Host + "/test")
  1418  	if err != nil {
  1419  		t.Fatalf("NewRepository() error = %v", err)
  1420  	}
  1421  	repo.PlainHTTP = true
  1422  	repo.SetReferrersCapability(false)
  1423  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1424  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1425  	}
  1426  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1427  		return nil
  1428  	}); err == nil {
  1429  		t.Errorf("Repository.Referrers() error = nil, wantErr %v", true)
  1430  	}
  1431  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1432  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1433  	}
  1434  }
  1435  
  1436  func TestRepository_Referrers_RepositoryNotFound(t *testing.T) {
  1437  	manifest := []byte(`{"layers":[]}`)
  1438  	manifestDesc := ocispec.Descriptor{
  1439  		MediaType: ocispec.MediaTypeImageManifest,
  1440  		Digest:    digest.FromBytes(manifest),
  1441  		Size:      int64(len(manifest)),
  1442  	}
  1443  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1444  		referrersUrl := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1445  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1446  		tagSchemaUrl := "/v2/test/manifests/" + referrersTag
  1447  		if r.Method == http.MethodGet &&
  1448  			(r.URL.Path == referrersUrl || r.URL.Path == tagSchemaUrl) {
  1449  			w.WriteHeader(http.StatusNotFound)
  1450  			w.Write([]byte(`{ "errors": [ { "code": "NAME_UNKNOWN", "message": "repository name not known to registry" } ] }`))
  1451  			return
  1452  		}
  1453  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1454  		w.WriteHeader(http.StatusNotFound)
  1455  	}))
  1456  	defer ts.Close()
  1457  	uri, err := url.Parse(ts.URL)
  1458  	if err != nil {
  1459  		t.Fatalf("invalid test http server: %v", err)
  1460  	}
  1461  	ctx := context.Background()
  1462  
  1463  	// test auto detect
  1464  	// repository not found, should return error
  1465  	repo, err := NewRepository(uri.Host + "/test")
  1466  	if err != nil {
  1467  		t.Fatalf("NewRepository() error = %v", err)
  1468  	}
  1469  	repo.PlainHTTP = true
  1470  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1471  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1472  	}
  1473  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1474  		return nil
  1475  	}); err == nil {
  1476  		t.Errorf("Repository.Referrers() error = %v, wantErr %v", err, true)
  1477  	}
  1478  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  1479  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  1480  	}
  1481  
  1482  	// test force attempt Referrers
  1483  	// repository not found, should return error
  1484  	repo, err = NewRepository(uri.Host + "/test")
  1485  	if err != nil {
  1486  		t.Fatalf("NewRepository() error = %v", err)
  1487  	}
  1488  	repo.PlainHTTP = true
  1489  	repo.SetReferrersCapability(true)
  1490  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1491  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  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, wantErr %v", err, true)
  1497  	}
  1498  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  1499  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  1500  	}
  1501  
  1502  	// test force attempt tag schema
  1503  	// repository not found, but should not return error
  1504  	repo, err = NewRepository(uri.Host + "/test")
  1505  	if err != nil {
  1506  		t.Fatalf("NewRepository() error = %v", err)
  1507  	}
  1508  	repo.PlainHTTP = true
  1509  	repo.SetReferrersCapability(false)
  1510  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1511  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1512  	}
  1513  	if err := repo.Referrers(ctx, manifestDesc, "", func(got []ocispec.Descriptor) error {
  1514  		return nil
  1515  	}); err != nil {
  1516  		t.Errorf("Repository.Referrers() error = %v, wantErr %v", err, nil)
  1517  	}
  1518  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  1519  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  1520  	}
  1521  }
  1522  
  1523  func TestRepository_Referrers_ServerFiltering(t *testing.T) {
  1524  	manifest := []byte(`{"layers":[]}`)
  1525  	manifestDesc := ocispec.Descriptor{
  1526  		MediaType: ocispec.MediaTypeImageManifest,
  1527  		Digest:    digest.FromBytes(manifest),
  1528  		Size:      int64(len(manifest)),
  1529  	}
  1530  	referrerSet := [][]ocispec.Descriptor{
  1531  		{
  1532  			{
  1533  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1534  				Size:         1,
  1535  				Digest:       digest.FromString("1"),
  1536  				ArtifactType: "application/vnd.test",
  1537  			},
  1538  			{
  1539  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1540  				Size:         2,
  1541  				Digest:       digest.FromString("2"),
  1542  				ArtifactType: "application/vnd.test",
  1543  			},
  1544  		},
  1545  		{
  1546  			{
  1547  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1548  				Size:         3,
  1549  				Digest:       digest.FromString("3"),
  1550  				ArtifactType: "application/vnd.test",
  1551  			},
  1552  			{
  1553  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1554  				Size:         4,
  1555  				Digest:       digest.FromString("4"),
  1556  				ArtifactType: "application/vnd.test",
  1557  			},
  1558  		},
  1559  		{
  1560  			{
  1561  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1562  				Size:         5,
  1563  				Digest:       digest.FromString("5"),
  1564  				ArtifactType: "application/vnd.test",
  1565  			},
  1566  		},
  1567  	}
  1568  	var ts *httptest.Server
  1569  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1570  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1571  		queryParams, err := url.ParseQuery(r.URL.RawQuery)
  1572  		if err != nil {
  1573  			t.Fatal("failed to parse url query")
  1574  		}
  1575  		if r.Method != http.MethodGet ||
  1576  			r.URL.Path != path ||
  1577  			reflect.DeepEqual(queryParams["artifactType"], []string{"application%2Fvnd.test"}) {
  1578  			t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1579  			w.WriteHeader(http.StatusNotFound)
  1580  			return
  1581  		}
  1582  		q := r.URL.Query()
  1583  		n, err := strconv.Atoi(q.Get("n"))
  1584  		if err != nil || n != 2 {
  1585  			t.Errorf("bad page size: %s", q.Get("n"))
  1586  			w.WriteHeader(http.StatusBadRequest)
  1587  			return
  1588  		}
  1589  		var referrers []ocispec.Descriptor
  1590  		switch q.Get("test") {
  1591  		case "foo":
  1592  			referrers = referrerSet[1]
  1593  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  1594  		case "bar":
  1595  			referrers = referrerSet[2]
  1596  		default:
  1597  			referrers = referrerSet[0]
  1598  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  1599  		}
  1600  		result := ocispec.Index{
  1601  			Versioned: specs.Versioned{
  1602  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1603  			},
  1604  			MediaType: ocispec.MediaTypeImageIndex,
  1605  			Manifests: referrers,
  1606  			Annotations: map[string]string{
  1607  				ocispec.AnnotationReferrersFiltersApplied: "artifactType",
  1608  			},
  1609  		}
  1610  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1611  			t.Errorf("failed to write response: %v", err)
  1612  		}
  1613  	}))
  1614  	defer ts.Close()
  1615  	uri, err := url.Parse(ts.URL)
  1616  	if err != nil {
  1617  		t.Fatalf("invalid test http server: %v", err)
  1618  	}
  1619  
  1620  	repo, err := NewRepository(uri.Host + "/test")
  1621  	if err != nil {
  1622  		t.Fatalf("NewRepository() error = %v", err)
  1623  	}
  1624  	repo.PlainHTTP = true
  1625  	repo.ReferrerListPageSize = 2
  1626  
  1627  	ctx := context.Background()
  1628  	index := 0
  1629  	if err := repo.Referrers(ctx, manifestDesc, "application/vnd.test", func(got []ocispec.Descriptor) error {
  1630  		if index >= len(referrerSet) {
  1631  			t.Fatalf("out of index bound: %d", index)
  1632  		}
  1633  		referrers := referrerSet[index]
  1634  		index++
  1635  		if !reflect.DeepEqual(got, referrers) {
  1636  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1637  		}
  1638  		return nil
  1639  	}); err != nil {
  1640  		t.Errorf("Repository.Referrers() error = %v", err)
  1641  	}
  1642  	if index != len(referrerSet) {
  1643  		t.Errorf("fn invoked %d time(s), want %d", index, len(referrerSet))
  1644  	}
  1645  }
  1646  
  1647  func TestRepository_Referrers_ClientFiltering(t *testing.T) {
  1648  	manifest := []byte(`{"layers":[]}`)
  1649  	manifestDesc := ocispec.Descriptor{
  1650  		MediaType: ocispec.MediaTypeImageManifest,
  1651  		Digest:    digest.FromBytes(manifest),
  1652  		Size:      int64(len(manifest)),
  1653  	}
  1654  	referrerSet := [][]ocispec.Descriptor{
  1655  		{
  1656  			{
  1657  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1658  				Size:         1,
  1659  				Digest:       digest.FromString("1"),
  1660  				ArtifactType: "application/vnd.test",
  1661  			},
  1662  			{
  1663  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1664  				Size:         2,
  1665  				Digest:       digest.FromString("2"),
  1666  				ArtifactType: "application/vnd.foo",
  1667  			},
  1668  		},
  1669  		{
  1670  			{
  1671  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1672  				Size:         3,
  1673  				Digest:       digest.FromString("3"),
  1674  				ArtifactType: "application/vnd.test",
  1675  			},
  1676  			{
  1677  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1678  				Size:         4,
  1679  				Digest:       digest.FromString("4"),
  1680  				ArtifactType: "application/vnd.bar",
  1681  			},
  1682  		},
  1683  		{
  1684  			{
  1685  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1686  				Size:         5,
  1687  				Digest:       digest.FromString("5"),
  1688  				ArtifactType: "application/vnd.baz",
  1689  			},
  1690  		},
  1691  	}
  1692  	filteredReferrerSet := [][]ocispec.Descriptor{
  1693  		{
  1694  			{
  1695  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1696  				Size:         1,
  1697  				Digest:       digest.FromString("1"),
  1698  				ArtifactType: "application/vnd.test",
  1699  			},
  1700  		},
  1701  		{
  1702  			{
  1703  				MediaType:    ocispec.MediaTypeArtifactManifest,
  1704  				Size:         3,
  1705  				Digest:       digest.FromString("3"),
  1706  				ArtifactType: "application/vnd.test",
  1707  			},
  1708  		},
  1709  	}
  1710  	var ts *httptest.Server
  1711  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1712  		path := "/v2/test/referrers/" + manifestDesc.Digest.String()
  1713  		queryParams, err := url.ParseQuery(r.URL.RawQuery)
  1714  		if err != nil {
  1715  			t.Fatal("failed to parse url query")
  1716  		}
  1717  		if r.Method != http.MethodGet ||
  1718  			r.URL.Path != path ||
  1719  			reflect.DeepEqual(queryParams["artifactType"], []string{"application%2Fvnd.test"}) {
  1720  			t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1721  			w.WriteHeader(http.StatusNotFound)
  1722  			return
  1723  		}
  1724  		q := r.URL.Query()
  1725  		n, err := strconv.Atoi(q.Get("n"))
  1726  		if err != nil || n != 2 {
  1727  			t.Errorf("bad page size: %s", q.Get("n"))
  1728  			w.WriteHeader(http.StatusBadRequest)
  1729  			return
  1730  		}
  1731  		var referrers []ocispec.Descriptor
  1732  		switch q.Get("test") {
  1733  		case "foo":
  1734  			referrers = referrerSet[1]
  1735  			w.Header().Set("Link", fmt.Sprintf(`<%s%s?n=2&test=bar>; rel="next"`, ts.URL, path))
  1736  		case "bar":
  1737  			referrers = referrerSet[2]
  1738  		default:
  1739  			referrers = referrerSet[0]
  1740  			w.Header().Set("Link", fmt.Sprintf(`<%s?n=2&test=foo>; rel="next"`, path))
  1741  		}
  1742  		result := ocispec.Index{
  1743  			Versioned: specs.Versioned{
  1744  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1745  			},
  1746  			MediaType: ocispec.MediaTypeImageIndex,
  1747  			Manifests: referrers,
  1748  		}
  1749  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1750  			t.Errorf("failed to write response: %v", err)
  1751  		}
  1752  	}))
  1753  	defer ts.Close()
  1754  	uri, err := url.Parse(ts.URL)
  1755  	if err != nil {
  1756  		t.Fatalf("invalid test http server: %v", err)
  1757  	}
  1758  
  1759  	repo, err := NewRepository(uri.Host + "/test")
  1760  	if err != nil {
  1761  		t.Fatalf("NewRepository() error = %v", err)
  1762  	}
  1763  	repo.PlainHTTP = true
  1764  	repo.ReferrerListPageSize = 2
  1765  
  1766  	ctx := context.Background()
  1767  	index := 0
  1768  	if err := repo.Referrers(ctx, manifestDesc, "application/vnd.test", func(got []ocispec.Descriptor) error {
  1769  		if index >= len(filteredReferrerSet) {
  1770  			t.Fatalf("out of index bound: %d", index)
  1771  		}
  1772  		referrers := filteredReferrerSet[index]
  1773  		index++
  1774  		if !reflect.DeepEqual(got, referrers) {
  1775  			t.Errorf("Repository.Referrers() = %v, want %v", got, referrers)
  1776  		}
  1777  		return nil
  1778  	}); err != nil {
  1779  		t.Errorf("Repository.Referrers() error = %v", err)
  1780  	}
  1781  	if index != len(filteredReferrerSet) {
  1782  		t.Errorf("fn invoked %d time(s), want %d", index, len(referrerSet))
  1783  	}
  1784  }
  1785  
  1786  func TestRepository_Referrers_TagSchemaFallback_ClientFiltering(t *testing.T) {
  1787  	manifest := []byte(`{"layers":[]}`)
  1788  	manifestDesc := ocispec.Descriptor{
  1789  		MediaType: ocispec.MediaTypeImageManifest,
  1790  		Digest:    digest.FromBytes(manifest),
  1791  		Size:      int64(len(manifest)),
  1792  	}
  1793  
  1794  	referrers := []ocispec.Descriptor{
  1795  		{
  1796  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1797  			Size:         1,
  1798  			Digest:       digest.FromString("1"),
  1799  			ArtifactType: "application/vnd.test",
  1800  		},
  1801  		{
  1802  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1803  			Size:         2,
  1804  			Digest:       digest.FromString("2"),
  1805  			ArtifactType: "application/vnd.foo",
  1806  		},
  1807  		{
  1808  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1809  			Size:         3,
  1810  			Digest:       digest.FromString("3"),
  1811  			ArtifactType: "application/vnd.test",
  1812  		},
  1813  		{
  1814  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1815  			Size:         4,
  1816  			Digest:       digest.FromString("4"),
  1817  			ArtifactType: "application/vnd.bar",
  1818  		},
  1819  		{
  1820  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1821  			Size:         5,
  1822  			Digest:       digest.FromString("5"),
  1823  			ArtifactType: "application/vnd.baz",
  1824  		},
  1825  	}
  1826  	filteredReferrers := []ocispec.Descriptor{
  1827  		{
  1828  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1829  			Size:         1,
  1830  			Digest:       digest.FromString("1"),
  1831  			ArtifactType: "application/vnd.test",
  1832  		},
  1833  		{
  1834  			MediaType:    ocispec.MediaTypeArtifactManifest,
  1835  			Size:         3,
  1836  			Digest:       digest.FromString("3"),
  1837  			ArtifactType: "application/vnd.test",
  1838  		},
  1839  	}
  1840  
  1841  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1842  		referrersTag := strings.Replace(manifestDesc.Digest.String(), ":", "-", 1)
  1843  		path := "/v2/test/manifests/" + referrersTag
  1844  		if r.Method != http.MethodGet || r.URL.Path != path {
  1845  			if r.URL.Path != "/v2/test/referrers/"+manifestDesc.Digest.String() {
  1846  				t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  1847  			}
  1848  			w.WriteHeader(http.StatusNotFound)
  1849  			return
  1850  		}
  1851  
  1852  		result := ocispec.Index{
  1853  			Versioned: specs.Versioned{
  1854  				SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  1855  			},
  1856  			MediaType: ocispec.MediaTypeImageIndex,
  1857  			Manifests: referrers,
  1858  		}
  1859  		if err := json.NewEncoder(w).Encode(result); err != nil {
  1860  			t.Errorf("failed to write response: %v", err)
  1861  		}
  1862  	}))
  1863  	defer ts.Close()
  1864  	uri, err := url.Parse(ts.URL)
  1865  	if err != nil {
  1866  		t.Fatalf("invalid test http server: %v", err)
  1867  	}
  1868  
  1869  	repo, err := NewRepository(uri.Host + "/test")
  1870  	if err != nil {
  1871  		t.Fatalf("NewRepository() error = %v", err)
  1872  	}
  1873  	repo.PlainHTTP = true
  1874  
  1875  	ctx := context.Background()
  1876  	if err := repo.Referrers(ctx, manifestDesc, "application/vnd.test", func(got []ocispec.Descriptor) error {
  1877  		if !reflect.DeepEqual(got, filteredReferrers) {
  1878  			t.Errorf("Repository.Referrers() = %v, want %v", got, filteredReferrers)
  1879  		}
  1880  		return nil
  1881  	}); err != nil {
  1882  		t.Errorf("Repository.Referrers() error = %v", err)
  1883  	}
  1884  }
  1885  
  1886  func Test_BlobStore_Fetch(t *testing.T) {
  1887  	blob := []byte("hello world")
  1888  	blobDesc := ocispec.Descriptor{
  1889  		MediaType: "test",
  1890  		Digest:    digest.FromBytes(blob),
  1891  		Size:      int64(len(blob)),
  1892  	}
  1893  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1894  		if r.Method != http.MethodGet {
  1895  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  1896  			w.WriteHeader(http.StatusMethodNotAllowed)
  1897  			return
  1898  		}
  1899  		switch r.URL.Path {
  1900  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  1901  			w.Header().Set("Content-Type", "application/octet-stream")
  1902  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  1903  			if _, err := w.Write(blob); err != nil {
  1904  				t.Errorf("failed to write %q: %v", r.URL, err)
  1905  			}
  1906  		default:
  1907  			w.WriteHeader(http.StatusNotFound)
  1908  		}
  1909  	}))
  1910  	defer ts.Close()
  1911  	uri, err := url.Parse(ts.URL)
  1912  	if err != nil {
  1913  		t.Fatalf("invalid test http server: %v", err)
  1914  	}
  1915  
  1916  	repo, err := NewRepository(uri.Host + "/test")
  1917  	if err != nil {
  1918  		t.Fatalf("NewRepository() error = %v", err)
  1919  	}
  1920  	repo.PlainHTTP = true
  1921  	store := repo.Blobs()
  1922  	ctx := context.Background()
  1923  
  1924  	rc, err := store.Fetch(ctx, blobDesc)
  1925  	if err != nil {
  1926  		t.Fatalf("Blobs.Fetch() error = %v", err)
  1927  	}
  1928  	buf := bytes.NewBuffer(nil)
  1929  	if _, err := buf.ReadFrom(rc); err != nil {
  1930  		t.Errorf("fail to read: %v", err)
  1931  	}
  1932  	if err := rc.Close(); err != nil {
  1933  		t.Errorf("fail to close: %v", err)
  1934  	}
  1935  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  1936  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob)
  1937  	}
  1938  
  1939  	content := []byte("foobar")
  1940  	contentDesc := ocispec.Descriptor{
  1941  		MediaType: "test",
  1942  		Digest:    digest.FromBytes(content),
  1943  		Size:      int64(len(content)),
  1944  	}
  1945  	_, err = store.Fetch(ctx, contentDesc)
  1946  	if !errors.Is(err, errdef.ErrNotFound) {
  1947  		t.Errorf("Blobs.Fetch() error = %v, wantErr %v", err, errdef.ErrNotFound)
  1948  	}
  1949  }
  1950  
  1951  func Test_BlobStore_Fetch_Seek(t *testing.T) {
  1952  	blob := []byte("hello world")
  1953  	blobDesc := ocispec.Descriptor{
  1954  		MediaType: "test",
  1955  		Digest:    digest.FromBytes(blob),
  1956  		Size:      int64(len(blob)),
  1957  	}
  1958  	seekable := false
  1959  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1960  		if r.Method != http.MethodGet {
  1961  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  1962  			w.WriteHeader(http.StatusMethodNotAllowed)
  1963  			return
  1964  		}
  1965  		switch r.URL.Path {
  1966  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  1967  			w.Header().Set("Content-Type", "application/octet-stream")
  1968  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  1969  			if seekable {
  1970  				w.Header().Set("Accept-Ranges", "bytes")
  1971  			}
  1972  			rangeHeader := r.Header.Get("Range")
  1973  			if !seekable || rangeHeader == "" {
  1974  				w.WriteHeader(http.StatusOK)
  1975  				if _, err := w.Write(blob); err != nil {
  1976  					t.Errorf("failed to write %q: %v", r.URL, err)
  1977  				}
  1978  				return
  1979  			}
  1980  			var start, end int
  1981  			_, err := fmt.Sscanf(rangeHeader, "bytes=%d-%d", &start, &end)
  1982  			if err != nil {
  1983  				t.Errorf("invalid range header: %s", rangeHeader)
  1984  				w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
  1985  				return
  1986  			}
  1987  			if start < 0 || start > end || start >= int(blobDesc.Size) {
  1988  				t.Errorf("invalid range: %s", rangeHeader)
  1989  				w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
  1990  				return
  1991  			}
  1992  			end++
  1993  			if end > int(blobDesc.Size) {
  1994  				end = int(blobDesc.Size)
  1995  			}
  1996  			w.WriteHeader(http.StatusPartialContent)
  1997  			if _, err := w.Write(blob[start:end]); err != nil {
  1998  				t.Errorf("failed to write %q: %v", r.URL, err)
  1999  			}
  2000  		default:
  2001  			w.WriteHeader(http.StatusNotFound)
  2002  		}
  2003  	}))
  2004  	defer ts.Close()
  2005  	uri, err := url.Parse(ts.URL)
  2006  	if err != nil {
  2007  		t.Fatalf("invalid test http server: %v", err)
  2008  	}
  2009  
  2010  	repo, err := NewRepository(uri.Host + "/test")
  2011  	if err != nil {
  2012  		t.Fatalf("NewRepository() error = %v", err)
  2013  	}
  2014  	repo.PlainHTTP = true
  2015  	store := repo.Blobs()
  2016  	ctx := context.Background()
  2017  
  2018  	rc, err := store.Fetch(ctx, blobDesc)
  2019  	if err != nil {
  2020  		t.Fatalf("Blobs.Fetch() error = %v", err)
  2021  	}
  2022  	if _, ok := rc.(io.Seeker); ok {
  2023  		t.Errorf("Blobs.Fetch() returns io.Seeker on non-seekable content")
  2024  	}
  2025  	buf := bytes.NewBuffer(nil)
  2026  	if _, err := buf.ReadFrom(rc); err != nil {
  2027  		t.Errorf("fail to read: %v", err)
  2028  	}
  2029  	if err := rc.Close(); err != nil {
  2030  		t.Errorf("fail to close: %v", err)
  2031  	}
  2032  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2033  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob)
  2034  	}
  2035  
  2036  	seekable = true
  2037  	rc, err = store.Fetch(ctx, blobDesc)
  2038  	if err != nil {
  2039  		t.Fatalf("Blobs.Fetch() error = %v", err)
  2040  	}
  2041  	s, ok := rc.(io.Seeker)
  2042  	if !ok {
  2043  		t.Fatalf("Blobs.Fetch() = %v, want io.Seeker", rc)
  2044  	}
  2045  	buf.Reset()
  2046  	if _, err := buf.ReadFrom(rc); err != nil {
  2047  		t.Errorf("fail to read: %v", err)
  2048  	}
  2049  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2050  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob)
  2051  	}
  2052  
  2053  	_, err = s.Seek(3, io.SeekStart)
  2054  	if err != nil {
  2055  		t.Errorf("fail to seek: %v", err)
  2056  	}
  2057  	buf.Reset()
  2058  	if _, err := buf.ReadFrom(rc); err != nil {
  2059  		t.Errorf("fail to read: %v", err)
  2060  	}
  2061  	if got := buf.Bytes(); !bytes.Equal(got, blob[3:]) {
  2062  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob[3:])
  2063  	}
  2064  
  2065  	if err := rc.Close(); err != nil {
  2066  		t.Errorf("fail to close: %v", err)
  2067  	}
  2068  }
  2069  
  2070  func Test_BlobStore_Fetch_ZeroSizedBlob(t *testing.T) {
  2071  	blob := []byte("")
  2072  	blobDesc := ocispec.Descriptor{
  2073  		MediaType: "test",
  2074  		Digest:    digest.FromBytes(blob),
  2075  		Size:      int64(len(blob)),
  2076  	}
  2077  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2078  		if r.Method != http.MethodGet {
  2079  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2080  			w.WriteHeader(http.StatusMethodNotAllowed)
  2081  			return
  2082  		}
  2083  
  2084  		switch r.URL.Path {
  2085  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2086  			if rangeHeader := r.Header.Get("Range"); rangeHeader != "" {
  2087  				t.Errorf("unexpected range header")
  2088  				w.WriteHeader(http.StatusBadRequest)
  2089  				return
  2090  			}
  2091  			w.Header().Set("Content-Type", "application/octet-stream")
  2092  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2093  		default:
  2094  			w.WriteHeader(http.StatusNotFound)
  2095  		}
  2096  	}))
  2097  	defer ts.Close()
  2098  	uri, err := url.Parse(ts.URL)
  2099  	if err != nil {
  2100  		t.Fatalf("invalid test http server: %v", err)
  2101  	}
  2102  
  2103  	repo, err := NewRepository(uri.Host + "/test")
  2104  	if err != nil {
  2105  		t.Fatalf("NewRepository() error = %v", err)
  2106  	}
  2107  	repo.PlainHTTP = true
  2108  	store := repo.Blobs()
  2109  	ctx := context.Background()
  2110  
  2111  	rc, err := store.Fetch(ctx, blobDesc)
  2112  	if err != nil {
  2113  		t.Fatalf("Blobs.Fetch() error = %v", err)
  2114  	}
  2115  	buf := bytes.NewBuffer(nil)
  2116  	if _, err := buf.ReadFrom(rc); err != nil {
  2117  		t.Errorf("fail to read: %v", err)
  2118  	}
  2119  	if err := rc.Close(); err != nil {
  2120  		t.Errorf("fail to close: %v", err)
  2121  	}
  2122  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2123  		t.Errorf("Blobs.Fetch() = %v, want %v", got, blob)
  2124  	}
  2125  }
  2126  
  2127  func Test_BlobStore_Push(t *testing.T) {
  2128  	blob := []byte("hello world")
  2129  	blobDesc := ocispec.Descriptor{
  2130  		MediaType: "test",
  2131  		Digest:    digest.FromBytes(blob),
  2132  		Size:      int64(len(blob)),
  2133  	}
  2134  	var gotBlob []byte
  2135  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
  2136  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2137  		switch {
  2138  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
  2139  			w.Header().Set("Location", "/v2/test/blobs/uploads/"+uuid)
  2140  			w.WriteHeader(http.StatusAccepted)
  2141  			return
  2142  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
  2143  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
  2144  				w.WriteHeader(http.StatusBadRequest)
  2145  				break
  2146  			}
  2147  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
  2148  				w.WriteHeader(http.StatusBadRequest)
  2149  				break
  2150  			}
  2151  			buf := bytes.NewBuffer(nil)
  2152  			if _, err := buf.ReadFrom(r.Body); err != nil {
  2153  				t.Errorf("fail to read: %v", err)
  2154  			}
  2155  			gotBlob = buf.Bytes()
  2156  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2157  			w.WriteHeader(http.StatusCreated)
  2158  			return
  2159  		default:
  2160  			w.WriteHeader(http.StatusForbidden)
  2161  		}
  2162  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2163  	}))
  2164  	defer ts.Close()
  2165  	uri, err := url.Parse(ts.URL)
  2166  	if err != nil {
  2167  		t.Fatalf("invalid test http server: %v", err)
  2168  	}
  2169  
  2170  	repo, err := NewRepository(uri.Host + "/test")
  2171  	if err != nil {
  2172  		t.Fatalf("NewRepository() error = %v", err)
  2173  	}
  2174  	repo.PlainHTTP = true
  2175  	store := repo.Blobs()
  2176  	ctx := context.Background()
  2177  
  2178  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  2179  	if err != nil {
  2180  		t.Fatalf("Blobs.Push() error = %v", err)
  2181  	}
  2182  	if !bytes.Equal(gotBlob, blob) {
  2183  		t.Errorf("Blobs.Push() = %v, want %v", gotBlob, blob)
  2184  	}
  2185  }
  2186  
  2187  func Test_BlobStore_Exists(t *testing.T) {
  2188  	blob := []byte("hello world")
  2189  	blobDesc := ocispec.Descriptor{
  2190  		MediaType: "test",
  2191  		Digest:    digest.FromBytes(blob),
  2192  		Size:      int64(len(blob)),
  2193  	}
  2194  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2195  		if r.Method != http.MethodHead {
  2196  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2197  			w.WriteHeader(http.StatusMethodNotAllowed)
  2198  			return
  2199  		}
  2200  		switch r.URL.Path {
  2201  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2202  			w.Header().Set("Content-Type", "application/octet-stream")
  2203  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2204  			w.Header().Set("Content-Length", strconv.Itoa(int(blobDesc.Size)))
  2205  		default:
  2206  			w.WriteHeader(http.StatusNotFound)
  2207  		}
  2208  	}))
  2209  	defer ts.Close()
  2210  	uri, err := url.Parse(ts.URL)
  2211  	if err != nil {
  2212  		t.Fatalf("invalid test http server: %v", err)
  2213  	}
  2214  
  2215  	repo, err := NewRepository(uri.Host + "/test")
  2216  	if err != nil {
  2217  		t.Fatalf("NewRepository() error = %v", err)
  2218  	}
  2219  	repo.PlainHTTP = true
  2220  	store := repo.Blobs()
  2221  	ctx := context.Background()
  2222  
  2223  	exists, err := store.Exists(ctx, blobDesc)
  2224  	if err != nil {
  2225  		t.Fatalf("Blobs.Exists() error = %v", err)
  2226  	}
  2227  	if !exists {
  2228  		t.Errorf("Blobs.Exists() = %v, want %v", exists, true)
  2229  	}
  2230  
  2231  	content := []byte("foobar")
  2232  	contentDesc := ocispec.Descriptor{
  2233  		MediaType: "test",
  2234  		Digest:    digest.FromBytes(content),
  2235  		Size:      int64(len(content)),
  2236  	}
  2237  	exists, err = store.Exists(ctx, contentDesc)
  2238  	if err != nil {
  2239  		t.Fatalf("Blobs.Exists() error = %v", err)
  2240  	}
  2241  	if exists {
  2242  		t.Errorf("Blobs.Exists() = %v, want %v", exists, false)
  2243  	}
  2244  }
  2245  
  2246  func Test_BlobStore_Delete(t *testing.T) {
  2247  	blob := []byte("hello world")
  2248  	blobDesc := ocispec.Descriptor{
  2249  		MediaType: "test",
  2250  		Digest:    digest.FromBytes(blob),
  2251  		Size:      int64(len(blob)),
  2252  	}
  2253  	blobDeleted := false
  2254  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2255  		if r.Method != http.MethodDelete {
  2256  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2257  			w.WriteHeader(http.StatusMethodNotAllowed)
  2258  			return
  2259  		}
  2260  		switch r.URL.Path {
  2261  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2262  			blobDeleted = true
  2263  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2264  			w.WriteHeader(http.StatusAccepted)
  2265  		default:
  2266  			w.WriteHeader(http.StatusNotFound)
  2267  		}
  2268  	}))
  2269  	defer ts.Close()
  2270  	uri, err := url.Parse(ts.URL)
  2271  	if err != nil {
  2272  		t.Fatalf("invalid test http server: %v", err)
  2273  	}
  2274  
  2275  	repo, err := NewRepository(uri.Host + "/test")
  2276  	if err != nil {
  2277  		t.Fatalf("NewRepository() error = %v", err)
  2278  	}
  2279  	repo.PlainHTTP = true
  2280  	store := repo.Blobs()
  2281  	ctx := context.Background()
  2282  
  2283  	err = store.Delete(ctx, blobDesc)
  2284  	if err != nil {
  2285  		t.Fatalf("Blobs.Delete() error = %v", err)
  2286  	}
  2287  	if !blobDeleted {
  2288  		t.Errorf("Blobs.Delete() = %v, want %v", blobDeleted, true)
  2289  	}
  2290  
  2291  	content := []byte("foobar")
  2292  	contentDesc := ocispec.Descriptor{
  2293  		MediaType: "test",
  2294  		Digest:    digest.FromBytes(content),
  2295  		Size:      int64(len(content)),
  2296  	}
  2297  	err = store.Delete(ctx, contentDesc)
  2298  	if !errors.Is(err, errdef.ErrNotFound) {
  2299  		t.Errorf("Blobs.Delete() error = %v, wantErr %v", err, errdef.ErrNotFound)
  2300  	}
  2301  }
  2302  
  2303  func Test_BlobStore_Resolve(t *testing.T) {
  2304  	blob := []byte("hello world")
  2305  	blobDesc := ocispec.Descriptor{
  2306  		MediaType: "test",
  2307  		Digest:    digest.FromBytes(blob),
  2308  		Size:      int64(len(blob)),
  2309  	}
  2310  	ref := "foobar"
  2311  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2312  		if r.Method != http.MethodHead {
  2313  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2314  			w.WriteHeader(http.StatusMethodNotAllowed)
  2315  			return
  2316  		}
  2317  		switch r.URL.Path {
  2318  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2319  			w.Header().Set("Content-Type", "application/octet-stream")
  2320  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2321  			w.Header().Set("Content-Length", strconv.Itoa(int(blobDesc.Size)))
  2322  		default:
  2323  			w.WriteHeader(http.StatusNotFound)
  2324  		}
  2325  	}))
  2326  	defer ts.Close()
  2327  	uri, err := url.Parse(ts.URL)
  2328  	if err != nil {
  2329  		t.Fatalf("invalid test http server: %v", err)
  2330  	}
  2331  
  2332  	repoName := uri.Host + "/test"
  2333  	repo, err := NewRepository(repoName)
  2334  	if err != nil {
  2335  		t.Fatalf("NewRepository() error = %v", err)
  2336  	}
  2337  	repo.PlainHTTP = true
  2338  	store := repo.Blobs()
  2339  	ctx := context.Background()
  2340  
  2341  	got, err := store.Resolve(ctx, blobDesc.Digest.String())
  2342  	if err != nil {
  2343  		t.Fatalf("Blobs.Resolve() error = %v", err)
  2344  	}
  2345  	if got.Digest != blobDesc.Digest || got.Size != blobDesc.Size {
  2346  		t.Errorf("Blobs.Resolve() = %v, want %v", got, blobDesc)
  2347  	}
  2348  
  2349  	_, err = store.Resolve(ctx, ref)
  2350  	if !errors.Is(err, digest.ErrDigestInvalidFormat) {
  2351  		t.Errorf("Blobs.Resolve() error = %v, wantErr %v", err, digest.ErrDigestInvalidFormat)
  2352  	}
  2353  
  2354  	fqdnRef := repoName + "@" + blobDesc.Digest.String()
  2355  	got, err = store.Resolve(ctx, fqdnRef)
  2356  	if err != nil {
  2357  		t.Fatalf("Blobs.Resolve() error = %v", err)
  2358  	}
  2359  	if got.Digest != blobDesc.Digest || got.Size != blobDesc.Size {
  2360  		t.Errorf("Blobs.Resolve() = %v, want %v", got, blobDesc)
  2361  	}
  2362  
  2363  	content := []byte("foobar")
  2364  	contentDesc := ocispec.Descriptor{
  2365  		MediaType: "test",
  2366  		Digest:    digest.FromBytes(content),
  2367  		Size:      int64(len(content)),
  2368  	}
  2369  	_, err = store.Resolve(ctx, contentDesc.Digest.String())
  2370  	if !errors.Is(err, errdef.ErrNotFound) {
  2371  		t.Errorf("Blobs.Resolve() error = %v, wantErr %v", err, errdef.ErrNotFound)
  2372  	}
  2373  }
  2374  
  2375  func Test_BlobStore_FetchReference(t *testing.T) {
  2376  	blob := []byte("hello world")
  2377  	blobDesc := ocispec.Descriptor{
  2378  		MediaType: "test",
  2379  		Digest:    digest.FromBytes(blob),
  2380  		Size:      int64(len(blob)),
  2381  	}
  2382  	ref := "foobar"
  2383  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2384  		if r.Method != http.MethodGet {
  2385  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2386  			w.WriteHeader(http.StatusMethodNotAllowed)
  2387  			return
  2388  		}
  2389  		switch r.URL.Path {
  2390  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2391  			w.Header().Set("Content-Type", "application/octet-stream")
  2392  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2393  			if _, err := w.Write(blob); err != nil {
  2394  				t.Errorf("failed to write %q: %v", r.URL, err)
  2395  			}
  2396  		default:
  2397  			w.WriteHeader(http.StatusNotFound)
  2398  		}
  2399  	}))
  2400  	defer ts.Close()
  2401  	uri, err := url.Parse(ts.URL)
  2402  	if err != nil {
  2403  		t.Fatalf("invalid test http server: %v", err)
  2404  	}
  2405  
  2406  	repoName := uri.Host + "/test"
  2407  	repo, err := NewRepository(repoName)
  2408  	if err != nil {
  2409  		t.Fatalf("NewRepository() error = %v", err)
  2410  	}
  2411  	repo.PlainHTTP = true
  2412  	store := repo.Blobs()
  2413  	ctx := context.Background()
  2414  
  2415  	// test with digest
  2416  	gotDesc, rc, err := store.FetchReference(ctx, blobDesc.Digest.String())
  2417  	if err != nil {
  2418  		t.Fatalf("Blobs.FetchReference() error = %v", err)
  2419  	}
  2420  	if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size {
  2421  		t.Errorf("Blobs.FetchReference() = %v, want %v", gotDesc, blobDesc)
  2422  	}
  2423  	buf := bytes.NewBuffer(nil)
  2424  	if _, err := buf.ReadFrom(rc); err != nil {
  2425  		t.Errorf("fail to read: %v", err)
  2426  	}
  2427  	if err := rc.Close(); err != nil {
  2428  		t.Errorf("fail to close: %v", err)
  2429  	}
  2430  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2431  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob)
  2432  	}
  2433  
  2434  	// test with non-digest reference
  2435  	_, _, err = store.FetchReference(ctx, ref)
  2436  	if !errors.Is(err, digest.ErrDigestInvalidFormat) {
  2437  		t.Errorf("Blobs.FetchReference() error = %v, wantErr %v", err, digest.ErrDigestInvalidFormat)
  2438  	}
  2439  
  2440  	// test with FQDN reference
  2441  	fqdnRef := repoName + "@" + blobDesc.Digest.String()
  2442  	gotDesc, rc, err = store.FetchReference(ctx, fqdnRef)
  2443  	if err != nil {
  2444  		t.Fatalf("Blobs.FetchReference() error = %v", err)
  2445  	}
  2446  	if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size {
  2447  		t.Errorf("Blobs.FetchReference() = %v, want %v", gotDesc, blobDesc)
  2448  	}
  2449  	buf.Reset()
  2450  	if _, err := buf.ReadFrom(rc); err != nil {
  2451  		t.Errorf("fail to read: %v", err)
  2452  	}
  2453  	if err := rc.Close(); err != nil {
  2454  		t.Errorf("fail to close: %v", err)
  2455  	}
  2456  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2457  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob)
  2458  	}
  2459  
  2460  	content := []byte("foobar")
  2461  	contentDesc := ocispec.Descriptor{
  2462  		MediaType: "test",
  2463  		Digest:    digest.FromBytes(content),
  2464  		Size:      int64(len(content)),
  2465  	}
  2466  
  2467  	// test with other digest
  2468  	_, _, err = store.FetchReference(ctx, contentDesc.Digest.String())
  2469  	if !errors.Is(err, errdef.ErrNotFound) {
  2470  		t.Errorf("Blobs.FetchReference() error = %v, wantErr %v", err, errdef.ErrNotFound)
  2471  	}
  2472  }
  2473  
  2474  func Test_BlobStore_FetchReference_Seek(t *testing.T) {
  2475  	blob := []byte("hello world")
  2476  	blobDesc := ocispec.Descriptor{
  2477  		MediaType: "test",
  2478  		Digest:    digest.FromBytes(blob),
  2479  		Size:      int64(len(blob)),
  2480  	}
  2481  	seekable := false
  2482  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2483  		if r.Method != http.MethodGet {
  2484  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2485  			w.WriteHeader(http.StatusMethodNotAllowed)
  2486  			return
  2487  		}
  2488  		switch r.URL.Path {
  2489  		case "/v2/test/blobs/" + blobDesc.Digest.String():
  2490  			w.Header().Set("Content-Type", "application/octet-stream")
  2491  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  2492  			if seekable {
  2493  				w.Header().Set("Accept-Ranges", "bytes")
  2494  			}
  2495  			rangeHeader := r.Header.Get("Range")
  2496  			if !seekable || rangeHeader == "" {
  2497  				w.WriteHeader(http.StatusOK)
  2498  				if _, err := w.Write(blob); err != nil {
  2499  					t.Errorf("failed to write %q: %v", r.URL, err)
  2500  				}
  2501  				return
  2502  			}
  2503  			var start int
  2504  			_, err := fmt.Sscanf(rangeHeader, "bytes=%d-", &start)
  2505  			if err != nil {
  2506  				t.Errorf("invalid range header: %s", rangeHeader)
  2507  				w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
  2508  				return
  2509  			}
  2510  			if start < 0 || start >= int(blobDesc.Size) {
  2511  				t.Errorf("invalid range: %s", rangeHeader)
  2512  				w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
  2513  				return
  2514  			}
  2515  
  2516  			w.WriteHeader(http.StatusPartialContent)
  2517  			if _, err := w.Write(blob[start:]); err != nil {
  2518  				t.Errorf("failed to write %q: %v", r.URL, err)
  2519  			}
  2520  		default:
  2521  			w.WriteHeader(http.StatusNotFound)
  2522  		}
  2523  	}))
  2524  	defer ts.Close()
  2525  	uri, err := url.Parse(ts.URL)
  2526  	if err != nil {
  2527  		t.Fatalf("invalid test http server: %v", err)
  2528  	}
  2529  
  2530  	repo, err := NewRepository(uri.Host + "/test")
  2531  	if err != nil {
  2532  		t.Fatalf("NewRepository() error = %v", err)
  2533  	}
  2534  	repo.PlainHTTP = true
  2535  	store := repo.Blobs()
  2536  	ctx := context.Background()
  2537  
  2538  	// test non-seekable content
  2539  	gotDesc, rc, err := store.FetchReference(ctx, blobDesc.Digest.String())
  2540  	if err != nil {
  2541  		t.Fatalf("Blobs.FetchReference() error = %v", err)
  2542  	}
  2543  	if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size {
  2544  		t.Errorf("Blobs.FetchReference() = %v, want %v", gotDesc, blobDesc)
  2545  	}
  2546  	if _, ok := rc.(io.Seeker); ok {
  2547  		t.Errorf("Blobs.FetchReference() returns io.Seeker on non-seekable content")
  2548  	}
  2549  	buf := bytes.NewBuffer(nil)
  2550  	if _, err := buf.ReadFrom(rc); err != nil {
  2551  		t.Errorf("fail to read: %v", err)
  2552  	}
  2553  	if err := rc.Close(); err != nil {
  2554  		t.Errorf("fail to close: %v", err)
  2555  	}
  2556  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2557  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob)
  2558  	}
  2559  
  2560  	// test seekable content
  2561  	seekable = true
  2562  	gotDesc, rc, err = store.FetchReference(ctx, blobDesc.Digest.String())
  2563  	if err != nil {
  2564  		t.Fatalf("Blobs.FetchReference() error = %v", err)
  2565  	}
  2566  	if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size {
  2567  		t.Errorf("Blobs.FetchReference() = %v, want %v", gotDesc, blobDesc)
  2568  	}
  2569  	s, ok := rc.(io.Seeker)
  2570  	if !ok {
  2571  		t.Fatalf("Blobs.FetchReference() = %v, want io.Seeker", rc)
  2572  	}
  2573  	buf.Reset()
  2574  	if _, err := buf.ReadFrom(rc); err != nil {
  2575  		t.Errorf("fail to read: %v", err)
  2576  	}
  2577  	if got := buf.Bytes(); !bytes.Equal(got, blob) {
  2578  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob)
  2579  	}
  2580  
  2581  	_, err = s.Seek(3, io.SeekStart)
  2582  	if err != nil {
  2583  		t.Errorf("fail to seek: %v", err)
  2584  	}
  2585  	buf.Reset()
  2586  	if _, err := buf.ReadFrom(rc); err != nil {
  2587  		t.Errorf("fail to read: %v", err)
  2588  	}
  2589  	if got := buf.Bytes(); !bytes.Equal(got, blob[3:]) {
  2590  		t.Errorf("Blobs.FetchReference() = %v, want %v", got, blob[3:])
  2591  	}
  2592  
  2593  	if err := rc.Close(); err != nil {
  2594  		t.Errorf("fail to close: %v", err)
  2595  	}
  2596  }
  2597  
  2598  func Test_generateBlobDescriptorWithVariousDockerContentDigestHeaders(t *testing.T) {
  2599  	reference := registry.Reference{
  2600  		Registry:   "eastern.haan.com",
  2601  		Reference:  "<calculate>",
  2602  		Repository: "from25to220ce",
  2603  	}
  2604  	tests := getTestIOStructMapForGetDescriptorClass()
  2605  	for testName, dcdIOStruct := range tests {
  2606  		if dcdIOStruct.isTag {
  2607  			continue
  2608  		}
  2609  
  2610  		for i, method := range []string{http.MethodGet, http.MethodHead} {
  2611  			reference.Reference = dcdIOStruct.clientSuppliedReference
  2612  
  2613  			resp := http.Response{
  2614  				Header: http.Header{
  2615  					"Content-Type":            []string{"application/vnd.docker.distribution.manifest.v2+json"},
  2616  					dockerContentDigestHeader: []string{dcdIOStruct.serverCalculatedDigest.String()},
  2617  				},
  2618  			}
  2619  			if method == http.MethodGet {
  2620  				resp.Body = io.NopCloser(bytes.NewBufferString(theAmazingBanClan))
  2621  			}
  2622  			resp.Request = &http.Request{
  2623  				Method: method,
  2624  			}
  2625  
  2626  			var err error
  2627  			var d digest.Digest
  2628  			if d, err = reference.Digest(); err != nil {
  2629  				t.Errorf(
  2630  					"[Blob.%v] %v; got digest from a tag reference unexpectedly",
  2631  					method, testName,
  2632  				)
  2633  			}
  2634  
  2635  			errExpected := []bool{dcdIOStruct.errExpectedOnGET, dcdIOStruct.errExpectedOnHEAD}[i]
  2636  			if len(d) == 0 {
  2637  				// To avoid an otherwise impossible scenario in the tested code
  2638  				// path, we set d so that verifyContentDigest does not break.
  2639  				d = dcdIOStruct.serverCalculatedDigest
  2640  			}
  2641  			_, err = generateBlobDescriptor(&resp, d)
  2642  			if !errExpected && err != nil {
  2643  				t.Errorf(
  2644  					"[Blob.%v] %v; expected no error for request, but got err: %v",
  2645  					method, testName, err,
  2646  				)
  2647  			} else if errExpected && err == nil {
  2648  				t.Errorf(
  2649  					"[Blob.%v] %v; expected an error for request, but got none",
  2650  					method, testName,
  2651  				)
  2652  			}
  2653  		}
  2654  	}
  2655  }
  2656  
  2657  func TestManifestStoreInterface(t *testing.T) {
  2658  	var ms interface{} = &manifestStore{}
  2659  	if _, ok := ms.(interfaces.ReferenceParser); !ok {
  2660  		t.Error("&manifestStore{} does not conform interfaces.ReferenceParser")
  2661  	}
  2662  }
  2663  
  2664  func Test_ManifestStore_Fetch(t *testing.T) {
  2665  	manifest := []byte(`{"layers":[]}`)
  2666  	manifestDesc := ocispec.Descriptor{
  2667  		MediaType: ocispec.MediaTypeImageManifest,
  2668  		Digest:    digest.FromBytes(manifest),
  2669  		Size:      int64(len(manifest)),
  2670  	}
  2671  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2672  		if r.Method != http.MethodGet {
  2673  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2674  			w.WriteHeader(http.StatusMethodNotAllowed)
  2675  			return
  2676  		}
  2677  		switch r.URL.Path {
  2678  		case "/v2/test/manifests/" + manifestDesc.Digest.String():
  2679  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  2680  				t.Errorf("manifest not convertable: %s", accept)
  2681  				w.WriteHeader(http.StatusBadRequest)
  2682  				return
  2683  			}
  2684  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  2685  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  2686  			if _, err := w.Write(manifest); err != nil {
  2687  				t.Errorf("failed to write %q: %v", r.URL, err)
  2688  			}
  2689  		default:
  2690  			w.WriteHeader(http.StatusNotFound)
  2691  		}
  2692  	}))
  2693  	defer ts.Close()
  2694  	uri, err := url.Parse(ts.URL)
  2695  	if err != nil {
  2696  		t.Fatalf("invalid test http server: %v", err)
  2697  	}
  2698  
  2699  	repo, err := NewRepository(uri.Host + "/test")
  2700  	if err != nil {
  2701  		t.Fatalf("NewRepository() error = %v", err)
  2702  	}
  2703  	repo.PlainHTTP = true
  2704  	store := repo.Manifests()
  2705  	ctx := context.Background()
  2706  
  2707  	rc, err := store.Fetch(ctx, manifestDesc)
  2708  	if err != nil {
  2709  		t.Fatalf("Manifests.Fetch() error = %v", err)
  2710  	}
  2711  	buf := bytes.NewBuffer(nil)
  2712  	if _, err := buf.ReadFrom(rc); err != nil {
  2713  		t.Errorf("fail to read: %v", err)
  2714  	}
  2715  	if err := rc.Close(); err != nil {
  2716  		t.Errorf("fail to close: %v", err)
  2717  	}
  2718  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  2719  		t.Errorf("Manifests.Fetch() = %v, want %v", got, manifest)
  2720  	}
  2721  
  2722  	content := []byte(`{"manifests":[]}`)
  2723  	contentDesc := ocispec.Descriptor{
  2724  		MediaType: ocispec.MediaTypeImageIndex,
  2725  		Digest:    digest.FromBytes(content),
  2726  		Size:      int64(len(content)),
  2727  	}
  2728  	_, err = store.Fetch(ctx, contentDesc)
  2729  	if !errors.Is(err, errdef.ErrNotFound) {
  2730  		t.Errorf("Manifests.Fetch() error = %v, wantErr %v", err, errdef.ErrNotFound)
  2731  	}
  2732  }
  2733  
  2734  func Test_ManifestStore_Push(t *testing.T) {
  2735  	manifest := []byte(`{"layers":[]}`)
  2736  	manifestDesc := ocispec.Descriptor{
  2737  		MediaType: ocispec.MediaTypeImageManifest,
  2738  		Digest:    digest.FromBytes(manifest),
  2739  		Size:      int64(len(manifest)),
  2740  	}
  2741  	var gotManifest []byte
  2742  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2743  		switch {
  2744  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  2745  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  2746  				w.WriteHeader(http.StatusBadRequest)
  2747  				break
  2748  			}
  2749  			buf := bytes.NewBuffer(nil)
  2750  			if _, err := buf.ReadFrom(r.Body); err != nil {
  2751  				t.Errorf("fail to read: %v", err)
  2752  			}
  2753  			gotManifest = buf.Bytes()
  2754  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  2755  			w.WriteHeader(http.StatusCreated)
  2756  			return
  2757  		default:
  2758  			w.WriteHeader(http.StatusForbidden)
  2759  		}
  2760  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2761  	}))
  2762  	defer ts.Close()
  2763  	uri, err := url.Parse(ts.URL)
  2764  	if err != nil {
  2765  		t.Fatalf("invalid test http server: %v", err)
  2766  	}
  2767  
  2768  	repo, err := NewRepository(uri.Host + "/test")
  2769  	if err != nil {
  2770  		t.Fatalf("NewRepository() error = %v", err)
  2771  	}
  2772  	repo.PlainHTTP = true
  2773  	store := repo.Manifests()
  2774  	ctx := context.Background()
  2775  
  2776  	err = store.Push(ctx, manifestDesc, bytes.NewReader(manifest))
  2777  	if err != nil {
  2778  		t.Fatalf("Manifests.Push() error = %v", err)
  2779  	}
  2780  	if !bytes.Equal(gotManifest, manifest) {
  2781  		t.Errorf("Manifests.Push() = %v, want %v", gotManifest, manifest)
  2782  	}
  2783  }
  2784  
  2785  func Test_ManifestStore_Push_ReferrersAPIAvailable(t *testing.T) {
  2786  	// generate test content
  2787  	subject := []byte(`{"layers":[]}`)
  2788  	subjectDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeArtifactManifest, subject)
  2789  	artifact := ocispec.Artifact{
  2790  		MediaType: ocispec.MediaTypeArtifactManifest,
  2791  		Subject:   &subjectDesc,
  2792  	}
  2793  	artifactJSON, err := json.Marshal(artifact)
  2794  	if err != nil {
  2795  		t.Errorf("failed to marshal manifest: %v", err)
  2796  	}
  2797  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  2798  	manifest := ocispec.Manifest{
  2799  		MediaType: ocispec.MediaTypeImageManifest,
  2800  		Subject:   &subjectDesc,
  2801  	}
  2802  	manifestJSON, err := json.Marshal(manifest)
  2803  	if err != nil {
  2804  		t.Errorf("failed to marshal manifest: %v", err)
  2805  	}
  2806  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  2807  
  2808  	var gotManifest []byte
  2809  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2810  		switch {
  2811  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  2812  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  2813  				w.WriteHeader(http.StatusBadRequest)
  2814  				break
  2815  			}
  2816  			buf := bytes.NewBuffer(nil)
  2817  			if _, err := buf.ReadFrom(r.Body); err != nil {
  2818  				t.Errorf("fail to read: %v", err)
  2819  			}
  2820  			gotManifest = buf.Bytes()
  2821  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  2822  			w.WriteHeader(http.StatusCreated)
  2823  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  2824  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  2825  				w.WriteHeader(http.StatusBadRequest)
  2826  				break
  2827  			}
  2828  			buf := bytes.NewBuffer(nil)
  2829  			if _, err := buf.ReadFrom(r.Body); err != nil {
  2830  				t.Errorf("fail to read: %v", err)
  2831  			}
  2832  			gotManifest = buf.Bytes()
  2833  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  2834  			w.WriteHeader(http.StatusCreated)
  2835  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  2836  			result := ocispec.Index{
  2837  				Versioned: specs.Versioned{
  2838  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  2839  				},
  2840  				MediaType: ocispec.MediaTypeImageIndex,
  2841  				Manifests: []ocispec.Descriptor{},
  2842  			}
  2843  			if err := json.NewEncoder(w).Encode(result); err != nil {
  2844  				t.Errorf("failed to write response: %v", err)
  2845  			}
  2846  		default:
  2847  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2848  			w.WriteHeader(http.StatusNotFound)
  2849  		}
  2850  	}))
  2851  	defer ts.Close()
  2852  	uri, err := url.Parse(ts.URL)
  2853  	if err != nil {
  2854  		t.Fatalf("invalid test http server: %v", err)
  2855  	}
  2856  
  2857  	ctx := context.Background()
  2858  	repo, err := NewRepository(uri.Host + "/test")
  2859  	if err != nil {
  2860  		t.Fatalf("NewRepository() error = %v", err)
  2861  	}
  2862  	repo.PlainHTTP = true
  2863  
  2864  	// test push artifact with subject
  2865  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  2866  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  2867  	}
  2868  	err = repo.Push(ctx, artifactDesc, bytes.NewReader(artifactJSON))
  2869  	if err != nil {
  2870  		t.Fatalf("Manifests.Push() error = %v", err)
  2871  	}
  2872  	if !bytes.Equal(gotManifest, artifactJSON) {
  2873  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  2874  	}
  2875  
  2876  	// test push image manifest with subject
  2877  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  2878  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  2879  	}
  2880  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  2881  	if err != nil {
  2882  		t.Fatalf("Manifests.Push() error = %v", err)
  2883  	}
  2884  	if !bytes.Equal(gotManifest, manifestJSON) {
  2885  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  2886  	}
  2887  }
  2888  
  2889  func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) {
  2890  	// generate test content
  2891  	subject := []byte(`{"layers":[]}`)
  2892  	subjectDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeArtifactManifest, subject)
  2893  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  2894  	artifact := ocispec.Artifact{
  2895  		MediaType:    ocispec.MediaTypeArtifactManifest,
  2896  		Subject:      &subjectDesc,
  2897  		ArtifactType: "application/vnd.test",
  2898  		Annotations:  map[string]string{"foo": "bar"},
  2899  	}
  2900  	artifactJSON, err := json.Marshal(artifact)
  2901  	if err != nil {
  2902  		t.Errorf("failed to marshal manifest: %v", err)
  2903  	}
  2904  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  2905  	artifactDesc.ArtifactType = artifact.ArtifactType
  2906  	artifactDesc.Annotations = artifact.Annotations
  2907  
  2908  	// test push artifact with subject
  2909  	index_1 := ocispec.Index{
  2910  		Versioned: specs.Versioned{
  2911  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  2912  		},
  2913  		MediaType: ocispec.MediaTypeImageIndex,
  2914  		Manifests: []ocispec.Descriptor{
  2915  			artifactDesc,
  2916  		},
  2917  	}
  2918  	indexJSON_1, err := json.Marshal(index_1)
  2919  	if err != nil {
  2920  		t.Errorf("failed to marshal manifest: %v", err)
  2921  	}
  2922  	indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1)
  2923  	var gotManifest []byte
  2924  	var gotReferrerIndex []byte
  2925  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2926  		switch {
  2927  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  2928  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  2929  				w.WriteHeader(http.StatusBadRequest)
  2930  				break
  2931  			}
  2932  			buf := bytes.NewBuffer(nil)
  2933  			if _, err := buf.ReadFrom(r.Body); err != nil {
  2934  				t.Errorf("fail to read: %v", err)
  2935  			}
  2936  			gotManifest = buf.Bytes()
  2937  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  2938  			w.WriteHeader(http.StatusCreated)
  2939  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  2940  			w.WriteHeader(http.StatusNotFound)
  2941  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  2942  			w.WriteHeader(http.StatusNotFound)
  2943  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  2944  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  2945  				w.WriteHeader(http.StatusBadRequest)
  2946  				break
  2947  			}
  2948  			buf := bytes.NewBuffer(nil)
  2949  			if _, err := buf.ReadFrom(r.Body); err != nil {
  2950  				t.Errorf("fail to read: %v", err)
  2951  			}
  2952  			gotReferrerIndex = buf.Bytes()
  2953  			w.Header().Set("Docker-Content-Digest", indexDesc_1.Digest.String())
  2954  			w.WriteHeader(http.StatusCreated)
  2955  		default:
  2956  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  2957  			w.WriteHeader(http.StatusNotFound)
  2958  		}
  2959  	}))
  2960  	defer ts.Close()
  2961  	uri, err := url.Parse(ts.URL)
  2962  	if err != nil {
  2963  		t.Fatalf("invalid test http server: %v", err)
  2964  	}
  2965  
  2966  	ctx := context.Background()
  2967  	repo, err := NewRepository(uri.Host + "/test")
  2968  	if err != nil {
  2969  		t.Fatalf("NewRepository() error = %v", err)
  2970  	}
  2971  	repo.PlainHTTP = true
  2972  
  2973  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  2974  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  2975  	}
  2976  	err = repo.Push(ctx, artifactDesc, bytes.NewReader(artifactJSON))
  2977  	if err != nil {
  2978  		t.Fatalf("Manifests.Push() error = %v", err)
  2979  	}
  2980  	if !bytes.Equal(gotManifest, artifactJSON) {
  2981  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  2982  	}
  2983  	if !bytes.Equal(gotReferrerIndex, indexJSON_1) {
  2984  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1))
  2985  	}
  2986  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  2987  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  2988  	}
  2989  
  2990  	// test push image manifest with subject, referrer list should be updated
  2991  	manifest := ocispec.Manifest{
  2992  		MediaType: ocispec.MediaTypeImageManifest,
  2993  		Config: ocispec.Descriptor{
  2994  			MediaType: "testconfig",
  2995  		},
  2996  		Subject:     &subjectDesc,
  2997  		Annotations: map[string]string{"foo": "bar"},
  2998  	}
  2999  	manifestJSON, err := json.Marshal(manifest)
  3000  	if err != nil {
  3001  		t.Errorf("failed to marshal manifest: %v", err)
  3002  	}
  3003  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  3004  	manifestDesc.ArtifactType = manifest.Config.MediaType
  3005  	manifestDesc.Annotations = manifest.Annotations
  3006  	index_2 := ocispec.Index{
  3007  		Versioned: specs.Versioned{
  3008  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3009  		},
  3010  		MediaType: ocispec.MediaTypeImageIndex,
  3011  		Manifests: []ocispec.Descriptor{
  3012  			artifactDesc,
  3013  			manifestDesc,
  3014  		},
  3015  	}
  3016  	indexJSON_2, err := json.Marshal(index_2)
  3017  	if err != nil {
  3018  		t.Errorf("failed to marshal manifest: %v", err)
  3019  	}
  3020  	indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2)
  3021  	var manifestDeleted bool
  3022  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3023  		switch {
  3024  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3025  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  3026  				w.WriteHeader(http.StatusBadRequest)
  3027  				break
  3028  			}
  3029  			buf := bytes.NewBuffer(nil)
  3030  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3031  				t.Errorf("fail to read: %v", err)
  3032  			}
  3033  			gotManifest = buf.Bytes()
  3034  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3035  			w.WriteHeader(http.StatusCreated)
  3036  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  3037  			w.WriteHeader(http.StatusNotFound)
  3038  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3039  			w.Write(indexJSON_1)
  3040  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3041  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  3042  				w.WriteHeader(http.StatusBadRequest)
  3043  				break
  3044  			}
  3045  			buf := bytes.NewBuffer(nil)
  3046  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3047  				t.Errorf("fail to read: %v", err)
  3048  			}
  3049  			gotReferrerIndex = buf.Bytes()
  3050  			w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String())
  3051  			w.WriteHeader(http.StatusCreated)
  3052  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_1.Digest.String():
  3053  			manifestDeleted = true
  3054  			// no "Docker-Content-Digest" header for manifest deletion
  3055  			w.WriteHeader(http.StatusAccepted)
  3056  		default:
  3057  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3058  			w.WriteHeader(http.StatusNotFound)
  3059  		}
  3060  	}))
  3061  	defer ts.Close()
  3062  	uri, err = url.Parse(ts.URL)
  3063  	if err != nil {
  3064  		t.Fatalf("invalid test http server: %v", err)
  3065  	}
  3066  
  3067  	ctx = context.Background()
  3068  	repo, err = NewRepository(uri.Host + "/test")
  3069  	if err != nil {
  3070  		t.Fatalf("NewRepository() error = %v", err)
  3071  	}
  3072  	repo.PlainHTTP = true
  3073  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3074  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3075  	}
  3076  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  3077  	if err != nil {
  3078  		t.Fatalf("Manifests.Push() error = %v", err)
  3079  	}
  3080  	if !bytes.Equal(gotManifest, manifestJSON) {
  3081  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  3082  	}
  3083  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  3084  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  3085  	}
  3086  	if !manifestDeleted {
  3087  		t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, true)
  3088  	}
  3089  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3090  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3091  	}
  3092  
  3093  	// test push image manifest with subject again, referrers list should not be changed
  3094  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3095  		switch {
  3096  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3097  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  3098  				w.WriteHeader(http.StatusBadRequest)
  3099  				break
  3100  			}
  3101  			buf := bytes.NewBuffer(nil)
  3102  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3103  				t.Errorf("fail to read: %v", err)
  3104  			}
  3105  			gotManifest = buf.Bytes()
  3106  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3107  			w.WriteHeader(http.StatusCreated)
  3108  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  3109  			w.WriteHeader(http.StatusNotFound)
  3110  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3111  			w.Write(indexJSON_2)
  3112  		default:
  3113  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3114  			w.WriteHeader(http.StatusNotFound)
  3115  		}
  3116  	}))
  3117  	defer ts.Close()
  3118  	uri, err = url.Parse(ts.URL)
  3119  	if err != nil {
  3120  		t.Fatalf("invalid test http server: %v", err)
  3121  	}
  3122  
  3123  	ctx = context.Background()
  3124  	repo, err = NewRepository(uri.Host + "/test")
  3125  	if err != nil {
  3126  		t.Fatalf("NewRepository() error = %v", err)
  3127  	}
  3128  	repo.PlainHTTP = true
  3129  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3130  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3131  	}
  3132  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  3133  	if err != nil {
  3134  		t.Fatalf("Manifests.Push() error = %v", err)
  3135  	}
  3136  	if !bytes.Equal(gotManifest, manifestJSON) {
  3137  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  3138  	}
  3139  	// referrers list should not be changed
  3140  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  3141  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  3142  	}
  3143  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3144  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3145  	}
  3146  }
  3147  
  3148  func Test_ManifestStore_Exists(t *testing.T) {
  3149  	manifest := []byte(`{"layers":[]}`)
  3150  	manifestDesc := ocispec.Descriptor{
  3151  		MediaType: ocispec.MediaTypeImageManifest,
  3152  		Digest:    digest.FromBytes(manifest),
  3153  		Size:      int64(len(manifest)),
  3154  	}
  3155  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3156  		if r.Method != http.MethodHead {
  3157  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3158  			w.WriteHeader(http.StatusMethodNotAllowed)
  3159  			return
  3160  		}
  3161  		switch r.URL.Path {
  3162  		case "/v2/test/manifests/" + manifestDesc.Digest.String():
  3163  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  3164  				t.Errorf("manifest not convertable: %s", accept)
  3165  				w.WriteHeader(http.StatusBadRequest)
  3166  				return
  3167  			}
  3168  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  3169  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3170  			w.Header().Set("Content-Length", strconv.Itoa(int(manifestDesc.Size)))
  3171  		default:
  3172  			w.WriteHeader(http.StatusNotFound)
  3173  		}
  3174  	}))
  3175  	defer ts.Close()
  3176  	uri, err := url.Parse(ts.URL)
  3177  	if err != nil {
  3178  		t.Fatalf("invalid test http server: %v", err)
  3179  	}
  3180  
  3181  	repo, err := NewRepository(uri.Host + "/test")
  3182  	if err != nil {
  3183  		t.Fatalf("NewRepository() error = %v", err)
  3184  	}
  3185  	repo.PlainHTTP = true
  3186  	store := repo.Manifests()
  3187  	ctx := context.Background()
  3188  
  3189  	exists, err := store.Exists(ctx, manifestDesc)
  3190  	if err != nil {
  3191  		t.Fatalf("Manifests.Exists() error = %v", err)
  3192  	}
  3193  	if !exists {
  3194  		t.Errorf("Manifests.Exists() = %v, want %v", exists, true)
  3195  	}
  3196  
  3197  	content := []byte(`{"manifests":[]}`)
  3198  	contentDesc := ocispec.Descriptor{
  3199  		MediaType: ocispec.MediaTypeImageIndex,
  3200  		Digest:    digest.FromBytes(content),
  3201  		Size:      int64(len(content)),
  3202  	}
  3203  	exists, err = store.Exists(ctx, contentDesc)
  3204  	if err != nil {
  3205  		t.Fatalf("Manifests.Exists() error = %v", err)
  3206  	}
  3207  	if exists {
  3208  		t.Errorf("Manifests.Exists() = %v, want %v", exists, false)
  3209  	}
  3210  }
  3211  
  3212  func Test_ManifestStore_Delete(t *testing.T) {
  3213  	manifest := []byte(`{"layers":[]}`)
  3214  	manifestDesc := ocispec.Descriptor{
  3215  		MediaType: ocispec.MediaTypeImageManifest,
  3216  		Digest:    digest.FromBytes(manifest),
  3217  		Size:      int64(len(manifest)),
  3218  	}
  3219  	manifestDeleted := false
  3220  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3221  		if r.Method != http.MethodDelete && r.Method != http.MethodGet {
  3222  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3223  			w.WriteHeader(http.StatusMethodNotAllowed)
  3224  		}
  3225  		switch {
  3226  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3227  			manifestDeleted = true
  3228  			// no "Docker-Content-Digest" header for manifest deletion
  3229  			w.WriteHeader(http.StatusAccepted)
  3230  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3231  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  3232  				t.Errorf("manifest not convertable: %s", accept)
  3233  				w.WriteHeader(http.StatusBadRequest)
  3234  				return
  3235  			}
  3236  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  3237  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3238  			if _, err := w.Write(manifest); err != nil {
  3239  				t.Errorf("failed to write %q: %v", r.URL, err)
  3240  			}
  3241  		default:
  3242  			w.WriteHeader(http.StatusNotFound)
  3243  		}
  3244  	}))
  3245  	defer ts.Close()
  3246  	uri, err := url.Parse(ts.URL)
  3247  	if err != nil {
  3248  		t.Fatalf("invalid test http server: %v", err)
  3249  	}
  3250  
  3251  	repo, err := NewRepository(uri.Host + "/test")
  3252  	if err != nil {
  3253  		t.Fatalf("NewRepository() error = %v", err)
  3254  	}
  3255  	repo.PlainHTTP = true
  3256  	store := repo.Manifests()
  3257  	ctx := context.Background()
  3258  
  3259  	// test delete manifest without subject
  3260  	err = store.Delete(ctx, manifestDesc)
  3261  	if err != nil {
  3262  		t.Fatalf("Manifests.Delete() error = %v", err)
  3263  	}
  3264  	if !manifestDeleted {
  3265  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3266  	}
  3267  
  3268  	// test delete content that does not exist
  3269  	content := []byte(`{"manifests":[]}`)
  3270  	contentDesc := ocispec.Descriptor{
  3271  		MediaType: ocispec.MediaTypeImageIndex,
  3272  		Digest:    digest.FromBytes(content),
  3273  		Size:      int64(len(content)),
  3274  	}
  3275  	err = store.Delete(ctx, contentDesc)
  3276  	if !errors.Is(err, errdef.ErrNotFound) {
  3277  		t.Errorf("Manifests.Delete() error = %v, wantErr %v", err, errdef.ErrNotFound)
  3278  	}
  3279  }
  3280  
  3281  func Test_ManifestStore_Delete_ReferrersAPIAvailable(t *testing.T) {
  3282  	// generate test content
  3283  	subject := []byte(`{"layers":[]}`)
  3284  	subjectDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeArtifactManifest, subject)
  3285  	artifact := ocispec.Artifact{
  3286  		MediaType: ocispec.MediaTypeArtifactManifest,
  3287  		Subject:   &subjectDesc,
  3288  	}
  3289  	artifactJSON, err := json.Marshal(artifact)
  3290  	if err != nil {
  3291  		t.Errorf("failed to marshal manifest: %v", err)
  3292  	}
  3293  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  3294  	manifest := ocispec.Manifest{
  3295  		MediaType: ocispec.MediaTypeImageManifest,
  3296  		Subject:   &subjectDesc,
  3297  	}
  3298  	manifestJSON, err := json.Marshal(manifest)
  3299  	if err != nil {
  3300  		t.Errorf("failed to marshal manifest: %v", err)
  3301  	}
  3302  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  3303  	manifestDeleted := false
  3304  	artifactDeleted := false
  3305  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3306  		if r.Method != http.MethodDelete && r.Method != http.MethodGet {
  3307  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3308  			w.WriteHeader(http.StatusMethodNotAllowed)
  3309  		}
  3310  		switch {
  3311  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3312  			artifactDeleted = true
  3313  			// no "Docker-Content-Digest" header for manifest deletion
  3314  			w.WriteHeader(http.StatusAccepted)
  3315  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3316  			manifestDeleted = true
  3317  			// no "Docker-Content-Digest" header for manifest deletion
  3318  			w.WriteHeader(http.StatusAccepted)
  3319  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3320  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  3321  				t.Errorf("manifest not convertable: %s", accept)
  3322  				w.WriteHeader(http.StatusBadRequest)
  3323  				return
  3324  			}
  3325  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  3326  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  3327  			if _, err := w.Write(artifactJSON); err != nil {
  3328  				t.Errorf("failed to write %q: %v", r.URL, err)
  3329  			}
  3330  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  3331  			result := ocispec.Index{
  3332  				Versioned: specs.Versioned{
  3333  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3334  				},
  3335  				MediaType: ocispec.MediaTypeImageIndex,
  3336  				Manifests: []ocispec.Descriptor{},
  3337  			}
  3338  			if err := json.NewEncoder(w).Encode(result); err != nil {
  3339  				t.Errorf("failed to write response: %v", err)
  3340  			}
  3341  		default:
  3342  			w.WriteHeader(http.StatusNotFound)
  3343  		}
  3344  	}))
  3345  	defer ts.Close()
  3346  	uri, err := url.Parse(ts.URL)
  3347  	if err != nil {
  3348  		t.Fatalf("invalid test http server: %v", err)
  3349  	}
  3350  	repo, err := NewRepository(uri.Host + "/test")
  3351  	if err != nil {
  3352  		t.Fatalf("NewRepository() error = %v", err)
  3353  	}
  3354  	repo.PlainHTTP = true
  3355  	store := repo.Manifests()
  3356  	ctx := context.Background()
  3357  	// test delete artifact with subject
  3358  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3359  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3360  	}
  3361  	err = store.Delete(ctx, artifactDesc)
  3362  	if err != nil {
  3363  		t.Fatalf("Manifests.Delete() error = %v", err)
  3364  	}
  3365  	if !artifactDeleted {
  3366  		t.Errorf("Manifests.Delete() = %v, want %v", artifactDeleted, true)
  3367  	}
  3368  
  3369  	// test delete manifest with subject
  3370  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  3371  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  3372  	}
  3373  	err = store.Delete(ctx, manifestDesc)
  3374  	if err != nil {
  3375  		t.Fatalf("Manifests.Delete() error = %v", err)
  3376  	}
  3377  	if !manifestDeleted {
  3378  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3379  	}
  3380  
  3381  	// test delete content that does not exist
  3382  	content := []byte("whatever")
  3383  	contentDesc := ocispec.Descriptor{
  3384  		MediaType: ocispec.MediaTypeImageManifest,
  3385  		Digest:    digest.FromBytes(content),
  3386  		Size:      int64(len(content)),
  3387  	}
  3388  	ctx = context.Background()
  3389  	err = store.Delete(ctx, contentDesc)
  3390  	if !errors.Is(err, errdef.ErrNotFound) {
  3391  		t.Errorf("Manifests.Delete() error = %v, wantErr %v", err, errdef.ErrNotFound)
  3392  	}
  3393  }
  3394  
  3395  func Test_ManifestStore_Delete_ReferrersAPIUnavailable(t *testing.T) {
  3396  	// generate test content
  3397  	subject := []byte(`{"layers":[]}`)
  3398  	subjectDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeArtifactManifest, subject)
  3399  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  3400  	artifact := ocispec.Artifact{
  3401  		MediaType: ocispec.MediaTypeArtifactManifest,
  3402  		Subject:   &subjectDesc,
  3403  	}
  3404  	artifactJSON, err := json.Marshal(artifact)
  3405  	if err != nil {
  3406  		t.Errorf("failed to marshal manifest: %v", err)
  3407  	}
  3408  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  3409  	manifest := ocispec.Manifest{
  3410  		MediaType: ocispec.MediaTypeImageManifest,
  3411  		Subject:   &subjectDesc,
  3412  	}
  3413  	manifestJSON, err := json.Marshal(manifest)
  3414  	if err != nil {
  3415  		t.Errorf("failed to marshal manifest: %v", err)
  3416  	}
  3417  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  3418  
  3419  	// test delete artifact with subject
  3420  	index_1 := ocispec.Index{
  3421  		Versioned: specs.Versioned{
  3422  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3423  		},
  3424  		MediaType: ocispec.MediaTypeImageIndex,
  3425  		Manifests: []ocispec.Descriptor{
  3426  			artifactDesc,
  3427  			manifestDesc,
  3428  		},
  3429  	}
  3430  	indexJSON_1, err := json.Marshal(index_1)
  3431  	if err != nil {
  3432  		t.Errorf("failed to marshal manifest: %v", err)
  3433  	}
  3434  	indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1)
  3435  	index_2 := ocispec.Index{
  3436  		Versioned: specs.Versioned{
  3437  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3438  		},
  3439  		MediaType: ocispec.MediaTypeImageIndex,
  3440  		Manifests: []ocispec.Descriptor{
  3441  			manifestDesc,
  3442  		},
  3443  	}
  3444  	indexJSON_2, err := json.Marshal(index_2)
  3445  	if err != nil {
  3446  		t.Errorf("failed to marshal manifest: %v", err)
  3447  	}
  3448  	indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2)
  3449  
  3450  	manifestDeleted := false
  3451  	indexDeleted := false
  3452  	var gotReferrerIndex []byte
  3453  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3454  		switch {
  3455  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3456  			manifestDeleted = true
  3457  			// no "Docker-Content-Digest" header for manifest deletion
  3458  			w.WriteHeader(http.StatusAccepted)
  3459  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3460  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  3461  				t.Errorf("manifest not convertable: %s", accept)
  3462  				w.WriteHeader(http.StatusBadRequest)
  3463  				return
  3464  			}
  3465  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  3466  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  3467  			if _, err := w.Write(artifactJSON); err != nil {
  3468  				t.Errorf("failed to write %q: %v", r.URL, err)
  3469  			}
  3470  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  3471  			w.WriteHeader(http.StatusNotFound)
  3472  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3473  			w.Write(indexJSON_1)
  3474  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3475  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  3476  				w.WriteHeader(http.StatusBadRequest)
  3477  				break
  3478  			}
  3479  			buf := bytes.NewBuffer(nil)
  3480  			if _, err := buf.ReadFrom(r.Body); err != nil {
  3481  				t.Errorf("fail to read: %v", err)
  3482  			}
  3483  			gotReferrerIndex = buf.Bytes()
  3484  			w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String())
  3485  			w.WriteHeader(http.StatusCreated)
  3486  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_1.Digest.String():
  3487  			indexDeleted = true
  3488  			// no "Docker-Content-Digest" header for manifest deletion
  3489  			w.WriteHeader(http.StatusAccepted)
  3490  		default:
  3491  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3492  			w.WriteHeader(http.StatusNotFound)
  3493  		}
  3494  	}))
  3495  	defer ts.Close()
  3496  	uri, err := url.Parse(ts.URL)
  3497  	if err != nil {
  3498  		t.Fatalf("invalid test http server: %v", err)
  3499  	}
  3500  	repo, err := NewRepository(uri.Host + "/test")
  3501  	if err != nil {
  3502  		t.Fatalf("NewRepository() error = %v", err)
  3503  	}
  3504  	repo.PlainHTTP = true
  3505  	store := repo.Manifests()
  3506  	ctx := context.Background()
  3507  
  3508  	// test delete artifact with subject
  3509  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3510  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3511  	}
  3512  	err = store.Delete(ctx, artifactDesc)
  3513  	if err != nil {
  3514  		t.Fatalf("Manifests.Delete() error = %v", err)
  3515  	}
  3516  	if !manifestDeleted {
  3517  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3518  	}
  3519  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  3520  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  3521  	}
  3522  	if !indexDeleted {
  3523  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3524  	}
  3525  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3526  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3527  	}
  3528  
  3529  	// test delete manifest with subject
  3530  	manifestDeleted = false
  3531  	indexDeleted = false
  3532  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3533  		switch {
  3534  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3535  			manifestDeleted = true
  3536  			// no "Docker-Content-Digest" header for manifest deletion
  3537  			w.WriteHeader(http.StatusAccepted)
  3538  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  3539  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  3540  				t.Errorf("manifest not convertable: %s", accept)
  3541  				w.WriteHeader(http.StatusBadRequest)
  3542  				return
  3543  			}
  3544  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  3545  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3546  			if _, err := w.Write(manifestJSON); err != nil {
  3547  				t.Errorf("failed to write %q: %v", r.URL, err)
  3548  			}
  3549  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  3550  			w.WriteHeader(http.StatusNotFound)
  3551  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3552  			w.Write(indexJSON_2)
  3553  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_2.Digest.String():
  3554  			indexDeleted = true
  3555  			// no "Docker-Content-Digest" header for manifest deletion
  3556  			w.WriteHeader(http.StatusAccepted)
  3557  		default:
  3558  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3559  			w.WriteHeader(http.StatusNotFound)
  3560  		}
  3561  	}))
  3562  	defer ts.Close()
  3563  	uri, err = url.Parse(ts.URL)
  3564  	if err != nil {
  3565  		t.Fatalf("invalid test http server: %v", err)
  3566  	}
  3567  	repo, err = NewRepository(uri.Host + "/test")
  3568  	if err != nil {
  3569  		t.Fatalf("NewRepository() error = %v", err)
  3570  	}
  3571  	repo.PlainHTTP = true
  3572  	store = repo.Manifests()
  3573  	ctx = context.Background()
  3574  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3575  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3576  	}
  3577  	err = store.Delete(ctx, manifestDesc)
  3578  	if err != nil {
  3579  		t.Fatalf("Manifests.Delete() error = %v", err)
  3580  	}
  3581  	if !manifestDeleted {
  3582  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3583  	}
  3584  	if !indexDeleted {
  3585  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3586  	}
  3587  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3588  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3589  	}
  3590  }
  3591  
  3592  func Test_ManifestStore_Delete_ReferrersAPIUnavailable_InconsistentIndex(t *testing.T) {
  3593  	// generate test content
  3594  	subject := []byte(`{"layers":[]}`)
  3595  	subjectDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeArtifactManifest, subject)
  3596  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  3597  	artifact := ocispec.Artifact{
  3598  		MediaType: ocispec.MediaTypeArtifactManifest,
  3599  		Subject:   &subjectDesc,
  3600  	}
  3601  	artifactJSON, err := json.Marshal(artifact)
  3602  	if err != nil {
  3603  		t.Errorf("failed to marshal manifest: %v", err)
  3604  	}
  3605  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  3606  
  3607  	// test inconsistent state: index not found
  3608  	manifestDeleted := true
  3609  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3610  		switch {
  3611  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3612  			manifestDeleted = true
  3613  			// no "Docker-Content-Digest" header for manifest deletion
  3614  			w.WriteHeader(http.StatusAccepted)
  3615  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3616  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  3617  				t.Errorf("manifest not convertable: %s", accept)
  3618  				w.WriteHeader(http.StatusBadRequest)
  3619  				return
  3620  			}
  3621  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  3622  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  3623  			if _, err := w.Write(artifactJSON); err != nil {
  3624  				t.Errorf("failed to write %q: %v", r.URL, err)
  3625  			}
  3626  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  3627  			w.WriteHeader(http.StatusNotFound)
  3628  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3629  			w.WriteHeader(http.StatusNotFound)
  3630  		default:
  3631  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3632  			w.WriteHeader(http.StatusNotFound)
  3633  		}
  3634  	}))
  3635  	defer ts.Close()
  3636  	uri, err := url.Parse(ts.URL)
  3637  	if err != nil {
  3638  		t.Fatalf("invalid test http server: %v", err)
  3639  	}
  3640  	repo, err := NewRepository(uri.Host + "/test")
  3641  	if err != nil {
  3642  		t.Fatalf("NewRepository() error = %v", err)
  3643  	}
  3644  	repo.PlainHTTP = true
  3645  	store := repo.Manifests()
  3646  	ctx := context.Background()
  3647  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3648  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3649  	}
  3650  	err = store.Delete(ctx, artifactDesc)
  3651  	if err != nil {
  3652  		t.Fatalf("Manifests.Delete() error = %v", err)
  3653  	}
  3654  	if !manifestDeleted {
  3655  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3656  	}
  3657  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3658  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3659  	}
  3660  
  3661  	// test inconsistent state: empty referrers list
  3662  	manifestDeleted = true
  3663  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3664  		switch {
  3665  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3666  			manifestDeleted = true
  3667  			// no "Docker-Content-Digest" header for manifest deletion
  3668  			w.WriteHeader(http.StatusAccepted)
  3669  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3670  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  3671  				t.Errorf("manifest not convertable: %s", accept)
  3672  				w.WriteHeader(http.StatusBadRequest)
  3673  				return
  3674  			}
  3675  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  3676  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  3677  			if _, err := w.Write(artifactJSON); err != nil {
  3678  				t.Errorf("failed to write %q: %v", r.URL, err)
  3679  			}
  3680  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  3681  			w.WriteHeader(http.StatusNotFound)
  3682  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3683  			result := ocispec.Index{
  3684  				Versioned: specs.Versioned{
  3685  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3686  				},
  3687  				MediaType: ocispec.MediaTypeImageIndex,
  3688  				Manifests: []ocispec.Descriptor{},
  3689  			}
  3690  			if err := json.NewEncoder(w).Encode(result); err != nil {
  3691  				t.Errorf("failed to write response: %v", err)
  3692  			}
  3693  		default:
  3694  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3695  			w.WriteHeader(http.StatusNotFound)
  3696  		}
  3697  	}))
  3698  	defer ts.Close()
  3699  	uri, err = url.Parse(ts.URL)
  3700  	if err != nil {
  3701  		t.Fatalf("invalid test http server: %v", err)
  3702  	}
  3703  	repo, err = NewRepository(uri.Host + "/test")
  3704  	if err != nil {
  3705  		t.Fatalf("NewRepository() error = %v", err)
  3706  	}
  3707  	repo.PlainHTTP = true
  3708  	store = repo.Manifests()
  3709  	ctx = context.Background()
  3710  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3711  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3712  	}
  3713  	err = store.Delete(ctx, artifactDesc)
  3714  	if err != nil {
  3715  		t.Fatalf("Manifests.Delete() error = %v", err)
  3716  	}
  3717  	if !manifestDeleted {
  3718  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3719  	}
  3720  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3721  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3722  	}
  3723  
  3724  	// test inconsistent state: current referrer is not in referrers list
  3725  	manifestDeleted = true
  3726  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3727  		switch {
  3728  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3729  			manifestDeleted = true
  3730  			// no "Docker-Content-Digest" header for manifest deletion
  3731  			w.WriteHeader(http.StatusAccepted)
  3732  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String():
  3733  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, artifactDesc.MediaType) {
  3734  				t.Errorf("manifest not convertable: %s", accept)
  3735  				w.WriteHeader(http.StatusBadRequest)
  3736  				return
  3737  			}
  3738  			w.Header().Set("Content-Type", artifactDesc.MediaType)
  3739  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  3740  			if _, err := w.Write(artifactJSON); err != nil {
  3741  				t.Errorf("failed to write %q: %v", r.URL, err)
  3742  			}
  3743  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  3744  			w.WriteHeader(http.StatusNotFound)
  3745  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  3746  			result := ocispec.Index{
  3747  				Versioned: specs.Versioned{
  3748  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  3749  				},
  3750  				MediaType: ocispec.MediaTypeImageIndex,
  3751  				Manifests: []ocispec.Descriptor{
  3752  					content.NewDescriptorFromBytes(ocispec.MediaTypeArtifactManifest, []byte("whaterver")),
  3753  				},
  3754  			}
  3755  			if err := json.NewEncoder(w).Encode(result); err != nil {
  3756  				t.Errorf("failed to write response: %v", err)
  3757  			}
  3758  		default:
  3759  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3760  			w.WriteHeader(http.StatusNotFound)
  3761  		}
  3762  	}))
  3763  	defer ts.Close()
  3764  	uri, err = url.Parse(ts.URL)
  3765  	if err != nil {
  3766  		t.Fatalf("invalid test http server: %v", err)
  3767  	}
  3768  	repo, err = NewRepository(uri.Host + "/test")
  3769  	if err != nil {
  3770  		t.Fatalf("NewRepository() error = %v", err)
  3771  	}
  3772  	repo.PlainHTTP = true
  3773  	store = repo.Manifests()
  3774  	ctx = context.Background()
  3775  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  3776  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  3777  	}
  3778  	err = store.Delete(ctx, artifactDesc)
  3779  	if err != nil {
  3780  		t.Fatalf("Manifests.Delete() error = %v", err)
  3781  	}
  3782  	if !manifestDeleted {
  3783  		t.Errorf("Manifests.Delete() = %v, want %v", manifestDeleted, true)
  3784  	}
  3785  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  3786  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  3787  	}
  3788  }
  3789  
  3790  func Test_ManifestStore_Resolve(t *testing.T) {
  3791  	manifest := []byte(`{"layers":[]}`)
  3792  	manifestDesc := ocispec.Descriptor{
  3793  		MediaType: ocispec.MediaTypeImageIndex,
  3794  		Digest:    digest.FromBytes(manifest),
  3795  		Size:      int64(len(manifest)),
  3796  	}
  3797  	ref := "foobar"
  3798  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3799  		if r.Method != http.MethodHead {
  3800  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3801  			w.WriteHeader(http.StatusMethodNotAllowed)
  3802  			return
  3803  		}
  3804  		switch r.URL.Path {
  3805  		case "/v2/test/manifests/" + manifestDesc.Digest.String(),
  3806  			"/v2/test/manifests/" + ref:
  3807  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  3808  				t.Errorf("manifest not convertable: %s", accept)
  3809  				w.WriteHeader(http.StatusBadRequest)
  3810  				return
  3811  			}
  3812  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  3813  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3814  			w.Header().Set("Content-Length", strconv.Itoa(int(manifestDesc.Size)))
  3815  		default:
  3816  			w.WriteHeader(http.StatusNotFound)
  3817  		}
  3818  	}))
  3819  	defer ts.Close()
  3820  	uri, err := url.Parse(ts.URL)
  3821  	if err != nil {
  3822  		t.Fatalf("invalid test http server: %v", err)
  3823  	}
  3824  
  3825  	repoName := uri.Host + "/test"
  3826  	repo, err := NewRepository(repoName)
  3827  	if err != nil {
  3828  		t.Fatalf("NewRepository() error = %v", err)
  3829  	}
  3830  	repo.PlainHTTP = true
  3831  	store := repo.Manifests()
  3832  	ctx := context.Background()
  3833  
  3834  	got, err := store.Resolve(ctx, manifestDesc.Digest.String())
  3835  	if err != nil {
  3836  		t.Fatalf("Manifests.Resolve() error = %v", err)
  3837  	}
  3838  	if !reflect.DeepEqual(got, manifestDesc) {
  3839  		t.Errorf("Manifests.Resolve() = %v, want %v", got, manifestDesc)
  3840  	}
  3841  
  3842  	got, err = store.Resolve(ctx, ref)
  3843  	if err != nil {
  3844  		t.Fatalf("Manifests.Resolve() error = %v", err)
  3845  	}
  3846  	if !reflect.DeepEqual(got, manifestDesc) {
  3847  		t.Errorf("Manifests.Resolve() = %v, want %v", got, manifestDesc)
  3848  	}
  3849  
  3850  	tagDigestRef := "whatever" + "@" + manifestDesc.Digest.String()
  3851  	got, err = repo.Resolve(ctx, tagDigestRef)
  3852  	if err != nil {
  3853  		t.Fatalf("Manifests.Resolve() error = %v", err)
  3854  	}
  3855  	if !reflect.DeepEqual(got, manifestDesc) {
  3856  		t.Errorf("Manifests.Resolve() = %v, want %v", got, manifestDesc)
  3857  	}
  3858  
  3859  	fqdnRef := repoName + ":" + tagDigestRef
  3860  	got, err = repo.Resolve(ctx, fqdnRef)
  3861  	if err != nil {
  3862  		t.Fatalf("Manifests.Resolve() error = %v", err)
  3863  	}
  3864  	if !reflect.DeepEqual(got, manifestDesc) {
  3865  		t.Errorf("Manifests.Resolve() = %v, want %v", got, manifestDesc)
  3866  	}
  3867  
  3868  	content := []byte(`{"manifests":[]}`)
  3869  	contentDesc := ocispec.Descriptor{
  3870  		MediaType: ocispec.MediaTypeImageIndex,
  3871  		Digest:    digest.FromBytes(content),
  3872  		Size:      int64(len(content)),
  3873  	}
  3874  	_, err = store.Resolve(ctx, contentDesc.Digest.String())
  3875  	if !errors.Is(err, errdef.ErrNotFound) {
  3876  		t.Errorf("Manifests.Resolve() error = %v, wantErr %v", err, errdef.ErrNotFound)
  3877  	}
  3878  }
  3879  
  3880  func Test_ManifestStore_FetchReference(t *testing.T) {
  3881  	manifest := []byte(`{"layers":[]}`)
  3882  	manifestDesc := ocispec.Descriptor{
  3883  		MediaType: ocispec.MediaTypeImageIndex,
  3884  		Digest:    digest.FromBytes(manifest),
  3885  		Size:      int64(len(manifest)),
  3886  	}
  3887  	ref := "foobar"
  3888  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3889  		if r.Method != http.MethodGet {
  3890  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  3891  			w.WriteHeader(http.StatusMethodNotAllowed)
  3892  			return
  3893  		}
  3894  		switch r.URL.Path {
  3895  		case "/v2/test/manifests/" + manifestDesc.Digest.String(),
  3896  			"/v2/test/manifests/" + ref:
  3897  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, manifestDesc.MediaType) {
  3898  				t.Errorf("manifest not convertable: %s", accept)
  3899  				w.WriteHeader(http.StatusBadRequest)
  3900  				return
  3901  			}
  3902  			w.Header().Set("Content-Type", manifestDesc.MediaType)
  3903  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  3904  			if _, err := w.Write(manifest); err != nil {
  3905  				t.Errorf("failed to write %q: %v", r.URL, err)
  3906  			}
  3907  		default:
  3908  			w.WriteHeader(http.StatusNotFound)
  3909  		}
  3910  	}))
  3911  	defer ts.Close()
  3912  	uri, err := url.Parse(ts.URL)
  3913  	if err != nil {
  3914  		t.Fatalf("invalid test http server: %v", err)
  3915  	}
  3916  
  3917  	repoName := uri.Host + "/test"
  3918  	repo, err := NewRepository(repoName)
  3919  	if err != nil {
  3920  		t.Fatalf("NewRepository() error = %v", err)
  3921  	}
  3922  	repo.PlainHTTP = true
  3923  	store := repo.Manifests()
  3924  	ctx := context.Background()
  3925  
  3926  	// test with tag
  3927  	gotDesc, rc, err := store.FetchReference(ctx, ref)
  3928  	if err != nil {
  3929  		t.Fatalf("Manifests.FetchReference() error = %v", err)
  3930  	}
  3931  	if !reflect.DeepEqual(gotDesc, manifestDesc) {
  3932  		t.Errorf("Manifests.FetchReference() = %v, want %v", gotDesc, manifestDesc)
  3933  	}
  3934  	buf := bytes.NewBuffer(nil)
  3935  	if _, err := buf.ReadFrom(rc); err != nil {
  3936  		t.Errorf("fail to read: %v", err)
  3937  	}
  3938  	if err := rc.Close(); err != nil {
  3939  		t.Errorf("fail to close: %v", err)
  3940  	}
  3941  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  3942  		t.Errorf("Manifests.FetchReference() = %v, want %v", got, manifest)
  3943  	}
  3944  
  3945  	// test with other tag
  3946  	randomRef := "whatever"
  3947  	_, _, err = store.FetchReference(ctx, randomRef)
  3948  	if !errors.Is(err, errdef.ErrNotFound) {
  3949  		t.Errorf("Manifests.FetchReference() error = %v, wantErr %v", err, errdef.ErrNotFound)
  3950  	}
  3951  
  3952  	// test with digest
  3953  	gotDesc, rc, err = store.FetchReference(ctx, manifestDesc.Digest.String())
  3954  	if err != nil {
  3955  		t.Fatalf("Manifests.FetchReference() error = %v", err)
  3956  	}
  3957  	if !reflect.DeepEqual(gotDesc, manifestDesc) {
  3958  		t.Errorf("Manifests.FetchReference() = %v, want %v", gotDesc, manifestDesc)
  3959  	}
  3960  	buf.Reset()
  3961  	if _, err := buf.ReadFrom(rc); err != nil {
  3962  		t.Errorf("fail to read: %v", err)
  3963  	}
  3964  	if err := rc.Close(); err != nil {
  3965  		t.Errorf("fail to close: %v", err)
  3966  	}
  3967  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  3968  		t.Errorf("Manifests.FetchReference() = %v, want %v", got, manifest)
  3969  	}
  3970  
  3971  	// test with other digest
  3972  	randomContent := []byte("whatever")
  3973  	randomContentDigest := digest.FromBytes(randomContent)
  3974  	_, _, err = store.FetchReference(ctx, randomContentDigest.String())
  3975  	if !errors.Is(err, errdef.ErrNotFound) {
  3976  		t.Errorf("Manifests.FetchReference() error = %v, wantErr %v", err, errdef.ErrNotFound)
  3977  	}
  3978  
  3979  	// test with tag@digest
  3980  	tagDigestRef := randomRef + "@" + manifestDesc.Digest.String()
  3981  	gotDesc, rc, err = store.FetchReference(ctx, tagDigestRef)
  3982  	if err != nil {
  3983  		t.Fatalf("Manifests.FetchReference() error = %v", err)
  3984  	}
  3985  	if !reflect.DeepEqual(gotDesc, manifestDesc) {
  3986  		t.Errorf("Manifests.FetchReference() = %v, want %v", gotDesc, manifestDesc)
  3987  	}
  3988  	buf.Reset()
  3989  	if _, err := buf.ReadFrom(rc); err != nil {
  3990  		t.Errorf("fail to read: %v", err)
  3991  	}
  3992  	if err := rc.Close(); err != nil {
  3993  		t.Errorf("fail to close: %v", err)
  3994  	}
  3995  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  3996  		t.Errorf("Manifests.FetchReference() = %v, want %v", got, manifest)
  3997  	}
  3998  
  3999  	// test with FQDN
  4000  	fqdnRef := repoName + ":" + tagDigestRef
  4001  	gotDesc, rc, err = store.FetchReference(ctx, fqdnRef)
  4002  	if err != nil {
  4003  		t.Fatalf("Manifests.FetchReference() error = %v", err)
  4004  	}
  4005  	if !reflect.DeepEqual(gotDesc, manifestDesc) {
  4006  		t.Errorf("Manifests.FetchReference() = %v, want %v", gotDesc, manifestDesc)
  4007  	}
  4008  	buf.Reset()
  4009  	if _, err := buf.ReadFrom(rc); err != nil {
  4010  		t.Errorf("fail to read: %v", err)
  4011  	}
  4012  	if err := rc.Close(); err != nil {
  4013  		t.Errorf("fail to close: %v", err)
  4014  	}
  4015  	if got := buf.Bytes(); !bytes.Equal(got, manifest) {
  4016  		t.Errorf("Manifests.FetchReference() = %v, want %v", got, manifest)
  4017  	}
  4018  }
  4019  
  4020  func Test_ManifestStore_Tag(t *testing.T) {
  4021  	blob := []byte("hello world")
  4022  	blobDesc := ocispec.Descriptor{
  4023  		MediaType: "test",
  4024  		Digest:    digest.FromBytes(blob),
  4025  		Size:      int64(len(blob)),
  4026  	}
  4027  	index := []byte(`{"manifests":[]}`)
  4028  	indexDesc := ocispec.Descriptor{
  4029  		MediaType: ocispec.MediaTypeImageIndex,
  4030  		Digest:    digest.FromBytes(index),
  4031  		Size:      int64(len(index)),
  4032  	}
  4033  	var gotIndex []byte
  4034  	ref := "foobar"
  4035  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4036  		switch {
  4037  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+blobDesc.Digest.String():
  4038  			w.WriteHeader(http.StatusNotFound)
  4039  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
  4040  			if accept := r.Header.Get("Accept"); !strings.Contains(accept, indexDesc.MediaType) {
  4041  				t.Errorf("manifest not convertable: %s", accept)
  4042  				w.WriteHeader(http.StatusBadRequest)
  4043  				return
  4044  			}
  4045  			w.Header().Set("Content-Type", indexDesc.MediaType)
  4046  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  4047  			if _, err := w.Write(index); err != nil {
  4048  				t.Errorf("failed to write %q: %v", r.URL, err)
  4049  			}
  4050  		case r.Method == http.MethodPut &&
  4051  			r.URL.Path == "/v2/test/manifests/"+ref || r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String():
  4052  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
  4053  				w.WriteHeader(http.StatusBadRequest)
  4054  				break
  4055  			}
  4056  			buf := bytes.NewBuffer(nil)
  4057  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4058  				t.Errorf("fail to read: %v", err)
  4059  			}
  4060  			gotIndex = buf.Bytes()
  4061  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  4062  			w.WriteHeader(http.StatusCreated)
  4063  			return
  4064  		default:
  4065  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4066  			w.WriteHeader(http.StatusForbidden)
  4067  		}
  4068  	}))
  4069  	defer ts.Close()
  4070  	uri, err := url.Parse(ts.URL)
  4071  	if err != nil {
  4072  		t.Fatalf("invalid test http server: %v", err)
  4073  	}
  4074  
  4075  	repo, err := NewRepository(uri.Host + "/test")
  4076  	if err != nil {
  4077  		t.Fatalf("NewRepository() error = %v", err)
  4078  	}
  4079  	store := repo.Manifests()
  4080  	repo.PlainHTTP = true
  4081  	ctx := context.Background()
  4082  
  4083  	err = store.Tag(ctx, blobDesc, ref)
  4084  	if err == nil {
  4085  		t.Errorf("Repository.Tag() error = %v, wantErr %v", err, true)
  4086  	}
  4087  
  4088  	err = store.Tag(ctx, indexDesc, ref)
  4089  	if err != nil {
  4090  		t.Fatalf("Repository.Tag() error = %v", err)
  4091  	}
  4092  	if !bytes.Equal(gotIndex, index) {
  4093  		t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index)
  4094  	}
  4095  
  4096  	gotIndex = nil
  4097  	err = store.Tag(ctx, indexDesc, indexDesc.Digest.String())
  4098  	if err != nil {
  4099  		t.Fatalf("Repository.Tag() error = %v", err)
  4100  	}
  4101  	if !bytes.Equal(gotIndex, index) {
  4102  		t.Errorf("Repository.Tag() = %v, want %v", gotIndex, index)
  4103  	}
  4104  }
  4105  
  4106  func Test_ManifestStore_PushReference(t *testing.T) {
  4107  	index := []byte(`{"manifests":[]}`)
  4108  	indexDesc := ocispec.Descriptor{
  4109  		MediaType: ocispec.MediaTypeImageIndex,
  4110  		Digest:    digest.FromBytes(index),
  4111  		Size:      int64(len(index)),
  4112  	}
  4113  	var gotIndex []byte
  4114  	ref := "foobar"
  4115  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4116  		switch {
  4117  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+ref:
  4118  			if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType {
  4119  				w.WriteHeader(http.StatusBadRequest)
  4120  				break
  4121  			}
  4122  			buf := bytes.NewBuffer(nil)
  4123  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4124  				t.Errorf("fail to read: %v", err)
  4125  			}
  4126  			gotIndex = buf.Bytes()
  4127  			w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String())
  4128  			w.WriteHeader(http.StatusCreated)
  4129  			return
  4130  		default:
  4131  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4132  			w.WriteHeader(http.StatusForbidden)
  4133  		}
  4134  	}))
  4135  	defer ts.Close()
  4136  	uri, err := url.Parse(ts.URL)
  4137  	if err != nil {
  4138  		t.Fatalf("invalid test http server: %v", err)
  4139  	}
  4140  
  4141  	repo, err := NewRepository(uri.Host + "/test")
  4142  	if err != nil {
  4143  		t.Fatalf("NewRepository() error = %v", err)
  4144  	}
  4145  	store := repo.Manifests()
  4146  	repo.PlainHTTP = true
  4147  	ctx := context.Background()
  4148  	err = store.PushReference(ctx, indexDesc, bytes.NewReader(index), ref)
  4149  	if err != nil {
  4150  		t.Fatalf("Repository.PushReference() error = %v", err)
  4151  	}
  4152  	if !bytes.Equal(gotIndex, index) {
  4153  		t.Errorf("Repository.PushReference() = %v, want %v", gotIndex, index)
  4154  	}
  4155  }
  4156  
  4157  func Test_ManifestStore_PushReference_ReferrersAPIAvailable(t *testing.T) {
  4158  	// generate test content
  4159  	subject := []byte(`{"layers":[]}`)
  4160  	subjectDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeArtifactManifest, subject)
  4161  	artifact := ocispec.Artifact{
  4162  		MediaType: ocispec.MediaTypeArtifactManifest,
  4163  		Subject:   &subjectDesc,
  4164  	}
  4165  	artifactJSON, err := json.Marshal(artifact)
  4166  	if err != nil {
  4167  		t.Errorf("failed to marshal manifest: %v", err)
  4168  	}
  4169  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  4170  	artifactRef := "foo"
  4171  
  4172  	manifest := ocispec.Manifest{
  4173  		MediaType: ocispec.MediaTypeImageManifest,
  4174  		Subject:   &subjectDesc,
  4175  	}
  4176  	manifestJSON, err := json.Marshal(manifest)
  4177  	if err != nil {
  4178  		t.Errorf("failed to marshal manifest: %v", err)
  4179  	}
  4180  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  4181  	manifestRef := "bar"
  4182  
  4183  	var gotManifest []byte
  4184  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4185  		switch {
  4186  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactRef:
  4187  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  4188  				w.WriteHeader(http.StatusBadRequest)
  4189  				break
  4190  			}
  4191  			buf := bytes.NewBuffer(nil)
  4192  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4193  				t.Errorf("fail to read: %v", err)
  4194  			}
  4195  			gotManifest = buf.Bytes()
  4196  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  4197  			w.WriteHeader(http.StatusCreated)
  4198  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestRef:
  4199  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  4200  				w.WriteHeader(http.StatusBadRequest)
  4201  				break
  4202  			}
  4203  			buf := bytes.NewBuffer(nil)
  4204  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4205  				t.Errorf("fail to read: %v", err)
  4206  			}
  4207  			gotManifest = buf.Bytes()
  4208  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  4209  			w.WriteHeader(http.StatusCreated)
  4210  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4211  			result := ocispec.Index{
  4212  				Versioned: specs.Versioned{
  4213  					SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4214  				},
  4215  				MediaType: ocispec.MediaTypeImageIndex,
  4216  				Manifests: []ocispec.Descriptor{},
  4217  			}
  4218  			if err := json.NewEncoder(w).Encode(result); err != nil {
  4219  				t.Errorf("failed to write response: %v", err)
  4220  			}
  4221  		default:
  4222  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4223  			w.WriteHeader(http.StatusNotFound)
  4224  		}
  4225  	}))
  4226  	defer ts.Close()
  4227  	uri, err := url.Parse(ts.URL)
  4228  	if err != nil {
  4229  		t.Fatalf("invalid test http server: %v", err)
  4230  	}
  4231  
  4232  	ctx := context.Background()
  4233  	repo, err := NewRepository(uri.Host + "/test")
  4234  	if err != nil {
  4235  		t.Fatalf("NewRepository() error = %v", err)
  4236  	}
  4237  	repo.PlainHTTP = true
  4238  
  4239  	// test push artifact with subject
  4240  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4241  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4242  	}
  4243  	err = repo.PushReference(ctx, artifactDesc, bytes.NewReader(artifactJSON), artifactRef)
  4244  	if err != nil {
  4245  		t.Fatalf("Manifests.Push() error = %v", err)
  4246  	}
  4247  	if !bytes.Equal(gotManifest, artifactJSON) {
  4248  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  4249  	}
  4250  
  4251  	// test push image manifest with subject
  4252  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  4253  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  4254  	}
  4255  	err = repo.PushReference(ctx, manifestDesc, bytes.NewReader(manifestJSON), manifestRef)
  4256  	if err != nil {
  4257  		t.Fatalf("Manifests.Push() error = %v", err)
  4258  	}
  4259  	if !bytes.Equal(gotManifest, manifestJSON) {
  4260  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  4261  	}
  4262  }
  4263  
  4264  func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) {
  4265  	// generate test content
  4266  	subject := []byte(`{"layers":[]}`)
  4267  	subjectDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeArtifactManifest, subject)
  4268  	referrersTag := strings.Replace(subjectDesc.Digest.String(), ":", "-", 1)
  4269  	artifact := ocispec.Artifact{
  4270  		MediaType:    ocispec.MediaTypeArtifactManifest,
  4271  		Subject:      &subjectDesc,
  4272  		ArtifactType: "application/vnd.test",
  4273  		Annotations:  map[string]string{"foo": "bar"},
  4274  	}
  4275  	artifactJSON, err := json.Marshal(artifact)
  4276  	if err != nil {
  4277  		t.Errorf("failed to marshal manifest: %v", err)
  4278  	}
  4279  	artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON)
  4280  	artifactDesc.ArtifactType = artifact.ArtifactType
  4281  	artifactDesc.Annotations = artifact.Annotations
  4282  	artifactRef := "foo"
  4283  
  4284  	// test push artifact with subject
  4285  	index_1 := ocispec.Index{
  4286  		Versioned: specs.Versioned{
  4287  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4288  		},
  4289  		MediaType: ocispec.MediaTypeImageIndex,
  4290  		Manifests: []ocispec.Descriptor{
  4291  			artifactDesc,
  4292  		},
  4293  	}
  4294  	indexJSON_1, err := json.Marshal(index_1)
  4295  	if err != nil {
  4296  		t.Errorf("failed to marshal manifest: %v", err)
  4297  	}
  4298  	indexDesc_1 := content.NewDescriptorFromBytes(index_1.MediaType, indexJSON_1)
  4299  	var gotManifest []byte
  4300  	var gotReferrerIndex []byte
  4301  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4302  		switch {
  4303  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactRef:
  4304  			if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType {
  4305  				w.WriteHeader(http.StatusBadRequest)
  4306  				break
  4307  			}
  4308  			buf := bytes.NewBuffer(nil)
  4309  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4310  				t.Errorf("fail to read: %v", err)
  4311  			}
  4312  			gotManifest = buf.Bytes()
  4313  			w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String())
  4314  			w.WriteHeader(http.StatusCreated)
  4315  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4316  			w.WriteHeader(http.StatusNotFound)
  4317  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4318  			w.WriteHeader(http.StatusNotFound)
  4319  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4320  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  4321  				w.WriteHeader(http.StatusBadRequest)
  4322  				break
  4323  			}
  4324  			buf := bytes.NewBuffer(nil)
  4325  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4326  				t.Errorf("fail to read: %v", err)
  4327  			}
  4328  			gotReferrerIndex = buf.Bytes()
  4329  			w.Header().Set("Docker-Content-Digest", indexDesc_1.Digest.String())
  4330  			w.WriteHeader(http.StatusCreated)
  4331  		default:
  4332  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4333  			w.WriteHeader(http.StatusNotFound)
  4334  		}
  4335  	}))
  4336  	defer ts.Close()
  4337  	uri, err := url.Parse(ts.URL)
  4338  	if err != nil {
  4339  		t.Fatalf("invalid test http server: %v", err)
  4340  	}
  4341  
  4342  	ctx := context.Background()
  4343  	repo, err := NewRepository(uri.Host + "/test")
  4344  	if err != nil {
  4345  		t.Fatalf("NewRepository() error = %v", err)
  4346  	}
  4347  	repo.PlainHTTP = true
  4348  
  4349  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4350  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4351  	}
  4352  	err = repo.PushReference(ctx, artifactDesc, bytes.NewReader(artifactJSON), artifactRef)
  4353  	if err != nil {
  4354  		t.Fatalf("Manifests.Push() error = %v", err)
  4355  	}
  4356  	if !bytes.Equal(gotManifest, artifactJSON) {
  4357  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON))
  4358  	}
  4359  	if !bytes.Equal(gotReferrerIndex, indexJSON_1) {
  4360  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_1))
  4361  	}
  4362  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4363  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4364  	}
  4365  
  4366  	// test push image manifest with subject, referrers list should be updated
  4367  	manifest := ocispec.Manifest{
  4368  		MediaType: ocispec.MediaTypeImageManifest,
  4369  		Config: ocispec.Descriptor{
  4370  			MediaType: "testconfig",
  4371  		},
  4372  		Subject:     &subjectDesc,
  4373  		Annotations: map[string]string{"foo": "bar"},
  4374  	}
  4375  	manifestJSON, err := json.Marshal(manifest)
  4376  	if err != nil {
  4377  		t.Errorf("failed to marshal manifest: %v", err)
  4378  	}
  4379  	manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON)
  4380  	manifestDesc.ArtifactType = manifest.Config.MediaType
  4381  	manifestDesc.Annotations = manifest.Annotations
  4382  	manifestRef := "bar"
  4383  
  4384  	index_2 := ocispec.Index{
  4385  		Versioned: specs.Versioned{
  4386  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  4387  		},
  4388  		MediaType: ocispec.MediaTypeImageIndex,
  4389  		Manifests: []ocispec.Descriptor{
  4390  			artifactDesc,
  4391  			manifestDesc,
  4392  		},
  4393  	}
  4394  	indexJSON_2, err := json.Marshal(index_2)
  4395  	if err != nil {
  4396  		t.Errorf("failed to marshal manifest: %v", err)
  4397  	}
  4398  	indexDesc_2 := content.NewDescriptorFromBytes(index_2.MediaType, indexJSON_2)
  4399  	var manifestDeleted bool
  4400  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4401  		switch {
  4402  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestRef:
  4403  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  4404  				w.WriteHeader(http.StatusBadRequest)
  4405  				break
  4406  			}
  4407  			buf := bytes.NewBuffer(nil)
  4408  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4409  				t.Errorf("fail to read: %v", err)
  4410  			}
  4411  			gotManifest = buf.Bytes()
  4412  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  4413  			w.WriteHeader(http.StatusCreated)
  4414  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4415  			w.WriteHeader(http.StatusNotFound)
  4416  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4417  			w.Write(indexJSON_1)
  4418  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4419  			if contentType := r.Header.Get("Content-Type"); contentType != ocispec.MediaTypeImageIndex {
  4420  				w.WriteHeader(http.StatusBadRequest)
  4421  				break
  4422  			}
  4423  			buf := bytes.NewBuffer(nil)
  4424  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4425  				t.Errorf("fail to read: %v", err)
  4426  			}
  4427  			gotReferrerIndex = buf.Bytes()
  4428  			w.Header().Set("Docker-Content-Digest", indexDesc_2.Digest.String())
  4429  			w.WriteHeader(http.StatusCreated)
  4430  		case r.Method == http.MethodDelete && r.URL.Path == "/v2/test/manifests/"+indexDesc_1.Digest.String():
  4431  			manifestDeleted = true
  4432  			// no "Docker-Content-Digest" header for manifest deletion
  4433  			w.WriteHeader(http.StatusAccepted)
  4434  		default:
  4435  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4436  			w.WriteHeader(http.StatusNotFound)
  4437  		}
  4438  	}))
  4439  	defer ts.Close()
  4440  	uri, err = url.Parse(ts.URL)
  4441  	if err != nil {
  4442  		t.Fatalf("invalid test http server: %v", err)
  4443  	}
  4444  
  4445  	ctx = context.Background()
  4446  	repo, err = NewRepository(uri.Host + "/test")
  4447  	if err != nil {
  4448  		t.Fatalf("NewRepository() error = %v", err)
  4449  	}
  4450  	repo.PlainHTTP = true
  4451  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4452  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4453  	}
  4454  	err = repo.PushReference(ctx, manifestDesc, bytes.NewReader(manifestJSON), manifestRef)
  4455  	if err != nil {
  4456  		t.Fatalf("Manifests.PushReference() error = %v", err)
  4457  	}
  4458  	if !bytes.Equal(gotManifest, manifestJSON) {
  4459  		t.Errorf("Manifests.PushReference() = %v, want %v", string(gotManifest), string(manifestJSON))
  4460  	}
  4461  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  4462  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  4463  	}
  4464  	if !manifestDeleted {
  4465  		t.Errorf("manifestDeleted = %v, want %v", manifestDeleted, true)
  4466  	}
  4467  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4468  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4469  	}
  4470  
  4471  	// test push image manifest with subject again, referrers list should not be changed
  4472  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4473  		switch {
  4474  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String():
  4475  			if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType {
  4476  				w.WriteHeader(http.StatusBadRequest)
  4477  				break
  4478  			}
  4479  			buf := bytes.NewBuffer(nil)
  4480  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4481  				t.Errorf("fail to read: %v", err)
  4482  			}
  4483  			gotManifest = buf.Bytes()
  4484  			w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String())
  4485  			w.WriteHeader(http.StatusCreated)
  4486  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  4487  			w.WriteHeader(http.StatusNotFound)
  4488  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag:
  4489  			w.Write(indexJSON_2)
  4490  		default:
  4491  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4492  			w.WriteHeader(http.StatusNotFound)
  4493  		}
  4494  	}))
  4495  	defer ts.Close()
  4496  	uri, err = url.Parse(ts.URL)
  4497  	if err != nil {
  4498  		t.Fatalf("invalid test http server: %v", err)
  4499  	}
  4500  
  4501  	ctx = context.Background()
  4502  	repo, err = NewRepository(uri.Host + "/test")
  4503  	if err != nil {
  4504  		t.Fatalf("NewRepository() error = %v", err)
  4505  	}
  4506  	repo.PlainHTTP = true
  4507  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  4508  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  4509  	}
  4510  	err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON))
  4511  	if err != nil {
  4512  		t.Fatalf("Manifests.Push() error = %v", err)
  4513  	}
  4514  	if !bytes.Equal(gotManifest, manifestJSON) {
  4515  		t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON))
  4516  	}
  4517  	// referrers list should not be changed
  4518  	if !bytes.Equal(gotReferrerIndex, indexJSON_2) {
  4519  		t.Errorf("got referrers index = %v, want %v", string(gotReferrerIndex), string(indexJSON_2))
  4520  	}
  4521  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  4522  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  4523  	}
  4524  }
  4525  
  4526  func Test_ManifestStore_generateDescriptorWithVariousDockerContentDigestHeaders(t *testing.T) {
  4527  	reference := registry.Reference{
  4528  		Registry:   "eastern.haan.com",
  4529  		Reference:  "<calculate>",
  4530  		Repository: "from25to220ce",
  4531  	}
  4532  
  4533  	tests := getTestIOStructMapForGetDescriptorClass()
  4534  	for testName, dcdIOStruct := range tests {
  4535  		repo, err := NewRepository(fmt.Sprintf("%s/%s", reference.Repository, reference.Repository))
  4536  		if err != nil {
  4537  			t.Fatalf("failed to initialize repository")
  4538  		}
  4539  
  4540  		s := manifestStore{repo: repo}
  4541  
  4542  		for i, method := range []string{http.MethodGet, http.MethodHead} {
  4543  			reference.Reference = dcdIOStruct.clientSuppliedReference
  4544  
  4545  			resp := http.Response{
  4546  				Header: http.Header{
  4547  					"Content-Type":            []string{"application/vnd.docker.distribution.manifest.v2+json"},
  4548  					dockerContentDigestHeader: []string{dcdIOStruct.serverCalculatedDigest.String()},
  4549  				},
  4550  			}
  4551  			if method == http.MethodGet {
  4552  				resp.Body = io.NopCloser(bytes.NewBufferString(theAmazingBanClan))
  4553  			}
  4554  			resp.Request = &http.Request{
  4555  				Method: method,
  4556  			}
  4557  
  4558  			errExpected := []bool{dcdIOStruct.errExpectedOnGET, dcdIOStruct.errExpectedOnHEAD}[i]
  4559  			_, err = s.generateDescriptor(&resp, reference, method)
  4560  			if !errExpected && err != nil {
  4561  				t.Errorf(
  4562  					"[Manifest.%v] %v; expected no error for request, but got err: %v",
  4563  					method, testName, err,
  4564  				)
  4565  			} else if errExpected && err == nil {
  4566  				t.Errorf(
  4567  					"[Manifest.%v] %v; expected an error for request, but got none",
  4568  					method, testName,
  4569  				)
  4570  			}
  4571  		}
  4572  	}
  4573  }
  4574  
  4575  type testTransport struct {
  4576  	proxyHost           string
  4577  	underlyingTransport http.RoundTripper
  4578  	mockHost            string
  4579  }
  4580  
  4581  func (t *testTransport) RoundTrip(originalReq *http.Request) (*http.Response, error) {
  4582  	req := originalReq.Clone(originalReq.Context())
  4583  	mockHostName, mockPort, err := net.SplitHostPort(t.mockHost)
  4584  	// when t.mockHost is as form host:port
  4585  	if err == nil && (req.URL.Hostname() != mockHostName || req.URL.Port() != mockPort) {
  4586  		return nil, errors.New("bad request")
  4587  	}
  4588  	// when t.mockHost does not have specified port, in this case,
  4589  	// err is not nil
  4590  	if err != nil && req.URL.Hostname() != t.mockHost {
  4591  		return nil, errors.New("bad request")
  4592  	}
  4593  	req.Host = t.proxyHost
  4594  	req.URL.Host = t.proxyHost
  4595  	resp, err := t.underlyingTransport.RoundTrip(req)
  4596  	if err != nil {
  4597  		return nil, err
  4598  	}
  4599  	resp.Request.Host = t.mockHost
  4600  	resp.Request.URL.Host = t.mockHost
  4601  	return resp, nil
  4602  }
  4603  
  4604  // Helper function to create a registry.BlobStore for
  4605  // Test_BlobStore_Push_Port443
  4606  func blobStore_Push_Port443_create_store(uri *url.URL, testRegistry string) (registry.BlobStore, error) {
  4607  	repo, err := NewRepository(testRegistry + "/test")
  4608  	repo.Client = &auth.Client{
  4609  		Client: &http.Client{
  4610  			Transport: &testTransport{
  4611  				proxyHost:           uri.Host,
  4612  				underlyingTransport: http.DefaultTransport,
  4613  				mockHost:            testRegistry,
  4614  			},
  4615  		},
  4616  		Cache: auth.NewCache(),
  4617  	}
  4618  	repo.PlainHTTP = true
  4619  	store := repo.Blobs()
  4620  	return store, err
  4621  }
  4622  
  4623  func Test_BlobStore_Push_Port443(t *testing.T) {
  4624  	blob := []byte("hello world")
  4625  	blobDesc := ocispec.Descriptor{
  4626  		MediaType: "test",
  4627  		Digest:    digest.FromBytes(blob),
  4628  		Size:      int64(len(blob)),
  4629  	}
  4630  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
  4631  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4632  		switch {
  4633  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
  4634  			w.Header().Set("Location", "http://registry.wabbit-networks.io/v2/test/blobs/uploads/"+uuid)
  4635  			w.WriteHeader(http.StatusAccepted)
  4636  			return
  4637  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
  4638  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
  4639  				w.WriteHeader(http.StatusBadRequest)
  4640  				break
  4641  			}
  4642  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
  4643  				w.WriteHeader(http.StatusBadRequest)
  4644  				break
  4645  			}
  4646  			buf := bytes.NewBuffer(nil)
  4647  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4648  				t.Errorf("fail to read: %v", err)
  4649  			}
  4650  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  4651  			w.WriteHeader(http.StatusCreated)
  4652  			return
  4653  		default:
  4654  			w.WriteHeader(http.StatusForbidden)
  4655  		}
  4656  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4657  	}))
  4658  	defer ts.Close()
  4659  	uri, err := url.Parse(ts.URL)
  4660  	if err != nil {
  4661  		t.Fatalf("invalid test http server: %v", err)
  4662  	}
  4663  
  4664  	// Test case with Host: "registry.wabbit-networks.io:443",
  4665  	// Location: "registry.wabbit-networks.io"
  4666  	testRegistry := "registry.wabbit-networks.io:443"
  4667  	store, err := blobStore_Push_Port443_create_store(uri, testRegistry)
  4668  	if err != nil {
  4669  		t.Fatalf("blobStore_Push_Port443_create_store() error = %v", err)
  4670  	}
  4671  	ctx := context.Background()
  4672  
  4673  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  4674  	if err != nil {
  4675  		t.Fatalf("Blobs.Push() error = %v", err)
  4676  	}
  4677  
  4678  	// Test case with Host: "registry.wabbit-networks.io",
  4679  	// Location: "registry.wabbit-networks.io"
  4680  	testRegistry = "registry.wabbit-networks.io"
  4681  	store, err = blobStore_Push_Port443_create_store(uri, testRegistry)
  4682  	if err != nil {
  4683  		t.Fatalf("blobStore_Push_Port443_create_store() error = %v", err)
  4684  	}
  4685  
  4686  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  4687  	if err != nil {
  4688  		t.Fatalf("Blobs.Push() error = %v", err)
  4689  	}
  4690  }
  4691  
  4692  // Helper function to create a registry.BlobStore for
  4693  // Test_BlobStore_Push_Port443_HTTPS
  4694  func blobStore_Push_Port443_HTTPS_create_store(uri *url.URL, testRegistry string) (registry.BlobStore, error) {
  4695  	repo, err := NewRepository(testRegistry + "/test")
  4696  	tlsConfig := &tls.Config{
  4697  		InsecureSkipVerify: true,
  4698  	}
  4699  	transport := &http.Transport{
  4700  		TLSClientConfig: tlsConfig,
  4701  	}
  4702  	repo.Client = &auth.Client{
  4703  		Client: &http.Client{
  4704  			Transport: &testTransport{
  4705  				proxyHost:           uri.Host,
  4706  				underlyingTransport: transport,
  4707  				mockHost:            testRegistry,
  4708  			},
  4709  		},
  4710  		Cache: auth.NewCache(),
  4711  	}
  4712  	repo.PlainHTTP = false
  4713  	store := repo.Blobs()
  4714  	return store, err
  4715  }
  4716  
  4717  func Test_BlobStore_Push_Port443_HTTPS(t *testing.T) {
  4718  	blob := []byte("hello world")
  4719  	blobDesc := ocispec.Descriptor{
  4720  		MediaType: "test",
  4721  		Digest:    digest.FromBytes(blob),
  4722  		Size:      int64(len(blob)),
  4723  	}
  4724  	uuid := "4fd53bc9-565d-4527-ab80-3e051ac4880c"
  4725  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4726  		switch {
  4727  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
  4728  			w.Header().Set("Location", "https://registry.wabbit-networks.io/v2/test/blobs/uploads/"+uuid)
  4729  			w.WriteHeader(http.StatusAccepted)
  4730  			return
  4731  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
  4732  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
  4733  				w.WriteHeader(http.StatusBadRequest)
  4734  				break
  4735  			}
  4736  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
  4737  				w.WriteHeader(http.StatusBadRequest)
  4738  				break
  4739  			}
  4740  			buf := bytes.NewBuffer(nil)
  4741  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4742  				t.Errorf("fail to read: %v", err)
  4743  			}
  4744  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  4745  			w.WriteHeader(http.StatusCreated)
  4746  			return
  4747  		default:
  4748  			w.WriteHeader(http.StatusForbidden)
  4749  		}
  4750  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4751  	}))
  4752  	defer ts.Close()
  4753  	uri, err := url.Parse(ts.URL)
  4754  	if err != nil {
  4755  		t.Fatalf("invalid test https server: %v", err)
  4756  	}
  4757  
  4758  	ctx := context.Background()
  4759  	// Test case with Host: "registry.wabbit-networks.io:443",
  4760  	// Location: "registry.wabbit-networks.io"
  4761  	testRegistry := "registry.wabbit-networks.io:443"
  4762  	store, err := blobStore_Push_Port443_HTTPS_create_store(uri, testRegistry)
  4763  	if err != nil {
  4764  		t.Fatalf("blobStore_Push_Port443_HTTPS_create_store() error = %v", err)
  4765  	}
  4766  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  4767  	if err != nil {
  4768  		t.Fatalf("Blobs.Push() error = %v", err)
  4769  	}
  4770  
  4771  	// Test case with Host: "registry.wabbit-networks.io",
  4772  	// Location: "registry.wabbit-networks.io"
  4773  	testRegistry = "registry.wabbit-networks.io"
  4774  	store, err = blobStore_Push_Port443_HTTPS_create_store(uri, testRegistry)
  4775  	if err != nil {
  4776  		t.Fatalf("blobStore_Push_Port443_HTTPS_create_store() error = %v", err)
  4777  	}
  4778  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  4779  	if err != nil {
  4780  		t.Fatalf("Blobs.Push() error = %v", err)
  4781  	}
  4782  
  4783  	ts = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4784  		switch {
  4785  		case r.Method == http.MethodPost && r.URL.Path == "/v2/test/blobs/uploads/":
  4786  			w.Header().Set("Location", "https://registry.wabbit-networks.io:443/v2/test/blobs/uploads/"+uuid)
  4787  			w.WriteHeader(http.StatusAccepted)
  4788  			return
  4789  		case r.Method == http.MethodPut && r.URL.Path == "/v2/test/blobs/uploads/"+uuid:
  4790  			if contentType := r.Header.Get("Content-Type"); contentType != "application/octet-stream" {
  4791  				w.WriteHeader(http.StatusBadRequest)
  4792  				break
  4793  			}
  4794  			if contentDigest := r.URL.Query().Get("digest"); contentDigest != blobDesc.Digest.String() {
  4795  				w.WriteHeader(http.StatusBadRequest)
  4796  				break
  4797  			}
  4798  			buf := bytes.NewBuffer(nil)
  4799  			if _, err := buf.ReadFrom(r.Body); err != nil {
  4800  				t.Errorf("fail to read: %v", err)
  4801  			}
  4802  			w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
  4803  			w.WriteHeader(http.StatusCreated)
  4804  			return
  4805  		default:
  4806  			w.WriteHeader(http.StatusForbidden)
  4807  		}
  4808  		t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4809  	}))
  4810  	defer ts.Close()
  4811  	uri, err = url.Parse(ts.URL)
  4812  	if err != nil {
  4813  		t.Fatalf("invalid test https server: %v", err)
  4814  	}
  4815  
  4816  	// Test case with Host: "registry.wabbit-networks.io:443",
  4817  	// Location: "registry.wabbit-networks.io:443"
  4818  	testRegistry = "registry.wabbit-networks.io:443"
  4819  	store, err = blobStore_Push_Port443_HTTPS_create_store(uri, testRegistry)
  4820  	if err != nil {
  4821  		t.Fatalf("blobStore_Push_Port443_HTTPS_create_store() error = %v", err)
  4822  	}
  4823  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  4824  	if err != nil {
  4825  		t.Fatalf("Blobs.Push() error = %v", err)
  4826  	}
  4827  
  4828  	// Test case with Host: "registry.wabbit-networks.io",
  4829  	// Location: "registry.wabbit-networks.io:443"
  4830  	testRegistry = "registry.wabbit-networks.io"
  4831  	store, err = blobStore_Push_Port443_HTTPS_create_store(uri, testRegistry)
  4832  	if err != nil {
  4833  		t.Fatalf("blobStore_Push_Port443_HTTPS_create_store() error = %v", err)
  4834  	}
  4835  	err = store.Push(ctx, blobDesc, bytes.NewReader(blob))
  4836  	if err != nil {
  4837  		t.Fatalf("Blobs.Push() error = %v", err)
  4838  	}
  4839  }
  4840  
  4841  // Testing `last` parameter for Tags list
  4842  func TestRepository_Tags_WithLastParam(t *testing.T) {
  4843  	tagSet := strings.Split("abcdefghijklmnopqrstuvwxyz", "")
  4844  	var offset int
  4845  	var ts *httptest.Server
  4846  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4847  		if r.Method != http.MethodGet || r.URL.Path != "/v2/test/tags/list" {
  4848  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  4849  			w.WriteHeader(http.StatusNotFound)
  4850  			return
  4851  		}
  4852  		q := r.URL.Query()
  4853  		n, err := strconv.Atoi(q.Get("n"))
  4854  		if err != nil || n != 4 {
  4855  			t.Errorf("bad page size: %s", q.Get("n"))
  4856  			w.WriteHeader(http.StatusBadRequest)
  4857  			return
  4858  		}
  4859  		last := q.Get("last")
  4860  		if last != "" {
  4861  			offset = indexOf(last, tagSet) + 1
  4862  		}
  4863  		var tags []string
  4864  		switch q.Get("test") {
  4865  		case "foo":
  4866  			tags = tagSet[offset : offset+n]
  4867  			offset += n
  4868  			w.Header().Set("Link", fmt.Sprintf(`<%s/v2/test/tags/list?n=4&last=v&test=bar>; rel="next"`, ts.URL))
  4869  		case "bar":
  4870  			tags = tagSet[offset : offset+n]
  4871  		default:
  4872  			tags = tagSet[offset : offset+n]
  4873  			offset += n
  4874  			w.Header().Set("Link", fmt.Sprintf(`<%s/v2/test/tags/list?n=4&last=r&test=foo>; rel="next"`, ts.URL))
  4875  		}
  4876  		result := struct {
  4877  			Tags []string `json:"tags"`
  4878  		}{
  4879  			Tags: tags,
  4880  		}
  4881  		if err := json.NewEncoder(w).Encode(result); err != nil {
  4882  			t.Errorf("failed to write response: %v", err)
  4883  		}
  4884  	}))
  4885  	defer ts.Close()
  4886  	uri, err := url.Parse(ts.URL)
  4887  	if err != nil {
  4888  		t.Fatalf("invalid test http server: %v", err)
  4889  	}
  4890  
  4891  	repo, err := NewRepository(uri.Host + "/test")
  4892  	if err != nil {
  4893  		t.Fatalf("NewRepository() error = %v", err)
  4894  	}
  4895  	repo.PlainHTTP = true
  4896  	repo.TagListPageSize = 4
  4897  	last := "n"
  4898  	startInd := indexOf(last, tagSet) + 1
  4899  
  4900  	ctx := context.Background()
  4901  	if err := repo.Tags(ctx, last, func(got []string) error {
  4902  		want := tagSet[startInd : startInd+repo.TagListPageSize]
  4903  		startInd += repo.TagListPageSize
  4904  		if !reflect.DeepEqual(got, want) {
  4905  			t.Errorf("Registry.Repositories() = %v, want %v", got, want)
  4906  		}
  4907  		return nil
  4908  	}); err != nil {
  4909  		t.Errorf("Repository.Tags() error = %v", err)
  4910  	}
  4911  }
  4912  
  4913  func TestRepository_ParseReference(t *testing.T) {
  4914  	type args struct {
  4915  		reference string
  4916  	}
  4917  	tests := []struct {
  4918  		name    string
  4919  		repoRef registry.Reference
  4920  		args    args
  4921  		want    registry.Reference
  4922  		wantErr error
  4923  	}{
  4924  		{
  4925  			name: "parse tag",
  4926  			repoRef: registry.Reference{
  4927  				Registry:   "registry.example.com",
  4928  				Repository: "hello-world",
  4929  			},
  4930  			args: args{
  4931  				reference: "foobar",
  4932  			},
  4933  			want: registry.Reference{
  4934  				Registry:   "registry.example.com",
  4935  				Repository: "hello-world",
  4936  				Reference:  "foobar",
  4937  			},
  4938  			wantErr: nil,
  4939  		},
  4940  		{
  4941  			name: "parse digest",
  4942  			repoRef: registry.Reference{
  4943  				Registry:   "registry.example.com",
  4944  				Repository: "hello-world",
  4945  			},
  4946  			args: args{
  4947  				reference: "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  4948  			},
  4949  			want: registry.Reference{
  4950  				Registry:   "registry.example.com",
  4951  				Repository: "hello-world",
  4952  				Reference:  "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  4953  			},
  4954  			wantErr: nil,
  4955  		},
  4956  		{
  4957  			name: "parse tag@digest",
  4958  			repoRef: registry.Reference{
  4959  				Registry:   "registry.example.com",
  4960  				Repository: "hello-world",
  4961  			},
  4962  			args: args{
  4963  				reference: "foobar@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  4964  			},
  4965  			want: registry.Reference{
  4966  				Registry:   "registry.example.com",
  4967  				Repository: "hello-world",
  4968  				Reference:  "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  4969  			},
  4970  			wantErr: nil,
  4971  		},
  4972  		{
  4973  			name: "parse FQDN tag",
  4974  			repoRef: registry.Reference{
  4975  				Registry:   "registry.example.com",
  4976  				Repository: "hello-world",
  4977  			},
  4978  			args: args{
  4979  				reference: "registry.example.com/hello-world:foobar",
  4980  			},
  4981  			want: registry.Reference{
  4982  				Registry:   "registry.example.com",
  4983  				Repository: "hello-world",
  4984  				Reference:  "foobar",
  4985  			},
  4986  			wantErr: nil,
  4987  		},
  4988  		{
  4989  			name: "parse FQDN digest",
  4990  			repoRef: registry.Reference{
  4991  				Registry:   "registry.example.com",
  4992  				Repository: "hello-world",
  4993  			},
  4994  			args: args{
  4995  				reference: "registry.example.com/hello-world@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  4996  			},
  4997  			want: registry.Reference{
  4998  				Registry:   "registry.example.com",
  4999  				Repository: "hello-world",
  5000  				Reference:  "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  5001  			},
  5002  			wantErr: nil,
  5003  		},
  5004  		{
  5005  			name: "parse FQDN tag@digest",
  5006  			repoRef: registry.Reference{
  5007  				Registry:   "registry.example.com",
  5008  				Repository: "hello-world",
  5009  			},
  5010  			args: args{
  5011  				reference: "registry.example.com/hello-world:foobar@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  5012  			},
  5013  			want: registry.Reference{
  5014  				Registry:   "registry.example.com",
  5015  				Repository: "hello-world",
  5016  				Reference:  "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  5017  			},
  5018  			wantErr: nil,
  5019  		},
  5020  		{
  5021  			name: "empty reference",
  5022  			repoRef: registry.Reference{
  5023  				Registry:   "registry.example.com",
  5024  				Repository: "hello-world",
  5025  			},
  5026  			args: args{
  5027  				reference: "",
  5028  			},
  5029  			want:    registry.Reference{},
  5030  			wantErr: errdef.ErrInvalidReference,
  5031  		},
  5032  		{
  5033  			name: "missing repository",
  5034  			repoRef: registry.Reference{
  5035  				Registry:   "registry.example.com",
  5036  				Repository: "hello-world",
  5037  			},
  5038  			args: args{
  5039  				reference: "myregistry.example.com:hello-world",
  5040  			},
  5041  			want:    registry.Reference{},
  5042  			wantErr: errdef.ErrInvalidReference,
  5043  		},
  5044  		{
  5045  			name: "missing reference",
  5046  			repoRef: registry.Reference{
  5047  				Registry:   "registry.example.com",
  5048  				Repository: "hello-world",
  5049  			},
  5050  			args: args{
  5051  				reference: "registry.example.com/hello-world",
  5052  			},
  5053  			want:    registry.Reference{},
  5054  			wantErr: errdef.ErrInvalidReference,
  5055  		},
  5056  		{
  5057  			name: "registry mismatch",
  5058  			repoRef: registry.Reference{
  5059  				Registry:   "registry.example.com",
  5060  				Repository: "hello-world",
  5061  			},
  5062  			args: args{
  5063  				reference: "myregistry.example.com/hello-world:foobar@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  5064  			},
  5065  			want:    registry.Reference{},
  5066  			wantErr: errdef.ErrInvalidReference,
  5067  		},
  5068  		{
  5069  			name: "repository mismatch",
  5070  			repoRef: registry.Reference{
  5071  				Registry:   "registry.example.com",
  5072  				Repository: "hello-world",
  5073  			},
  5074  			args: args{
  5075  				reference: "registry.example.com/goodbye-world:foobar@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  5076  			},
  5077  			want:    registry.Reference{},
  5078  			wantErr: errdef.ErrInvalidReference,
  5079  		},
  5080  		{
  5081  			name: "digest posing as a tag",
  5082  			repoRef: registry.Reference{
  5083  				Registry:   "registry.example.com",
  5084  				Repository: "hello-world",
  5085  			},
  5086  			args: args{
  5087  				reference: "registry.example.com:5000/hello-world:sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  5088  			},
  5089  			want:    registry.Reference{},
  5090  			wantErr: errdef.ErrInvalidReference,
  5091  		},
  5092  		{
  5093  			name: "missing reference after the at sign",
  5094  			repoRef: registry.Reference{
  5095  				Registry:   "registry.example.com",
  5096  				Repository: "hello-world",
  5097  			},
  5098  			args: args{
  5099  				reference: "registry.example.com/hello-world@",
  5100  			},
  5101  			want:    registry.Reference{},
  5102  			wantErr: errdef.ErrInvalidReference,
  5103  		},
  5104  		{
  5105  			name: "missing reference after the colon",
  5106  			repoRef: registry.Reference{
  5107  				Registry: "localhost",
  5108  			},
  5109  			args: args{
  5110  				reference: "localhost:5000/hello:",
  5111  			},
  5112  			want:    registry.Reference{},
  5113  			wantErr: errdef.ErrInvalidReference,
  5114  		},
  5115  		{
  5116  			name:    "zero-size tag, zero-size digest",
  5117  			repoRef: registry.Reference{},
  5118  			args: args{
  5119  				reference: "localhost:5000/hello:@",
  5120  			},
  5121  			want:    registry.Reference{},
  5122  			wantErr: errdef.ErrInvalidReference,
  5123  		},
  5124  		{
  5125  			name:    "zero-size tag with valid digest",
  5126  			repoRef: registry.Reference{},
  5127  			args: args{
  5128  				reference: "localhost:5000/hello:@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  5129  			},
  5130  			want:    registry.Reference{},
  5131  			wantErr: errdef.ErrInvalidReference,
  5132  		},
  5133  		{
  5134  			name:    "valid tag with zero-size digest",
  5135  			repoRef: registry.Reference{},
  5136  			args: args{
  5137  				reference: "localhost:5000/hello:foobar@",
  5138  			},
  5139  			want:    registry.Reference{},
  5140  			wantErr: errdef.ErrInvalidReference,
  5141  		},
  5142  	}
  5143  	for _, tt := range tests {
  5144  		t.Run(tt.name, func(t *testing.T) {
  5145  			r := &Repository{
  5146  				Reference: tt.repoRef,
  5147  			}
  5148  			got, err := r.ParseReference(tt.args.reference)
  5149  			if !errors.Is(err, tt.wantErr) {
  5150  				t.Errorf("Repository.ParseReference() error = %v, wantErr %v", err, tt.wantErr)
  5151  				return
  5152  			}
  5153  			if !reflect.DeepEqual(got, tt.want) {
  5154  				t.Errorf("Repository.ParseReference() = %v, want %v", got, tt.want)
  5155  			}
  5156  		})
  5157  	}
  5158  }
  5159  
  5160  func TestRepository_SetReferrersCapability(t *testing.T) {
  5161  	repo, err := NewRepository("registry.example.com/test")
  5162  	if err != nil {
  5163  		t.Fatalf("NewRepository() error = %v", err)
  5164  	}
  5165  	// initial state
  5166  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5167  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5168  	}
  5169  
  5170  	// valid first time set
  5171  	if err := repo.SetReferrersCapability(true); err != nil {
  5172  		t.Errorf("Repository.SetReferrersCapability() error = %v", err)
  5173  	}
  5174  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5175  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5176  	}
  5177  
  5178  	// invalid second time set, state should be no changed
  5179  	if err := repo.SetReferrersCapability(false); !errors.Is(err, ErrReferrersCapabilityAlreadySet) {
  5180  		t.Errorf("Repository.SetReferrersCapability() error = %v, wantErr %v", err, ErrReferrersCapabilityAlreadySet)
  5181  	}
  5182  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5183  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5184  	}
  5185  }
  5186  
  5187  func Test_generateIndex(t *testing.T) {
  5188  	referrer_1 := ocispec.Artifact{
  5189  		MediaType:    ocispec.MediaTypeArtifactManifest,
  5190  		ArtifactType: "foo",
  5191  	}
  5192  	referrerJSON_1, err := json.Marshal(referrer_1)
  5193  	if err != nil {
  5194  		t.Fatal("failed to marshal manifest:", err)
  5195  	}
  5196  	referrer_2 := ocispec.Artifact{
  5197  		MediaType:    ocispec.MediaTypeArtifactManifest,
  5198  		ArtifactType: "bar",
  5199  	}
  5200  	referrerJSON_2, err := json.Marshal(referrer_2)
  5201  	if err != nil {
  5202  		t.Fatal("failed to marshal manifest:", err)
  5203  	}
  5204  	referrers := []ocispec.Descriptor{
  5205  		content.NewDescriptorFromBytes(referrer_1.MediaType, referrerJSON_1),
  5206  		content.NewDescriptorFromBytes(referrer_2.MediaType, referrerJSON_2),
  5207  	}
  5208  
  5209  	wantIndex := ocispec.Index{
  5210  		Versioned: specs.Versioned{
  5211  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  5212  		},
  5213  		MediaType: ocispec.MediaTypeImageIndex,
  5214  		Manifests: referrers,
  5215  	}
  5216  	wantIndexJSON, err := json.Marshal(wantIndex)
  5217  	if err != nil {
  5218  		t.Fatal("failed to marshal index:", err)
  5219  	}
  5220  	wantIndexDesc := content.NewDescriptorFromBytes(wantIndex.MediaType, wantIndexJSON)
  5221  
  5222  	wantEmptyIndex := ocispec.Index{
  5223  		Versioned: specs.Versioned{
  5224  			SchemaVersion: 2, // historical value. does not pertain to OCI or docker version
  5225  		},
  5226  		MediaType: ocispec.MediaTypeImageIndex,
  5227  		Manifests: []ocispec.Descriptor{},
  5228  	}
  5229  	wantEmptyIndexJSON, err := json.Marshal(wantEmptyIndex)
  5230  	if err != nil {
  5231  		t.Fatal("failed to marshal index:", err)
  5232  	}
  5233  	wantEmptyIndexDesc := content.NewDescriptorFromBytes(wantEmptyIndex.MediaType, wantEmptyIndexJSON)
  5234  
  5235  	tests := []struct {
  5236  		name      string
  5237  		manifests []ocispec.Descriptor
  5238  		wantDesc  ocispec.Descriptor
  5239  		wantBytes []byte
  5240  		wantErr   bool
  5241  	}{
  5242  		{
  5243  			name:      "non-empty referrers list",
  5244  			manifests: referrers,
  5245  			wantDesc:  wantIndexDesc,
  5246  			wantBytes: wantIndexJSON,
  5247  			wantErr:   false,
  5248  		},
  5249  		{
  5250  			name:      "nil referrers list",
  5251  			manifests: nil,
  5252  			wantDesc:  wantEmptyIndexDesc,
  5253  			wantBytes: wantEmptyIndexJSON,
  5254  			wantErr:   false,
  5255  		},
  5256  		{
  5257  			name:      "empty referrers list",
  5258  			manifests: nil,
  5259  			wantDesc:  wantEmptyIndexDesc,
  5260  			wantBytes: wantEmptyIndexJSON,
  5261  			wantErr:   false,
  5262  		},
  5263  	}
  5264  	for _, tt := range tests {
  5265  		t.Run(tt.name, func(t *testing.T) {
  5266  			got, got1, err := generateIndex(tt.manifests)
  5267  			if (err != nil) != tt.wantErr {
  5268  				t.Errorf("generateReferrersIndex() error = %v, wantErr %v", err, tt.wantErr)
  5269  				return
  5270  			}
  5271  			if !reflect.DeepEqual(got, tt.wantDesc) {
  5272  				t.Errorf("generateReferrersIndex() got = %v, want %v", got, tt.wantDesc)
  5273  			}
  5274  			if !reflect.DeepEqual(got1, tt.wantBytes) {
  5275  				t.Errorf("generateReferrersIndex() got1 = %v, want %v", got1, tt.wantBytes)
  5276  			}
  5277  		})
  5278  	}
  5279  }
  5280  
  5281  func TestRepository_pingReferrers(t *testing.T) {
  5282  	// referrers available
  5283  	count := 0
  5284  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5285  		switch {
  5286  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  5287  			count++
  5288  			w.WriteHeader(http.StatusOK)
  5289  		default:
  5290  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5291  			w.WriteHeader(http.StatusNotFound)
  5292  		}
  5293  
  5294  	}))
  5295  	defer ts.Close()
  5296  	uri, err := url.Parse(ts.URL)
  5297  	if err != nil {
  5298  		t.Fatalf("invalid test http server: %v", err)
  5299  	}
  5300  
  5301  	ctx := context.Background()
  5302  	repo, err := NewRepository(uri.Host + "/test")
  5303  	if err != nil {
  5304  		t.Fatalf("NewRepository() error = %v", err)
  5305  	}
  5306  	repo.PlainHTTP = true
  5307  
  5308  	// 1st call
  5309  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5310  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5311  	}
  5312  	got, err := repo.pingReferrers(ctx)
  5313  	if err != nil {
  5314  		t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  5315  	}
  5316  	if got != true {
  5317  		t.Errorf("Repository.pingReferrers() = %v, want %v", got, true)
  5318  	}
  5319  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5320  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5321  	}
  5322  	if count != 1 {
  5323  		t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  5324  	}
  5325  
  5326  	// 2nd call
  5327  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5328  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5329  	}
  5330  	got, err = repo.pingReferrers(ctx)
  5331  	if err != nil {
  5332  		t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  5333  	}
  5334  	if got != true {
  5335  		t.Errorf("Repository.pingReferrers() = %v, want %v", got, true)
  5336  	}
  5337  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5338  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5339  	}
  5340  	if count != 1 {
  5341  		t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  5342  	}
  5343  
  5344  	// referrers unavailable
  5345  	count = 0
  5346  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5347  		switch {
  5348  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  5349  			count++
  5350  			w.WriteHeader(http.StatusNotFound)
  5351  		default:
  5352  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5353  			w.WriteHeader(http.StatusNotFound)
  5354  		}
  5355  
  5356  	}))
  5357  	defer ts.Close()
  5358  	uri, err = url.Parse(ts.URL)
  5359  	if err != nil {
  5360  		t.Fatalf("invalid test http server: %v", err)
  5361  	}
  5362  
  5363  	ctx = context.Background()
  5364  	repo, err = NewRepository(uri.Host + "/test")
  5365  	if err != nil {
  5366  		t.Fatalf("NewRepository() error = %v", err)
  5367  	}
  5368  	repo.PlainHTTP = true
  5369  
  5370  	// 1st call
  5371  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5372  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5373  	}
  5374  	got, err = repo.pingReferrers(ctx)
  5375  	if err != nil {
  5376  		t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  5377  	}
  5378  	if got != false {
  5379  		t.Errorf("Repository.pingReferrers() = %v, want %v", got, false)
  5380  	}
  5381  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5382  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5383  	}
  5384  	if count != 1 {
  5385  		t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  5386  	}
  5387  
  5388  	// 2nd call
  5389  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5390  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5391  	}
  5392  	got, err = repo.pingReferrers(ctx)
  5393  	if err != nil {
  5394  		t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  5395  	}
  5396  	if got != false {
  5397  		t.Errorf("Repository.pingReferrers() = %v, want %v", got, false)
  5398  	}
  5399  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5400  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5401  	}
  5402  	if count != 1 {
  5403  		t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  5404  	}
  5405  }
  5406  
  5407  func TestRepository_pingReferrers_RepositoryNotFound(t *testing.T) {
  5408  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5409  		if r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest {
  5410  			w.WriteHeader(http.StatusNotFound)
  5411  			w.Write([]byte(`{ "errors": [ { "code": "NAME_UNKNOWN", "message": "repository name not known to registry" } ] }`))
  5412  			return
  5413  		}
  5414  		t.Errorf("unexpected access: %s %q", r.Method, r.URL)
  5415  		w.WriteHeader(http.StatusNotFound)
  5416  	}))
  5417  	defer ts.Close()
  5418  	uri, err := url.Parse(ts.URL)
  5419  	if err != nil {
  5420  		t.Fatalf("invalid test http server: %v", err)
  5421  	}
  5422  	ctx := context.Background()
  5423  
  5424  	// test referrers state unknown
  5425  	repo, err := NewRepository(uri.Host + "/test")
  5426  	if err != nil {
  5427  		t.Fatalf("NewRepository() error = %v", err)
  5428  	}
  5429  	repo.PlainHTTP = true
  5430  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5431  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5432  	}
  5433  	if _, err = repo.pingReferrers(ctx); err == nil {
  5434  		t.Fatalf("Repository.pingReferrers() error = %v, wantErr %v", err, true)
  5435  	}
  5436  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5437  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5438  	}
  5439  
  5440  	// test referrers state supported
  5441  	repo, err = NewRepository(uri.Host + "/test")
  5442  	if err != nil {
  5443  		t.Fatalf("NewRepository() error = %v", err)
  5444  	}
  5445  	repo.PlainHTTP = true
  5446  	repo.SetReferrersCapability(true)
  5447  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5448  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5449  	}
  5450  	got, err := repo.pingReferrers(ctx)
  5451  	if err != nil {
  5452  		t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  5453  	}
  5454  	if got != true {
  5455  		t.Errorf("Repository.pingReferrers() = %v, want %v", got, true)
  5456  	}
  5457  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5458  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5459  	}
  5460  
  5461  	// test referrers state unsupported
  5462  	repo, err = NewRepository(uri.Host + "/test")
  5463  	if err != nil {
  5464  		t.Fatalf("NewRepository() error = %v", err)
  5465  	}
  5466  	repo.PlainHTTP = true
  5467  	repo.SetReferrersCapability(false)
  5468  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5469  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5470  	}
  5471  	got, err = repo.pingReferrers(ctx)
  5472  	if err != nil {
  5473  		t.Errorf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  5474  	}
  5475  	if got != false {
  5476  		t.Errorf("Repository.pingReferrers() = %v, want %v", got, false)
  5477  	}
  5478  	if state := repo.loadReferrersState(); state != referrersStateUnsupported {
  5479  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnsupported)
  5480  	}
  5481  }
  5482  
  5483  func TestRepository_pingReferrers_Concurrent(t *testing.T) {
  5484  	// referrers available
  5485  	var count int32
  5486  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5487  		switch {
  5488  		case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest:
  5489  			atomic.AddInt32(&count, 1)
  5490  			w.WriteHeader(http.StatusOK)
  5491  		default:
  5492  			t.Errorf("unexpected access: %s %s", r.Method, r.URL)
  5493  			w.WriteHeader(http.StatusNotFound)
  5494  		}
  5495  
  5496  	}))
  5497  	defer ts.Close()
  5498  	uri, err := url.Parse(ts.URL)
  5499  	if err != nil {
  5500  		t.Fatalf("invalid test http server: %v", err)
  5501  	}
  5502  
  5503  	ctx := context.Background()
  5504  	repo, err := NewRepository(uri.Host + "/test")
  5505  	if err != nil {
  5506  		t.Fatalf("NewRepository() error = %v", err)
  5507  	}
  5508  	repo.PlainHTTP = true
  5509  
  5510  	concurrency := 64
  5511  	eg, egCtx := errgroup.WithContext(ctx)
  5512  
  5513  	if state := repo.loadReferrersState(); state != referrersStateUnknown {
  5514  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown)
  5515  	}
  5516  	for i := 0; i < concurrency; i++ {
  5517  		eg.Go(func() func() error {
  5518  			return func() error {
  5519  				got, err := repo.pingReferrers(egCtx)
  5520  				if err != nil {
  5521  					t.Fatalf("Repository.pingReferrers() error = %v, wantErr %v", err, nil)
  5522  				}
  5523  				if got != true {
  5524  					t.Errorf("Repository.pingReferrers() = %v, want %v", got, true)
  5525  				}
  5526  				return nil
  5527  			}
  5528  		}())
  5529  	}
  5530  	if err := eg.Wait(); err != nil {
  5531  		t.Fatal(err)
  5532  	}
  5533  
  5534  	if got := atomic.LoadInt32(&count); got != 1 {
  5535  		t.Errorf("count(Repository.pingReferrers()) = %v, want %v", count, 1)
  5536  	}
  5537  	if state := repo.loadReferrersState(); state != referrersStateSupported {
  5538  		t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported)
  5539  	}
  5540  }