Implement verify failure handling

This commit is contained in:
risk danger olson 2016-12-16 15:43:05 -07:00
parent a4ab24dca6
commit e1b4563815
3 changed files with 325 additions and 2 deletions

79
lfsapi/errors.go Normal file

@ -0,0 +1,79 @@
package lfsapi
import (
"fmt"
"net/http"
"github.com/git-lfs/git-lfs/errors"
)
type ClientError struct {
Message string `json:"message"`
DocumentationUrl string `json:"documentation_url,omitempty"`
RequestId string `json:"request_id,omitempty"`
}
func (e *ClientError) Error() string {
msg := e.Message
if len(e.DocumentationUrl) > 0 {
msg += "\nDocs: " + e.DocumentationUrl
}
if len(e.RequestId) > 0 {
msg += "\nRequest ID: " + e.RequestId
}
return msg
}
func (c *Client) handleResponse(res *http.Response) error {
if res.StatusCode < 400 {
return nil
}
cliErr := &ClientError{}
err := decodeResponse(res, cliErr)
if err == nil {
if len(cliErr.Message) == 0 {
err = defaultError(res)
} else {
err = errors.Wrap(cliErr, "http")
}
}
if res.StatusCode == 401 {
return errors.NewAuthError(err)
}
if res.StatusCode > 499 && res.StatusCode != 501 && res.StatusCode != 507 && res.StatusCode != 509 {
return errors.NewFatalError(err)
}
return err
}
var (
defaultErrors = map[int]string{
400: "Client error: %s",
401: "Authorization error: %s\nCheck that you have proper access to the repository",
403: "Authorization error: %s\nCheck that you have proper access to the repository",
404: "Repository or object not found: %s\nCheck that it exists and that you have proper access to it",
429: "Rate limit exceeded: %s",
500: "Server error: %s",
501: "Not Implemented: %s",
507: "Insufficient server storage: %s",
509: "Bandwidth limit exceeded: %s",
}
)
func defaultError(res *http.Response) error {
var msgFmt string
if f, ok := defaultErrors[res.StatusCode]; ok {
msgFmt = f
} else if res.StatusCode < 500 {
msgFmt = defaultErrors[400] + fmt.Sprintf(" from HTTP %d", res.StatusCode)
} else {
msgFmt = defaultErrors[500] + fmt.Sprintf(" from HTTP %d", res.StatusCode)
}
return errors.Errorf(msgFmt, res.Request.URL)
}

@ -1,10 +1,42 @@
package lfsapi
import "net/http"
import (
"encoding/json"
"net/http"
"regexp"
"github.com/pkg/errors"
)
var (
lfsMediaTypeRE = regexp.MustCompile(`\Aapplication/vnd\.git\-lfs\+json(;|\z)`)
jsonMediaTypeRE = regexp.MustCompile(`\Aapplication/json(;|\z)`)
)
type Client struct {
}
func (c *Client) Do(req *http.Request) (*http.Response, error) {
return http.DefaultClient.Do(req)
res, err := http.DefaultClient.Do(req)
if err != nil {
return res, err
}
return res, c.handleResponse(res)
}
func decodeResponse(res *http.Response, obj interface{}) error {
ctype := res.Header.Get("Content-Type")
if !(lfsMediaTypeRE.MatchString(ctype) || jsonMediaTypeRE.MatchString(ctype)) {
return nil
}
err := json.NewDecoder(res.Body).Decode(obj)
res.Body.Close()
if err != nil {
return errors.Wrapf(err, "Unable to parse HTTP response for %s %s", res.Request.Method, res.Request.URL)
}
return nil
}

