cuelabs.dev/go/oci/ociregistry@v0.0.0-20240906074133-82eb438dd565/error_test.go (about)

     1  package ociregistry
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"testing"
     9  
    10  	"github.com/go-quicktest/qt"
    11  )
    12  
    13  var errorTests = []struct {
    14  	testName              string
    15  	err                   error
    16  	wantMsg               string
    17  	wantMarshalData       rawJSONMessage
    18  	wantMarshalHTTPStatus int
    19  }{{
    20  	testName:              "RegularGoError",
    21  	err:                   fmt.Errorf("unknown error"),
    22  	wantMsg:               "unknown error",
    23  	wantMarshalData:       `{"errors":[{"code":"UNKNOWN","message":"unknown error"}]}`,
    24  	wantMarshalHTTPStatus: http.StatusInternalServerError,
    25  }, {
    26  	testName:              "RegistryError",
    27  	err:                   ErrBlobUnknown,
    28  	wantMsg:               "blob unknown: blob unknown to registry",
    29  	wantMarshalData:       `{"errors":[{"code":"BLOB_UNKNOWN","message":"blob unknown to registry"}]}`,
    30  	wantMarshalHTTPStatus: http.StatusNotFound,
    31  }, {
    32  	testName:              "WrappedRegistryErrorWithContextAtStart",
    33  	err:                   fmt.Errorf("some context: %w", ErrBlobUnknown),
    34  	wantMsg:               "some context: blob unknown: blob unknown to registry",
    35  	wantMarshalData:       `{"errors":[{"code":"BLOB_UNKNOWN","message":"some context: blob unknown: blob unknown to registry"}]}`,
    36  	wantMarshalHTTPStatus: http.StatusNotFound,
    37  }, {
    38  	testName:              "WrappedRegistryErrorWithContextAtEnd",
    39  	err:                   fmt.Errorf("%w: some context", ErrBlobUnknown),
    40  	wantMsg:               "blob unknown: blob unknown to registry: some context",
    41  	wantMarshalData:       `{"errors":[{"code":"BLOB_UNKNOWN","message":"blob unknown to registry: some context"}]}`,
    42  	wantMarshalHTTPStatus: http.StatusNotFound,
    43  }, {
    44  	testName: "HTTPStatusIgnoredWithKnownCode",
    45  	err:      NewHTTPError(fmt.Errorf("%w: some context", ErrBlobUnknown), http.StatusUnauthorized, nil, nil),
    46  	wantMsg:  "401 Unauthorized: blob unknown: blob unknown to registry: some context",
    47  	// Note: the "401 Unauthorized" remains intact because it's not redundant with respect
    48  	// to the 404 HTTP response code.
    49  	wantMarshalData:       `{"errors":[{"code":"BLOB_UNKNOWN","message":"401 Unauthorized: blob unknown: blob unknown to registry: some context"}]}`,
    50  	wantMarshalHTTPStatus: http.StatusNotFound,
    51  }, {
    52  	testName:              "HTTPStatusUsedWithUnknownCode",
    53  	err:                   NewHTTPError(NewError("a message with a code", "SOME_CODE", nil), http.StatusUnauthorized, nil, nil),
    54  	wantMsg:               "401 Unauthorized: some code: a message with a code",
    55  	wantMarshalData:       `{"errors":[{"code":"SOME_CODE","message":"a message with a code"}]}`,
    56  	wantMarshalHTTPStatus: http.StatusUnauthorized,
    57  }, {
    58  	testName:              "ErrorWithDetail",
    59  	err:                   NewError("a message with some detail", "SOME_CODE", json.RawMessage(`{"foo": true}`)),
    60  	wantMsg:               `some code: a message with some detail`,
    61  	wantMarshalData:       `{"errors":[{"code":"SOME_CODE","message":"a message with some detail","detail":{"foo":true}}]}`,
    62  	wantMarshalHTTPStatus: http.StatusInternalServerError,
    63  }}
    64  
    65  func TestError(t *testing.T) {
    66  	for _, test := range errorTests {
    67  		t.Run(test.testName, func(t *testing.T) {
    68  			qt.Check(t, qt.ErrorMatches(test.err, test.wantMsg))
    69  			data, httpStatus := MarshalError(test.err)
    70  			qt.Check(t, qt.Equals(httpStatus, test.wantMarshalHTTPStatus))
    71  			qt.Check(t, qt.JSONEquals(data, test.wantMarshalData), qt.Commentf("marshal data: %s", data))
    72  
    73  			// Check that the marshaled error unmarshals into WireError OK and
    74  			// that the code matches appropriately.
    75  			var errs *WireErrors
    76  			err := json.Unmarshal(data, &errs)
    77  			qt.Assert(t, qt.IsNil(err))
    78  			if ociErr := Error(nil); errors.As(test.err, &ociErr) {
    79  				qt.Assert(t, qt.IsTrue(errors.Is(errs, NewError("something", ociErr.Code(), nil))))
    80  			}
    81  		})
    82  	}
    83  }
    84  
    85  type rawJSONMessage string
    86  
    87  func (m rawJSONMessage) MarshalJSON() ([]byte, error) {
    88  	return []byte(m), nil
    89  }
    90  
    91  func (m *rawJSONMessage) UnmarshalJSON(data []byte) error {
    92  	*m = rawJSONMessage(data)
    93  	return nil
    94  }