Skip to content

Commit

Permalink
writable gateway: added tests from @ion1's RFC summary
Browse files Browse the repository at this point in the history
#1886 (comment)

License: MIT
Signed-off-by: Henry <cryptix@riseup.net>
  • Loading branch information
cryptix committed Oct 30, 2015
1 parent e9f9375 commit 70b14ec
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 4 deletions.
10 changes: 6 additions & 4 deletions core/corehttp/gateway_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,6 @@ func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) {
}

func (i *gatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) {
// effectivly unusable per rfc 7231
// https://github.com/ipfs/go-ipfs/pull/1886
// original discussion: gh/ipfs/go-ipfs/PR:617
webError(w, "putHandler: sorry, again - please use POST instead to overwrite ..?", err, http.StatusBadRequest)
rootPath, err := path.ParsePath(r.URL.Path)
if err != nil {
webError(w, "putHandler: ipfs path not valid", err, http.StatusBadRequest)
Expand Down Expand Up @@ -389,6 +385,12 @@ func (i *gatewayHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
return
}

// catches handler panic
if len(components) == 0 {
webError(w, "delete request not meaningful", fmt.Errorf("deleteHandler: empty path. %q", urlPath), http.StatusMethodNotAllowed)
return
}

pathNodes, err := i.node.Resolver.ResolveLinks(tctx, rootnd, components[:len(components)-1])
if err != nil {
webError(w, "Could not resolve parent object", err, http.StatusBadRequest)
Expand Down
162 changes: 162 additions & 0 deletions core/corehttp/gateway_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package corehttp

import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"

"github.com/ipfs/go-ipfs/assets"
"github.com/ipfs/go-ipfs/blocks/key"
"github.com/ipfs/go-ipfs/core"
"github.com/ipfs/go-ipfs/core/mock"

"github.com/cheekybits/is"
)

type testSession struct {
key *key.Key
mn *core.IpfsNode
hc http.Client
}

func newTestSession(t *testing.T, writable bool) *testSession {
mn, err := coremock.NewMockNode()
if err != nil {
t.Fatalf("coremock.NewMockNode() failed: %s", err)
}

tk, err := assets.SeedInitDocs(mn)
if err != nil {
t.Fatalf("assets.SeedInitDocs() failed: %s", err)
}

gwh, err := newGatewayHandler(mn, GatewayConfig{Writable: writable})
if err != nil {
t.Fatalf("newGatewayHandler() failed: %s", err)
}
serveMux := http.NewServeMux()
serveMux.Handle("/", gwh)

return &testSession{
key: tk,
mn: mn,
hc: http.Client{Transport: (*muxTransport)(serveMux)},
}
}

func TestGateway_GET(t *testing.T) {
ts := newTestSession(t, false)
is := is.New(t)
resp, err := ts.hc.Get("/ipfs/" + ts.key.B58String() + "/about")
is.Nil(err)
is.Equal(resp.StatusCode, http.StatusOK)
}

func TestGateway_POSTwDisabled(t *testing.T) {
ts := newTestSession(t, false)
is := is.New(t)
resp, err := ts.hc.Post("/ipfs/"+ts.key.B58String()+"/new", "test", nil)
is.Nil(err)
is.Equal(resp.StatusCode, http.StatusMethodNotAllowed)
}

func TestGateway_Meaningful(t *testing.T) {
ts := newTestSession(t, true)
tcases := []struct {
Method, URL string
StatusCode int
Body []byte
Location string
}{
// POST /ipfs creates a new resource under /ipfs
// whose name (in this case: hash) is determined by the gateway and receives the response:
{"POST", "/ipfs", http.StatusCreated, []byte("Hello World"), "/ipfs/QmUXTtySmd7LD4p6RG6rZW6RuUuPZXTtNMmRQ6DSQo3aMw"},

// TODO(cryptix): figure out how to specify the file/link name
{"POST", "/ipfs/" + ts.key.B58String(), http.StatusCreated, []byte("Hello World"), "/ipfs/QmUXTtySmd7LD4p6RG6rZW6RuUuPZXTtNMmRQ6DSQo3aMw"},

{"DELETE", "/ipfs/" + ts.key.B58String() + "/about", http.StatusCreated, []byte{}, "/ipfs/test"},
}
for i, tcase := range tcases {
req, err := http.NewRequest(tcase.Method, tcase.URL, bytes.NewReader(tcase.Body))
if err != nil {
t.Errorf("case %d NewRequest() failed: %s", i, err)
}
resp, err := ts.hc.Do(req)
if err != nil {
t.Errorf("case %d failed with error: %s", i, err)
}
if resp.StatusCode != tcase.StatusCode {
t.Errorf("case %d: status mismatch: want: %3d. got: %3d", i, tcase.StatusCode, resp.StatusCode)
if b, err := ioutil.ReadAll(resp.Body); err == nil && len(b) > 0 {
t.Logf("response body: %q", b)
}
}

if got := resp.Header.Get("Location"); got != tcase.Location {
t.Errorf("case %d: location mismatch: want: %s. got: %s", i, tcase.Location, got)
}
}
}

func TestGateway_NonMeaningful(t *testing.T) {
ts := newTestSession(t, true)
tcases := []struct {
Method, URL string
StatusCode int
}{
// PUT /ipfs is not meaningful (“I expect a future GET /ipfs to return this content”).
{"PUT", "/ipfs", http.StatusMethodNotAllowed},

// PUT /ipfs/QmFoo can only succeed if QmFoo is in fact the hash of the object being uploaded
// (requiring the client to compute the hash in advance).
{"PUT", "/ipfs/" + ts.key.B58String(), http.StatusMethodNotAllowed},

// PUT /ipfs/QmBar/baz is not meaningful.
// the gateway might not know anything about QmBar under which baz is requested to be created,
// and if it does know, QmBar already exists and is immutable.
{"PUT", "/ipfs/" + ts.key.B58String() + "/about", http.StatusMethodNotAllowed},

// DELETE /ipfs is not meaningful.
{"DELETE", "/ipfs", http.StatusMethodNotAllowed},

// DELETE /ipfs/QmFoo is not meaningful.
{"DELETE", "/ipfs/" + ts.key.B58String(), http.StatusMethodNotAllowed},
}

for i, tcase := range tcases {
req, err := http.NewRequest(tcase.Method, tcase.URL, bytes.NewReader([]byte{}))
if err != nil {
t.Errorf("case %d NewRequest() failed: %s", i, err)
}
resp, err := ts.hc.Do(req)
if err != nil {
t.Errorf("case %d failed with error: %s", i, err)
}
if resp.StatusCode != tcase.StatusCode {
t.Errorf("case %d: status mismatch: want: %3d. got: %3d", i, tcase.StatusCode, resp.StatusCode)
if b, err := ioutil.ReadAll(resp.Body); err == nil && len(b) > 0 {
t.Logf("response body: %q", b)
}
}
}
}

// muxTransport serves http requests without networking
type muxTransport http.ServeMux

func (t *muxTransport) RoundTrip(req *http.Request) (*http.Response, error) {
rw := httptest.NewRecorder()
rw.Body = new(bytes.Buffer)
(*http.ServeMux)(t).ServeHTTP(rw, req)
return &http.Response{
StatusCode: rw.Code,
Status: http.StatusText(rw.Code),
Header: rw.HeaderMap,
Body: ioutil.NopCloser(rw.Body),
ContentLength: int64(rw.Body.Len()),
Request: req,
}, nil
}

0 comments on commit 70b14ec

Please sign in to comment.