@ -4,9 +4,11 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/lfsapi"
"github.com/stretchr/testify/assert"
)
@ -60,3 +62,213 @@ func TestVerifySuccess(t *testing.T) {
assert.Nil(t, verifyUpload(c, tr))
assert.EqualValues(t, 1, called)
}
func TestVerifyAuthErrWithBody(t *testing.T) {
var called uint32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() != "/verify" {
w.WriteHeader(http.StatusNotFound)
return
}
atomic.AddUint32(&called, 1)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(401)
w.Write([]byte(`{"message":"custom auth error"}`))
}))
defer srv.Close()
c := &lfsapi.Client{}
tr := &Transfer{
Oid: "abc",
Size: 123,
Actions: map[string]*Action{
"verify": &Action{
Href: srv.URL + "/verify",
},
},
}
err := verifyUpload(c, tr)
assert.NotNil(t, err)
assert.True(t, errors.IsAuthError(err))
assert.Equal(t, "Authentication required: http: custom auth error", err.Error())
assert.EqualValues(t, 1, called)
}
func TestVerifyFatalWithBody(t *testing.T) {
var called uint32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() != "/verify" {
w.WriteHeader(http.StatusNotFound)
return
}
atomic.AddUint32(&called, 1)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
w.Write([]byte(`{"message":"custom fatal error"}`))
}))
defer srv.Close()
c := &lfsapi.Client{}
tr := &Transfer{
Oid: "abc",
Size: 123,
Actions: map[string]*Action{
"verify": &Action{
Href: srv.URL + "/verify",
},
},
}
err := verifyUpload(c, tr)
assert.NotNil(t, err)
assert.True(t, errors.IsFatalError(err))
assert.Equal(t, "Fatal error: http: custom fatal error", err.Error())
assert.EqualValues(t, 1, called)
}
func TestVerifyWithNonFatal500WithBody(t *testing.T) {
c := &lfsapi.Client{}
tr := &Transfer{
Oid: "abc",
Size: 123,
Actions: map[string]*Action{
"verify": &Action{},
},
}
var called uint32
nonFatalCodes := map[int]string{
501: "custom 501 error",
507: "custom 507 error",
509: "custom 509 error",
}
for nonFatalCode, expectedErr := range nonFatalCodes {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() != "/verify" {
w.WriteHeader(http.StatusNotFound)
return
}
atomic.AddUint32(&called, 1)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(nonFatalCode)
w.Write([]byte(`{"message":"` + expectedErr + `"}`))
}))
tr.Actions["verify"].Href = srv.URL + "/verify"
err := verifyUpload(c, tr)
t.Logf("non fatal code %d", nonFatalCode)
assert.NotNil(t, err)
assert.Equal(t, "http: "+expectedErr, err.Error())
srv.Close()
}
assert.EqualValues(t, 3, called)
}
func TestVerifyAuthErrWithoutBody(t *testing.T) {
var called uint32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() != "/verify" {
w.WriteHeader(http.StatusNotFound)
return
}
atomic.AddUint32(&called, 1)
w.WriteHeader(401)
}))
defer srv.Close()
c := &lfsapi.Client{}
tr := &Transfer{
Oid: "abc",
Size: 123,
Actions: map[string]*Action{
"verify": &Action{
Href: srv.URL + "/verify",
},
},
}
err := verifyUpload(c, tr)
assert.NotNil(t, err)
assert.True(t, errors.IsAuthError(err))
assert.True(t, strings.HasPrefix(err.Error(), "Authentication required: Authorization error:"), err.Error())
assert.EqualValues(t, 1, called)
}
func TestVerifyFatalWithoutBody(t *testing.T) {
var called uint32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() != "/verify" {
w.WriteHeader(http.StatusNotFound)
return
}
atomic.AddUint32(&called, 1)
w.WriteHeader(500)
}))
defer srv.Close()
c := &lfsapi.Client{}
tr := &Transfer{
Oid: "abc",
Size: 123,
Actions: map[string]*Action{
"verify": &Action{
Href: srv.URL + "/verify",
},
},
}
err := verifyUpload(c, tr)
assert.NotNil(t, err)
assert.True(t, errors.IsFatalError(err))
assert.True(t, strings.HasPrefix(err.Error(), "Fatal error: Server error:"), err.Error())
assert.EqualValues(t, 1, called)
}
func TestVerifyWithNonFatal500WithoutBody(t *testing.T) {
c := &lfsapi.Client{}
tr := &Transfer{
Oid: "abc",
Size: 123,
Actions: map[string]*Action{
"verify": &Action{},
},
}
var called uint32
nonFatalCodes := map[int]string{
501: "Not Implemented:",
507: "Insufficient server storage:",
509: "Bandwidth limit exceeded:",
}
for nonFatalCode, errPrefix := range nonFatalCodes {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.String() != "/verify" {
w.WriteHeader(http.StatusNotFound)
return
}
atomic.AddUint32(&called, 1)
w.WriteHeader(nonFatalCode)
}))
tr.Actions["verify"].Href = srv.URL + "/verify"
err := verifyUpload(c, tr)
t.Logf("non fatal code %d", nonFatalCode)
assert.NotNil(t, err)
assert.True(t, strings.HasPrefix(err.Error(), errPrefix))
srv.Close()
}
assert.EqualValues(t, 3, called)
}