...

Source file src/github.com/google/go-containerregistry/pkg/registry/registry_test.go

Documentation: github.com/google/go-containerregistry/pkg/registry

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     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  package registry_test
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"log"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"net/url"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/google/go-containerregistry/pkg/registry"
    28  	v1 "github.com/google/go-containerregistry/pkg/v1"
    29  )
    30  
    31  const (
    32  	weirdIndex = `{
    33    "manifests": [
    34  	  {
    35  			"digest":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
    36  			"mediaType":"application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
    37  		},{
    38  			"digest":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
    39  			"mediaType":"application/xml"
    40  		},{
    41  			"digest":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
    42  			"mediaType":"application/vnd.oci.image.manifest.v1+json"
    43  		}
    44  	]
    45  }`
    46  )
    47  
    48  func sha256String(s string) string {
    49  	h, _, _ := v1.SHA256(strings.NewReader(s))
    50  	return h.Hex
    51  }
    52  
    53  func TestCalls(t *testing.T) {
    54  	tcs := []struct {
    55  		Description string
    56  
    57  		// Request / setup
    58  		URL           string
    59  		Digests       map[string]string
    60  		Manifests     map[string]string
    61  		BlobStream    map[string]string
    62  		RequestHeader map[string]string
    63  
    64  		// Response
    65  		Code   int
    66  		Header map[string]string
    67  		Method string
    68  		Body   string // request body to send
    69  		Want   string // response body to expect
    70  	}{
    71  		{
    72  			Description: "/v2 returns 200",
    73  			Method:      "GET",
    74  			URL:         "/v2",
    75  			Code:        http.StatusOK,
    76  			Header:      map[string]string{"Docker-Distribution-API-Version": "registry/2.0"},
    77  		},
    78  		{
    79  			Description: "/v2/ returns 200",
    80  			Method:      "GET",
    81  			URL:         "/v2/",
    82  			Code:        http.StatusOK,
    83  			Header:      map[string]string{"Docker-Distribution-API-Version": "registry/2.0"},
    84  		},
    85  		{
    86  			Description: "/v2/bad returns 404",
    87  			Method:      "GET",
    88  			URL:         "/v2/bad",
    89  			Code:        http.StatusNotFound,
    90  			Header:      map[string]string{"Docker-Distribution-API-Version": "registry/2.0"},
    91  		},
    92  		{
    93  			Description: "GET non existent blob",
    94  			Method:      "GET",
    95  			URL:         "/v2/foo/blobs/sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
    96  			Code:        http.StatusNotFound,
    97  		},
    98  		{
    99  			Description: "HEAD non existent blob",
   100  			Method:      "HEAD",
   101  			URL:         "/v2/foo/blobs/sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   102  			Code:        http.StatusNotFound,
   103  		},
   104  		{
   105  			Description: "GET bad digest",
   106  			Method:      "GET",
   107  			URL:         "/v2/foo/blobs/sha256:asd",
   108  			Code:        http.StatusBadRequest,
   109  		},
   110  		{
   111  			Description: "HEAD bad digest",
   112  			Method:      "HEAD",
   113  			URL:         "/v2/foo/blobs/sha256:asd",
   114  			Code:        http.StatusBadRequest,
   115  		},
   116  		{
   117  			Description: "bad blob verb",
   118  			Method:      "FOO",
   119  			URL:         "/v2/foo/blobs/sha256:asd",
   120  			Code:        http.StatusBadRequest,
   121  		},
   122  		{
   123  			Description: "GET containerless blob",
   124  			Digests:     map[string]string{"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae": "foo"},
   125  			Method:      "GET",
   126  			URL:         "/v2/foo/blobs/sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   127  			Code:        http.StatusOK,
   128  			Header:      map[string]string{"Docker-Content-Digest": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"},
   129  			Want:        "foo",
   130  		},
   131  		{
   132  			Description: "GET blob",
   133  			Digests:     map[string]string{"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae": "foo"},
   134  			Method:      "GET",
   135  			URL:         "/v2/foo/blobs/sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   136  			Code:        http.StatusOK,
   137  			Header:      map[string]string{"Docker-Content-Digest": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"},
   138  			Want:        "foo",
   139  		},
   140  		{
   141  			Description: "HEAD blob",
   142  			Digests:     map[string]string{"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae": "foo"},
   143  			Method:      "HEAD",
   144  			URL:         "/v2/foo/blobs/sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   145  			Code:        http.StatusOK,
   146  			Header: map[string]string{
   147  				"Content-Length":        "3",
   148  				"Docker-Content-Digest": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   149  			},
   150  		},
   151  		{
   152  			Description: "DELETE blob",
   153  			Digests:     map[string]string{"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae": "foo"},
   154  			Method:      "DELETE",
   155  			URL:         "/v2/foo/blobs/sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   156  			Code:        http.StatusAccepted,
   157  		},
   158  		{
   159  			Description: "blob url with no container",
   160  			Method:      "GET",
   161  			URL:         "/v2/blobs/sha256:asd",
   162  			Code:        http.StatusBadRequest,
   163  		},
   164  		{
   165  			Description: "uploadurl",
   166  			Method:      "POST",
   167  			URL:         "/v2/foo/blobs/uploads",
   168  			Code:        http.StatusAccepted,
   169  			Header:      map[string]string{"Range": "0-0"},
   170  		},
   171  		{
   172  			Description: "uploadurl",
   173  			Method:      "POST",
   174  			URL:         "/v2/foo/blobs/uploads/",
   175  			Code:        http.StatusAccepted,
   176  			Header:      map[string]string{"Range": "0-0"},
   177  		},
   178  		{
   179  			Description: "upload put missing digest",
   180  			Method:      "PUT",
   181  			URL:         "/v2/foo/blobs/uploads/1",
   182  			Code:        http.StatusBadRequest,
   183  		},
   184  		{
   185  			Description: "monolithic upload good digest",
   186  			Method:      "POST",
   187  			URL:         "/v2/foo/blobs/uploads?digest=sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   188  			Code:        http.StatusCreated,
   189  			Body:        "foo",
   190  			Header:      map[string]string{"Docker-Content-Digest": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"},
   191  		},
   192  		{
   193  			Description: "monolithic upload bad digest",
   194  			Method:      "POST",
   195  			URL:         "/v2/foo/blobs/uploads?digest=sha256:fake",
   196  			Code:        http.StatusBadRequest,
   197  			Body:        "foo",
   198  		},
   199  		{
   200  			Description: "upload good digest",
   201  			Method:      "PUT",
   202  			URL:         "/v2/foo/blobs/uploads/1?digest=sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   203  			Code:        http.StatusCreated,
   204  			Body:        "foo",
   205  			Header:      map[string]string{"Docker-Content-Digest": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"},
   206  		},
   207  		{
   208  			Description: "upload bad digest",
   209  			Method:      "PUT",
   210  			URL:         "/v2/foo/blobs/uploads/1?digest=sha256:baddigest",
   211  			Code:        http.StatusBadRequest,
   212  			Body:        "foo",
   213  		},
   214  		{
   215  			Description: "stream upload",
   216  			Method:      "PATCH",
   217  			URL:         "/v2/foo/blobs/uploads/1",
   218  			Code:        http.StatusNoContent,
   219  			Body:        "foo",
   220  			Header: map[string]string{
   221  				"Range":    "0-2",
   222  				"Location": "/v2/foo/blobs/uploads/1",
   223  			},
   224  		},
   225  		{
   226  			Description: "stream duplicate upload",
   227  			Method:      "PATCH",
   228  			URL:         "/v2/foo/blobs/uploads/1",
   229  			Code:        http.StatusBadRequest,
   230  			Body:        "foo",
   231  			BlobStream:  map[string]string{"1": "foo"},
   232  		},
   233  		{
   234  			Description: "stream finish upload",
   235  			Method:      "PUT",
   236  			URL:         "/v2/foo/blobs/uploads/1?digest=sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
   237  			BlobStream:  map[string]string{"1": "foo"},
   238  			Code:        http.StatusCreated,
   239  			Header:      map[string]string{"Docker-Content-Digest": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"},
   240  		},
   241  		{
   242  			Description: "get missing manifest",
   243  			Method:      "GET",
   244  			URL:         "/v2/foo/manifests/latest",
   245  			Code:        http.StatusNotFound,
   246  		},
   247  		{
   248  			Description: "head missing manifest",
   249  			Method:      "HEAD",
   250  			URL:         "/v2/foo/manifests/latest",
   251  			Code:        http.StatusNotFound,
   252  		},
   253  		{
   254  			Description: "get missing manifest good container",
   255  			Manifests:   map[string]string{"foo/manifests/latest": "foo"},
   256  			Method:      "GET",
   257  			URL:         "/v2/foo/manifests/bar",
   258  			Code:        http.StatusNotFound,
   259  		},
   260  		{
   261  			Description: "head missing manifest good container",
   262  			Manifests:   map[string]string{"foo/manifests/latest": "foo"},
   263  			Method:      "HEAD",
   264  			URL:         "/v2/foo/manifests/bar",
   265  			Code:        http.StatusNotFound,
   266  		},
   267  		{
   268  			Description: "get manifest by tag",
   269  			Manifests:   map[string]string{"foo/manifests/latest": "foo"},
   270  			Method:      "GET",
   271  			URL:         "/v2/foo/manifests/latest",
   272  			Code:        http.StatusOK,
   273  			Want:        "foo",
   274  		},
   275  		{
   276  			Description: "get manifest by digest",
   277  			Manifests:   map[string]string{"foo/manifests/latest": "foo"},
   278  			Method:      "GET",
   279  			URL:         "/v2/foo/manifests/sha256:" + sha256String("foo"),
   280  			Code:        http.StatusOK,
   281  			Want:        "foo",
   282  		},
   283  		{
   284  			Description: "head manifest",
   285  			Manifests:   map[string]string{"foo/manifests/latest": "foo"},
   286  			Method:      "HEAD",
   287  			URL:         "/v2/foo/manifests/latest",
   288  			Code:        http.StatusOK,
   289  		},
   290  		{
   291  			Description: "create manifest",
   292  			Method:      "PUT",
   293  			URL:         "/v2/foo/manifests/latest",
   294  			Code:        http.StatusCreated,
   295  			Body:        "foo",
   296  		},
   297  		{
   298  			Description: "create index",
   299  			Method:      "PUT",
   300  			URL:         "/v2/foo/manifests/latest",
   301  			Code:        http.StatusCreated,
   302  			Body:        weirdIndex,
   303  			RequestHeader: map[string]string{
   304  				"Content-Type": "application/vnd.oci.image.index.v1+json",
   305  			},
   306  			Manifests: map[string]string{"foo/manifests/image": "foo"},
   307  		},
   308  		{
   309  			Description: "create index missing child",
   310  			Method:      "PUT",
   311  			URL:         "/v2/foo/manifests/latest",
   312  			Code:        http.StatusNotFound,
   313  			Body:        weirdIndex,
   314  			RequestHeader: map[string]string{
   315  				"Content-Type": "application/vnd.oci.image.index.v1+json",
   316  			},
   317  		},
   318  		{
   319  			Description: "bad index body",
   320  			Method:      "PUT",
   321  			URL:         "/v2/foo/manifests/latest",
   322  			Code:        http.StatusBadRequest,
   323  			Body:        "foo",
   324  			RequestHeader: map[string]string{
   325  				"Content-Type": "application/vnd.oci.image.index.v1+json",
   326  			},
   327  		},
   328  		{
   329  			Description: "bad manifest method",
   330  			Method:      "BAR",
   331  			URL:         "/v2/foo/manifests/latest",
   332  			Code:        http.StatusBadRequest,
   333  		},
   334  		{
   335  			Description:   "Chunk upload start",
   336  			Method:        "PATCH",
   337  			URL:           "/v2/foo/blobs/uploads/1",
   338  			RequestHeader: map[string]string{"Content-Range": "0-3"},
   339  			Code:          http.StatusNoContent,
   340  			Body:          "foo",
   341  			Header: map[string]string{
   342  				"Range":    "0-2",
   343  				"Location": "/v2/foo/blobs/uploads/1",
   344  			},
   345  		},
   346  		{
   347  			Description:   "Chunk upload bad content range",
   348  			Method:        "PATCH",
   349  			URL:           "/v2/foo/blobs/uploads/1",
   350  			RequestHeader: map[string]string{"Content-Range": "0-bar"},
   351  			Code:          http.StatusRequestedRangeNotSatisfiable,
   352  			Body:          "foo",
   353  		},
   354  		{
   355  			Description:   "Chunk upload overlaps previous data",
   356  			Method:        "PATCH",
   357  			URL:           "/v2/foo/blobs/uploads/1",
   358  			BlobStream:    map[string]string{"1": "foo"},
   359  			RequestHeader: map[string]string{"Content-Range": "2-5"},
   360  			Code:          http.StatusRequestedRangeNotSatisfiable,
   361  			Body:          "bar",
   362  		},
   363  		{
   364  			Description:   "Chunk upload after previous data",
   365  			Method:        "PATCH",
   366  			URL:           "/v2/foo/blobs/uploads/1",
   367  			BlobStream:    map[string]string{"1": "foo"},
   368  			RequestHeader: map[string]string{"Content-Range": "3-6"},
   369  			Code:          http.StatusNoContent,
   370  			Body:          "bar",
   371  			Header: map[string]string{
   372  				"Range":    "0-5",
   373  				"Location": "/v2/foo/blobs/uploads/1",
   374  			},
   375  		},
   376  		{
   377  			Description: "DELETE Unknown name",
   378  			Method:      "DELETE",
   379  			URL:         "/v2/test/honk/manifests/latest",
   380  			Code:        http.StatusNotFound,
   381  		},
   382  		{
   383  			Description: "DELETE Unknown manifest",
   384  			Manifests:   map[string]string{"honk/manifests/latest": "honk"},
   385  			Method:      "DELETE",
   386  			URL:         "/v2/honk/manifests/tag-honk",
   387  			Code:        http.StatusNotFound,
   388  		},
   389  		{
   390  			Description: "DELETE existing manifest",
   391  			Manifests:   map[string]string{"foo/manifests/latest": "foo"},
   392  			Method:      "DELETE",
   393  			URL:         "/v2/foo/manifests/latest",
   394  			Code:        http.StatusAccepted,
   395  		},
   396  		{
   397  			Description: "DELETE existing manifest by digest",
   398  			Manifests:   map[string]string{"foo/manifests/latest": "foo"},
   399  			Method:      "DELETE",
   400  			URL:         "/v2/foo/manifests/sha256:" + sha256String("foo"),
   401  			Code:        http.StatusAccepted,
   402  		},
   403  		{
   404  			Description: "list tags",
   405  			Manifests:   map[string]string{"foo/manifests/latest": "foo", "foo/manifests/tag1": "foo"},
   406  			Method:      "GET",
   407  			URL:         "/v2/foo/tags/list?n=1000",
   408  			Code:        http.StatusOK,
   409  			Want:        `{"name":"foo","tags":["latest","tag1"]}`,
   410  		},
   411  		{
   412  			Description: "limit tags",
   413  			Manifests:   map[string]string{"foo/manifests/latest": "foo", "foo/manifests/tag1": "foo"},
   414  			Method:      "GET",
   415  			URL:         "/v2/foo/tags/list?n=1",
   416  			Code:        http.StatusOK,
   417  			Want:        `{"name":"foo","tags":["latest"]}`,
   418  		},
   419  		{
   420  			Description: "offset tags",
   421  			Manifests:   map[string]string{"foo/manifests/latest": "foo", "foo/manifests/tag1": "foo"},
   422  			Method:      "GET",
   423  			URL:         "/v2/foo/tags/list?last=latest",
   424  			Code:        http.StatusOK,
   425  			Want:        `{"name":"foo","tags":["tag1"]}`,
   426  		},
   427  		{
   428  			Description: "list non existing tags",
   429  			Method:      "GET",
   430  			URL:         "/v2/foo/tags/list?n=1000",
   431  			Code:        http.StatusNotFound,
   432  		},
   433  		{
   434  			Description: "list repos",
   435  			Manifests:   map[string]string{"foo/manifests/latest": "foo", "bar/manifests/latest": "bar"},
   436  			Method:      "GET",
   437  			URL:         "/v2/_catalog?n=1000",
   438  			Code:        http.StatusOK,
   439  		},
   440  		{
   441  			Description: "fetch references",
   442  			Method:      "GET",
   443  			URL:         "/v2/foo/referrers/sha256:" + sha256String("foo"),
   444  			Code:        http.StatusOK,
   445  			Manifests: map[string]string{
   446  				"foo/manifests/image":           "foo",
   447  				"foo/manifests/points-to-image": "{\"subject\": {\"digest\": \"sha256:" + sha256String("foo") + "\"}}",
   448  			},
   449  			Header: map[string]string{
   450  				"Content-Type": "application/vnd.oci.image.index.v1+json",
   451  			},
   452  		},
   453  		{
   454  			Description: "fetch references, subject pointing elsewhere",
   455  			Method:      "GET",
   456  			URL:         "/v2/foo/referrers/sha256:" + sha256String("foo"),
   457  			Code:        http.StatusOK,
   458  			Manifests: map[string]string{
   459  				"foo/manifests/image":           "foo",
   460  				"foo/manifests/points-to-image": "{\"subject\": {\"digest\": \"sha256:" + sha256String("nonexistant") + "\"}}",
   461  			},
   462  			Header: map[string]string{
   463  				"Content-Type": "application/vnd.oci.image.index.v1+json",
   464  			},
   465  		},
   466  		{
   467  			Description: "fetch references, no results",
   468  			Method:      "GET",
   469  			URL:         "/v2/foo/referrers/sha256:" + sha256String("foo"),
   470  			Code:        http.StatusOK,
   471  			Manifests: map[string]string{
   472  				"foo/manifests/image": "foo",
   473  			},
   474  			Header: map[string]string{
   475  				"Content-Type": "application/vnd.oci.image.index.v1+json",
   476  			},
   477  		},
   478  		{
   479  			Description: "fetch references, missing repo",
   480  			Method:      "GET",
   481  			URL:         "/v2/does-not-exist/referrers/sha256:" + sha256String("foo"),
   482  			Code:        http.StatusNotFound,
   483  		},
   484  		{
   485  			Description: "fetch references, bad target (tag vs. digest)",
   486  			Method:      "GET",
   487  			URL:         "/v2/foo/referrers/latest",
   488  			Code:        http.StatusBadRequest,
   489  		},
   490  		{
   491  			Description: "fetch references, bad method",
   492  			Method:      "POST",
   493  			URL:         "/v2/foo/referrers/sha256:" + sha256String("foo"),
   494  			Code:        http.StatusBadRequest,
   495  		},
   496  	}
   497  
   498  	for _, tc := range tcs {
   499  
   500  		var logger *log.Logger
   501  		testf := func(t *testing.T) {
   502  
   503  			opts := []registry.Option{registry.WithReferrersSupport(true)}
   504  			if logger != nil {
   505  				opts = append(opts, registry.Logger(logger))
   506  			}
   507  			r := registry.New(opts...)
   508  			s := httptest.NewServer(r)
   509  			defer s.Close()
   510  
   511  			for manifest, contents := range tc.Manifests {
   512  				u, err := url.Parse(s.URL + "/v2/" + manifest)
   513  				if err != nil {
   514  					t.Fatalf("Error parsing %q: %v", s.URL+"/v2", err)
   515  				}
   516  				req := &http.Request{
   517  					Method: "PUT",
   518  					URL:    u,
   519  					Body:   io.NopCloser(strings.NewReader(contents)),
   520  				}
   521  				t.Log(req.Method, req.URL)
   522  				resp, err := s.Client().Do(req)
   523  				if err != nil {
   524  					t.Fatalf("Error uploading manifest: %v", err)
   525  				}
   526  				if resp.StatusCode != http.StatusCreated {
   527  					body, _ := io.ReadAll(resp.Body)
   528  					t.Fatalf("Error uploading manifest got status: %d %s", resp.StatusCode, body)
   529  				}
   530  				t.Logf("created manifest with digest %v", resp.Header.Get("Docker-Content-Digest"))
   531  			}
   532  
   533  			for digest, contents := range tc.Digests {
   534  				u, err := url.Parse(fmt.Sprintf("%s/v2/foo/blobs/uploads/1?digest=%s", s.URL, digest))
   535  				if err != nil {
   536  					t.Fatalf("Error parsing %q: %v", s.URL+tc.URL, err)
   537  				}
   538  				req := &http.Request{
   539  					Method: "PUT",
   540  					URL:    u,
   541  					Body:   io.NopCloser(strings.NewReader(contents)),
   542  				}
   543  				t.Log(req.Method, req.URL)
   544  				resp, err := s.Client().Do(req)
   545  				if err != nil {
   546  					t.Fatalf("Error uploading digest: %v", err)
   547  				}
   548  				if resp.StatusCode != http.StatusCreated {
   549  					body, _ := io.ReadAll(resp.Body)
   550  					t.Fatalf("Error uploading digest got status: %d %s", resp.StatusCode, body)
   551  				}
   552  			}
   553  
   554  			for upload, contents := range tc.BlobStream {
   555  				u, err := url.Parse(fmt.Sprintf("%s/v2/foo/blobs/uploads/%s", s.URL, upload))
   556  				if err != nil {
   557  					t.Fatalf("Error parsing %q: %v", s.URL+tc.URL, err)
   558  				}
   559  				req := &http.Request{
   560  					Method: "PATCH",
   561  					URL:    u,
   562  					Body:   io.NopCloser(strings.NewReader(contents)),
   563  				}
   564  				t.Log(req.Method, req.URL)
   565  				resp, err := s.Client().Do(req)
   566  				if err != nil {
   567  					t.Fatalf("Error streaming blob: %v", err)
   568  				}
   569  				if resp.StatusCode != http.StatusNoContent {
   570  					body, _ := io.ReadAll(resp.Body)
   571  					t.Fatalf("Error streaming blob: %d %s", resp.StatusCode, body)
   572  				}
   573  
   574  			}
   575  
   576  			u, err := url.Parse(s.URL + tc.URL)
   577  			if err != nil {
   578  				t.Fatalf("Error parsing %q: %v", s.URL+tc.URL, err)
   579  			}
   580  			req := &http.Request{
   581  				Method: tc.Method,
   582  				URL:    u,
   583  				Body:   io.NopCloser(strings.NewReader(tc.Body)),
   584  				Header: map[string][]string{},
   585  			}
   586  			for k, v := range tc.RequestHeader {
   587  				req.Header.Set(k, v)
   588  			}
   589  			t.Log(req.Method, req.URL)
   590  			resp, err := s.Client().Do(req)
   591  			if err != nil {
   592  				t.Fatalf("Error getting %q: %v", tc.URL, err)
   593  			}
   594  			defer resp.Body.Close()
   595  			body, err := io.ReadAll(resp.Body)
   596  			if err != nil {
   597  				t.Errorf("Reading response body: %v", err)
   598  			}
   599  			if resp.StatusCode != tc.Code {
   600  				t.Errorf("Incorrect status code, got %d, want %d; body: %s", resp.StatusCode, tc.Code, body)
   601  			}
   602  
   603  			for k, v := range tc.Header {
   604  				r := resp.Header.Get(k)
   605  				if r != v {
   606  					t.Errorf("Incorrect header %q received, got %q, want %q", k, r, v)
   607  				}
   608  			}
   609  
   610  			if tc.Want != "" && string(body) != tc.Want {
   611  				t.Errorf("Incorrect response body, got %q, want %q", body, tc.Want)
   612  			}
   613  		}
   614  		t.Run(tc.Description, testf)
   615  		logger = log.New(io.Discard, "", log.Ldate)
   616  		t.Run(tc.Description+" - custom log", testf)
   617  	}
   618  }
   619  

View as plain text