45b7b9cec3
Client attempts to parse the body of every error it receives as JSON regardless of the content-type. This commit rectifies by only parsing he error body as JSON if the Content-Type header is set to either "application/json" or "application/vnd.api+json". Signed-off-by: Milos Gajdos <milosthegajdos@gmail.com>
503 lines
13 KiB
Go
503 lines
13 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/distribution/distribution/v3"
|
|
"github.com/distribution/distribution/v3/registry/api/errcode"
|
|
v2 "github.com/distribution/distribution/v3/registry/api/v2"
|
|
"github.com/distribution/distribution/v3/testutil"
|
|
)
|
|
|
|
// Test implements distribution.BlobWriter
|
|
var _ distribution.BlobWriter = &httpBlobUpload{}
|
|
|
|
func TestUploadReadFrom(t *testing.T) {
|
|
_, b := newRandomBlob(64)
|
|
repo := "test/upload/readfrom"
|
|
locationPath := fmt.Sprintf("/v2/%s/uploads/testid", repo)
|
|
|
|
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodGet,
|
|
Route: "/v2/",
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusOK,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Distribution-API-Version": {"registry/2.0"},
|
|
}),
|
|
},
|
|
},
|
|
// Test Valid case
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {locationPath},
|
|
"Range": {"0-63"},
|
|
}),
|
|
},
|
|
},
|
|
// Test invalid range
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {locationPath},
|
|
"Range": {""},
|
|
}),
|
|
},
|
|
},
|
|
// Test 404
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusNotFound,
|
|
},
|
|
},
|
|
// Test 400 valid json
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusBadRequest,
|
|
Headers: http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
|
|
Body: []byte(`
|
|
{ "errors":
|
|
[
|
|
{
|
|
"code": "BLOB_UPLOAD_INVALID",
|
|
"message": "blob upload invalid",
|
|
"detail": "more detail"
|
|
}
|
|
]
|
|
} `),
|
|
},
|
|
},
|
|
// Test 400 invalid json
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusBadRequest,
|
|
Headers: http.Header{"Content-Type": []string{"application/json"}},
|
|
Body: []byte("something bad happened"),
|
|
},
|
|
},
|
|
// Test 500
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
},
|
|
})
|
|
|
|
e, c := testServer(m)
|
|
defer c()
|
|
|
|
blobUpload := &httpBlobUpload{
|
|
ctx: context.Background(),
|
|
client: &http.Client{},
|
|
}
|
|
|
|
// Valid case
|
|
blobUpload.location = e + locationPath
|
|
n, err := blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err != nil {
|
|
t.Fatalf("Error calling ReadFrom: %s", err)
|
|
}
|
|
if n != 64 {
|
|
t.Fatalf("Wrong length returned from ReadFrom: %d, expected 64", n)
|
|
}
|
|
|
|
// Bad range
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when bad range received")
|
|
}
|
|
|
|
// 404
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if err != distribution.ErrBlobUploadUnknown {
|
|
t.Fatalf("Wrong error thrown: %s, expected %s", err, distribution.ErrBlobUploadUnknown)
|
|
}
|
|
|
|
// 400 valid json
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(errcode.Errors); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else if len(uploadErr) != 1 {
|
|
t.Fatalf("Unexpected number of errors: %d, expected 1", len(uploadErr))
|
|
} else {
|
|
v2Err, ok := uploadErr[0].(errcode.Error)
|
|
if !ok {
|
|
t.Fatalf("Not an 'Error' type: %#v", uploadErr[0])
|
|
}
|
|
if v2Err.Code != v2.ErrorCodeBlobUploadInvalid {
|
|
t.Fatalf("Unexpected error code: %s, expected %d", v2Err.Code.String(), v2.ErrorCodeBlobUploadInvalid)
|
|
}
|
|
if expected := "blob upload invalid"; v2Err.Message != expected {
|
|
t.Fatalf("Unexpected error message: %q, expected %q", v2Err.Message, expected)
|
|
}
|
|
if expected := "more detail"; v2Err.Detail.(string) != expected {
|
|
t.Fatalf("Unexpected error message: %q, expected %q", v2Err.Detail.(string), expected)
|
|
}
|
|
}
|
|
|
|
// 400 invalid json
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(*UnexpectedHTTPResponseError); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else {
|
|
respStr := string(uploadErr.Response)
|
|
if expected := "something bad happened"; respStr != expected {
|
|
t.Fatalf("Unexpected response string: %s, expected: %s", respStr, expected)
|
|
}
|
|
}
|
|
|
|
// 500
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(*UnexpectedHTTPStatusError); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else if expected := "500 " + http.StatusText(http.StatusInternalServerError); uploadErr.Status != expected {
|
|
t.Fatalf("Unexpected response status: %s, expected %s", uploadErr.Status, expected)
|
|
}
|
|
}
|
|
|
|
func TestUploadSize(t *testing.T) {
|
|
_, b := newRandomBlob(64)
|
|
readFromLocationPath := "/v2/test/upload/readfrom/uploads/testid"
|
|
writeLocationPath := "/v2/test/upload/readfrom/uploads/testid"
|
|
|
|
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodGet,
|
|
Route: "/v2/",
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusOK,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Distribution-API-Version": {"registry/2.0"},
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: readFromLocationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {readFromLocationPath},
|
|
"Range": {"0-63"},
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: writeLocationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {writeLocationPath},
|
|
"Range": {"0-63"},
|
|
}),
|
|
},
|
|
},
|
|
})
|
|
|
|
e, c := testServer(m)
|
|
defer c()
|
|
|
|
// Writing with ReadFrom
|
|
blobUpload := &httpBlobUpload{
|
|
ctx: context.Background(),
|
|
client: &http.Client{},
|
|
location: e + readFromLocationPath,
|
|
}
|
|
|
|
if blobUpload.Size() != 0 {
|
|
t.Fatalf("Wrong size returned from Size: %d, expected 0", blobUpload.Size())
|
|
}
|
|
|
|
_, err := blobUpload.ReadFrom(bytes.NewReader(b))
|
|
if err != nil {
|
|
t.Fatalf("Error calling ReadFrom: %s", err)
|
|
}
|
|
|
|
if blobUpload.Size() != 64 {
|
|
t.Fatalf("Wrong size returned from Size: %d, expected 64", blobUpload.Size())
|
|
}
|
|
|
|
// Writing with Write
|
|
blobUpload = &httpBlobUpload{
|
|
ctx: context.Background(),
|
|
client: &http.Client{},
|
|
location: e + writeLocationPath,
|
|
}
|
|
|
|
_, err = blobUpload.Write(b)
|
|
if err != nil {
|
|
t.Fatalf("Error calling Write: %s", err)
|
|
}
|
|
|
|
if blobUpload.Size() != 64 {
|
|
t.Fatalf("Wrong size returned from Size: %d, expected 64", blobUpload.Size())
|
|
}
|
|
}
|
|
|
|
func TestUploadWrite(t *testing.T) {
|
|
_, b := newRandomBlob(64)
|
|
repo := "test/upload/write"
|
|
locationPath := fmt.Sprintf("/v2/%s/uploads/testid", repo)
|
|
|
|
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodGet,
|
|
Route: "/v2/",
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusOK,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Distribution-API-Version": {"registry/2.0"},
|
|
}),
|
|
},
|
|
},
|
|
// Test Valid case
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {locationPath},
|
|
"Range": {"0-63"},
|
|
}),
|
|
},
|
|
},
|
|
// Test invalid range
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusAccepted,
|
|
Headers: http.Header(map[string][]string{
|
|
"Docker-Upload-UUID": {"46603072-7a1b-4b41-98f9-fd8a7da89f9b"},
|
|
"Location": {locationPath},
|
|
"Range": {""},
|
|
}),
|
|
},
|
|
},
|
|
// Test 404
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusNotFound,
|
|
},
|
|
},
|
|
// Test 400 valid json
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusBadRequest,
|
|
Headers: http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
|
|
Body: []byte(`
|
|
{ "errors":
|
|
[
|
|
{
|
|
"code": "BLOB_UPLOAD_INVALID",
|
|
"message": "blob upload invalid",
|
|
"detail": "more detail"
|
|
}
|
|
]
|
|
} `),
|
|
},
|
|
},
|
|
// Test 400 invalid json
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusBadRequest,
|
|
Headers: http.Header{"Content-Type": []string{"application/json"}},
|
|
Body: []byte("something bad happened"),
|
|
},
|
|
},
|
|
// Test 500
|
|
{
|
|
Request: testutil.Request{
|
|
Method: http.MethodPatch,
|
|
Route: locationPath,
|
|
Body: b,
|
|
},
|
|
Response: testutil.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
},
|
|
})
|
|
|
|
e, c := testServer(m)
|
|
defer c()
|
|
|
|
blobUpload := &httpBlobUpload{
|
|
ctx: context.Background(),
|
|
client: &http.Client{},
|
|
}
|
|
|
|
// Valid case
|
|
blobUpload.location = e + locationPath
|
|
n, err := blobUpload.Write(b)
|
|
if err != nil {
|
|
t.Fatalf("Error calling Write: %s", err)
|
|
}
|
|
if n != 64 {
|
|
t.Fatalf("Wrong length returned from Write: %d, expected 64", n)
|
|
}
|
|
|
|
// Bad range
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when bad range received")
|
|
}
|
|
|
|
// 404
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if err != distribution.ErrBlobUploadUnknown {
|
|
t.Fatalf("Wrong error thrown: %s, expected %s", err, distribution.ErrBlobUploadUnknown)
|
|
}
|
|
|
|
// 400 valid json
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(errcode.Errors); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else if len(uploadErr) != 1 {
|
|
t.Fatalf("Unexpected number of errors: %d, expected 1", len(uploadErr))
|
|
} else {
|
|
v2Err, ok := uploadErr[0].(errcode.Error)
|
|
if !ok {
|
|
t.Fatalf("Not an 'Error' type: %#v", uploadErr[0])
|
|
}
|
|
if v2Err.Code != v2.ErrorCodeBlobUploadInvalid {
|
|
t.Fatalf("Unexpected error code: %s, expected %d", v2Err.Code.String(), v2.ErrorCodeBlobUploadInvalid)
|
|
}
|
|
if expected := "blob upload invalid"; v2Err.Message != expected {
|
|
t.Fatalf("Unexpected error message: %q, expected %q", v2Err.Message, expected)
|
|
}
|
|
if expected := "more detail"; v2Err.Detail.(string) != expected {
|
|
t.Fatalf("Unexpected error message: %q, expected %q", v2Err.Detail.(string), expected)
|
|
}
|
|
}
|
|
|
|
// 400 invalid json
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(*UnexpectedHTTPResponseError); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else {
|
|
respStr := string(uploadErr.Response)
|
|
if expected := "something bad happened"; respStr != expected {
|
|
t.Fatalf("Unexpected response string: %s, expected: %s", respStr, expected)
|
|
}
|
|
}
|
|
|
|
// 500
|
|
blobUpload.location = e + locationPath
|
|
_, err = blobUpload.Write(b)
|
|
if err == nil {
|
|
t.Fatalf("Expected error when not found")
|
|
}
|
|
if uploadErr, ok := err.(*UnexpectedHTTPStatusError); !ok {
|
|
t.Fatalf("Wrong error type %T: %s", err, err)
|
|
} else if expected := "500 " + http.StatusText(http.StatusInternalServerError); uploadErr.Status != expected {
|
|
t.Fatalf("Unexpected response status: %s, expected %s", uploadErr.Status, expected)
|
|
}
|
|
}
|