f14f2f4863
Up to now, we hardcoded progress to "false" in the proxy, regardless of what the original request said. We now leave it as it is, and just ignore any progress updates when processing the response. Since the response is buffered and sent back all together, they are still useless, but at least the clients (ipfs cli) won't show a 0% progress bar when successfully adding a file. License: MIT Signed-off-by: Hector Sanjuan <hector@protocol.ai>
610 lines
13 KiB
Go
610 lines
13 KiB
Go
package ipfshttp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ipfs/ipfs-cluster/api"
|
|
"github.com/ipfs/ipfs-cluster/test"
|
|
|
|
cid "github.com/ipfs/go-cid"
|
|
logging "github.com/ipfs/go-log"
|
|
ma "github.com/multiformats/go-multiaddr"
|
|
manet "github.com/multiformats/go-multiaddr-net"
|
|
)
|
|
|
|
func init() {
|
|
_ = logging.Logger
|
|
}
|
|
|
|
func testIPFSConnector(t *testing.T) (*Connector, *test.IpfsMock) {
|
|
mock := test.NewIpfsMock()
|
|
nodeMAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d",
|
|
mock.Addr, mock.Port))
|
|
proxyMAddr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/10001")
|
|
|
|
cfg := &Config{}
|
|
cfg.Default()
|
|
cfg.NodeAddr = nodeMAddr
|
|
cfg.ProxyAddr = proxyMAddr
|
|
cfg.ConnectSwarmsDelay = 0
|
|
|
|
ipfs, err := NewConnector(cfg)
|
|
if err != nil {
|
|
t.Fatal("creating an IPFSConnector should work: ", err)
|
|
}
|
|
ipfs.SetClient(test.NewMockRPCClient(t))
|
|
return ipfs, mock
|
|
}
|
|
|
|
func TestNewConnector(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
}
|
|
|
|
func TestIPFSID(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer ipfs.Shutdown()
|
|
id, err := ipfs.ID()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if id.ID != test.TestPeerID1 {
|
|
t.Error("expected testPeerID")
|
|
}
|
|
if len(id.Addresses) != 1 {
|
|
t.Error("expected 1 address")
|
|
}
|
|
if id.Error != "" {
|
|
t.Error("expected no error")
|
|
}
|
|
mock.Close()
|
|
id, err = ipfs.ID()
|
|
if err == nil {
|
|
t.Error("expected an error")
|
|
}
|
|
if id.Error != err.Error() {
|
|
t.Error("error messages should match")
|
|
}
|
|
}
|
|
|
|
func TestIPFSPin(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
c, _ := cid.Decode(test.TestCid1)
|
|
err := ipfs.Pin(c)
|
|
if err != nil {
|
|
t.Error("expected success pinning cid")
|
|
}
|
|
pinSt, err := ipfs.PinLsCid(c)
|
|
if err != nil {
|
|
t.Fatal("expected success doing ls")
|
|
}
|
|
if !pinSt.IsPinned() {
|
|
t.Error("cid should have been pinned")
|
|
}
|
|
|
|
c2, _ := cid.Decode(test.ErrorCid)
|
|
err = ipfs.Pin(c2)
|
|
if err == nil {
|
|
t.Error("expected error pinning cid")
|
|
}
|
|
}
|
|
|
|
func TestIPFSUnpin(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
c, _ := cid.Decode(test.TestCid1)
|
|
err := ipfs.Unpin(c)
|
|
if err != nil {
|
|
t.Error("expected success unpinning non-pinned cid")
|
|
}
|
|
ipfs.Pin(c)
|
|
err = ipfs.Unpin(c)
|
|
if err != nil {
|
|
t.Error("expected success unpinning pinned cid")
|
|
}
|
|
}
|
|
|
|
func TestIPFSPinLsCid(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
c, _ := cid.Decode(test.TestCid1)
|
|
c2, _ := cid.Decode(test.TestCid2)
|
|
|
|
ipfs.Pin(c)
|
|
ips, err := ipfs.PinLsCid(c)
|
|
if err != nil || !ips.IsPinned() {
|
|
t.Error("c should appear pinned")
|
|
}
|
|
|
|
ips, err = ipfs.PinLsCid(c2)
|
|
if err != nil || ips != api.IPFSPinStatusUnpinned {
|
|
t.Error("c2 should appear unpinned")
|
|
}
|
|
}
|
|
|
|
func TestIPFSPinLs(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
c, _ := cid.Decode(test.TestCid1)
|
|
c2, _ := cid.Decode(test.TestCid2)
|
|
|
|
ipfs.Pin(c)
|
|
ipfs.Pin(c2)
|
|
ipsMap, err := ipfs.PinLs("")
|
|
if err != nil {
|
|
t.Error("should not error")
|
|
}
|
|
|
|
if len(ipsMap) != 2 {
|
|
t.Fatal("the map does not contain expected keys")
|
|
}
|
|
|
|
if !ipsMap[test.TestCid1].IsPinned() || !ipsMap[test.TestCid2].IsPinned() {
|
|
t.Error("c1 and c2 should appear pinned")
|
|
}
|
|
}
|
|
|
|
func TestIPFSProxyVersion(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
|
|
res, err := http.Post(fmt.Sprintf("%s/version", proxyURL(ipfs)), "", nil)
|
|
if err != nil {
|
|
t.Fatal("should forward requests to ipfs host: ", err)
|
|
}
|
|
if res.StatusCode != http.StatusOK {
|
|
t.Error("the request should have succeeded")
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
resBytes, _ := ioutil.ReadAll(res.Body)
|
|
|
|
var resp struct {
|
|
Version string
|
|
}
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if resp.Version != "m.o.c.k" {
|
|
t.Error("wrong version")
|
|
}
|
|
}
|
|
|
|
func TestIPFSProxyPin(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
|
|
res, err := http.Post(fmt.Sprintf("%s/pin/add?arg=%s", proxyURL(ipfs), test.TestCid1), "", nil)
|
|
if err != nil {
|
|
t.Fatal("should have succeeded: ", err)
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
t.Error("the request should have succeeded")
|
|
}
|
|
resBytes, _ := ioutil.ReadAll(res.Body)
|
|
|
|
var resp ipfsPinOpResp
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(resp.Pins) != 1 || resp.Pins[0] != test.TestCid1 {
|
|
t.Error("wrong response")
|
|
}
|
|
|
|
// Try with a bad cid
|
|
res2, err := http.Post(fmt.Sprintf("%s/pin/add?arg=%s", proxyURL(ipfs), test.ErrorCid), "", nil)
|
|
if err != nil {
|
|
t.Fatal("request should work: ", err)
|
|
}
|
|
defer res2.Body.Close()
|
|
|
|
t.Log(fmt.Sprintf("%s/pin/add?arg=%s", proxyURL(ipfs), test.ErrorCid))
|
|
if res2.StatusCode != http.StatusInternalServerError {
|
|
t.Error("the request should return with InternalServerError")
|
|
}
|
|
|
|
resBytes, _ = ioutil.ReadAll(res2.Body)
|
|
var respErr ipfsError
|
|
err = json.Unmarshal(resBytes, &respErr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if respErr.Message != test.ErrBadCid.Error() {
|
|
t.Error("wrong response")
|
|
}
|
|
}
|
|
|
|
func TestIPFSProxyUnpin(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
|
|
res, err := http.Post(fmt.Sprintf("%s/pin/rm?arg=%s", proxyURL(ipfs), test.TestCid1), "", nil)
|
|
if err != nil {
|
|
t.Fatal("should have succeeded: ", err)
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
t.Error("the request should have succeeded")
|
|
}
|
|
|
|
resBytes, _ := ioutil.ReadAll(res.Body)
|
|
|
|
var resp ipfsPinOpResp
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(resp.Pins) != 1 || resp.Pins[0] != test.TestCid1 {
|
|
t.Error("wrong response")
|
|
}
|
|
|
|
// Try with a bad cid
|
|
res2, err := http.Post(fmt.Sprintf("%s/pin/rm?arg=%s", proxyURL(ipfs), test.ErrorCid), "", nil)
|
|
if err != nil {
|
|
t.Fatal("request should work: ", err)
|
|
}
|
|
defer res2.Body.Close()
|
|
|
|
if res2.StatusCode != http.StatusInternalServerError {
|
|
t.Error("the request should return with InternalServerError")
|
|
}
|
|
|
|
resBytes, _ = ioutil.ReadAll(res2.Body)
|
|
var respErr ipfsError
|
|
err = json.Unmarshal(resBytes, &respErr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if respErr.Message != test.ErrBadCid.Error() {
|
|
t.Error("wrong response")
|
|
}
|
|
}
|
|
|
|
func TestIPFSProxyPinLs(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
|
|
res, err := http.Post(fmt.Sprintf("%s/pin/ls?arg=%s", proxyURL(ipfs), test.TestCid1), "", nil)
|
|
if err != nil {
|
|
t.Fatal("should have succeeded: ", err)
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
t.Error("the request should have succeeded")
|
|
}
|
|
|
|
resBytes, _ := ioutil.ReadAll(res.Body)
|
|
|
|
var resp ipfsPinLsResp
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, ok := resp.Keys[test.TestCid1]
|
|
if len(resp.Keys) != 1 || !ok {
|
|
t.Error("wrong response")
|
|
}
|
|
|
|
res2, err := http.Post(fmt.Sprintf("%s/pin/ls", proxyURL(ipfs)), "", nil)
|
|
if err != nil {
|
|
t.Fatal("should have succeeded: ", err)
|
|
}
|
|
defer res2.Body.Close()
|
|
if res2.StatusCode != http.StatusOK {
|
|
t.Error("the request should have succeeded")
|
|
}
|
|
|
|
resBytes, _ = ioutil.ReadAll(res2.Body)
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(resp.Keys) != 3 {
|
|
t.Error("wrong response")
|
|
}
|
|
|
|
res3, err := http.Post(fmt.Sprintf("%s/pin/ls?arg=%s", proxyURL(ipfs), test.ErrorCid), "", nil)
|
|
if err != nil {
|
|
t.Fatal("should have succeeded: ", err)
|
|
}
|
|
defer res3.Body.Close()
|
|
if res3.StatusCode != http.StatusInternalServerError {
|
|
t.Error("the request should have failed")
|
|
}
|
|
}
|
|
|
|
func TestProxyAdd(t *testing.T) {
|
|
// TODO: find a way to ensure that the calls to
|
|
// rpc-api "Pin" happened.
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
|
|
urlQueries := []string{
|
|
"",
|
|
"pin=false",
|
|
"progress=true",
|
|
"wrap-with-directory",
|
|
}
|
|
|
|
reqs := make([]*http.Request, len(urlQueries), len(urlQueries))
|
|
|
|
for i := 0; i < len(urlQueries); i++ {
|
|
body := new(bytes.Buffer)
|
|
w := multipart.NewWriter(body)
|
|
part, err := w.CreateFormFile("file", "testfile")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = part.Write([]byte("this is a multipart file"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = w.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
url := fmt.Sprintf("%s/add?"+urlQueries[i], proxyURL(ipfs))
|
|
req, err := http.NewRequest("POST", url, body)
|
|
req.Header.Set("Content-Type", w.FormDataContentType())
|
|
reqs[i] = req
|
|
}
|
|
|
|
for i := 0; i < len(urlQueries); i++ {
|
|
res, err := http.DefaultClient.Do(reqs[i])
|
|
if err != nil {
|
|
t.Fatal("should have succeeded: ", err)
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
t.Fatal("Bad response status")
|
|
}
|
|
|
|
var hash ipfsAddResp
|
|
|
|
// We might return a progress notification, so we do it
|
|
// like this to ignore it easily
|
|
dec := json.NewDecoder(res.Body)
|
|
for dec.More() {
|
|
var resp ipfsAddResp
|
|
err := dec.Decode(&resp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if resp.Bytes != 0 {
|
|
continue
|
|
} else {
|
|
hash = resp
|
|
}
|
|
}
|
|
|
|
if hash.Hash != test.TestCid3 {
|
|
t.Logf("%+v", hash)
|
|
t.Error("expected TestCid1 as it is hardcoded in ipfs mock")
|
|
}
|
|
if hash.Name != "testfile" {
|
|
t.Logf("%+v", hash)
|
|
t.Error("expected testfile for hash name")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProxyAddError(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
res, err := http.Post(fmt.Sprintf("%s/add?recursive=true", proxyURL(ipfs)), "", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res.Body.Close()
|
|
if res.StatusCode != http.StatusInternalServerError {
|
|
t.Log(res.StatusCode)
|
|
t.Error("expected an error")
|
|
}
|
|
}
|
|
|
|
func TestDecideRecursivePins(t *testing.T) {
|
|
type testcases struct {
|
|
addResps []ipfsAddResp
|
|
query url.Values
|
|
expect []string
|
|
}
|
|
|
|
tcs := []testcases{
|
|
{
|
|
[]ipfsAddResp{{"a", "cida", 0}},
|
|
url.Values{},
|
|
[]string{"cida"},
|
|
},
|
|
{
|
|
[]ipfsAddResp{{"a/b", "cidb", 0}, {"a", "cida", 0}},
|
|
url.Values{},
|
|
[]string{"cida"},
|
|
},
|
|
{
|
|
[]ipfsAddResp{{"a/b", "cidb", 0}, {"c", "cidc", 0}, {"a", "cida", 0}},
|
|
url.Values{},
|
|
[]string{"cidc", "cida"},
|
|
},
|
|
{
|
|
[]ipfsAddResp{{"/a", "cida", 0}},
|
|
url.Values{},
|
|
[]string{"cida"},
|
|
},
|
|
{
|
|
[]ipfsAddResp{{"a/b/c/d", "cidd", 0}},
|
|
url.Values{},
|
|
[]string{"cidd"},
|
|
},
|
|
{
|
|
[]ipfsAddResp{{"a", "cida", 0}, {"b", "cidb", 0}, {"c", "cidc", 0}, {"d", "cidd", 0}},
|
|
url.Values{},
|
|
[]string{"cida", "cidb", "cidc", "cidd"},
|
|
},
|
|
{
|
|
[]ipfsAddResp{{"a", "cida", 0}, {"b", "cidb", 0}, {"", "cidwrap", 0}},
|
|
url.Values{
|
|
"wrap-in-directory": []string{"true"},
|
|
},
|
|
[]string{"cidwrap"},
|
|
},
|
|
{
|
|
|
|
[]ipfsAddResp{{"b", "", 0}, {"a", "cida", 0}},
|
|
url.Values{},
|
|
[]string{"cida"},
|
|
},
|
|
}
|
|
|
|
for i, tc := range tcs {
|
|
r := decideRecursivePins(tc.addResps, tc.query)
|
|
for j, ritem := range r {
|
|
if len(r) != len(tc.expect) {
|
|
t.Errorf("testcase %d failed", i)
|
|
break
|
|
}
|
|
if tc.expect[j] != ritem {
|
|
t.Errorf("testcase %d failed for item %d", i, j)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProxyError(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
|
|
res, err := http.Post(fmt.Sprintf("%s/bad/command", proxyURL(ipfs)), "", nil)
|
|
if err != nil {
|
|
t.Fatal("should have succeeded: ", err)
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != 404 {
|
|
t.Error("should have respected the status code")
|
|
}
|
|
}
|
|
|
|
func TestIPFSShutdown(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
if err := ipfs.Shutdown(); err != nil {
|
|
t.Error("expected a clean shutdown")
|
|
}
|
|
if err := ipfs.Shutdown(); err != nil {
|
|
t.Error("expected a second clean shutdown")
|
|
}
|
|
}
|
|
|
|
func TestConnectSwarms(t *testing.T) {
|
|
// In order to interactively test uncomment the following.
|
|
// Otherwise there is no good way to test this with the
|
|
// ipfs mock
|
|
// logging.SetDebugLogging()
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
func TestRepoSize(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
|
|
s, err := ipfs.RepoSize()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// See the ipfs mock implementation
|
|
if s != 0 {
|
|
t.Error("expected 0 bytes of size")
|
|
}
|
|
|
|
c, _ := cid.Decode(test.TestCid1)
|
|
err = ipfs.Pin(c)
|
|
if err != nil {
|
|
t.Error("expected success pinning cid")
|
|
}
|
|
|
|
s, err = ipfs.RepoSize()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s != 1000 {
|
|
t.Error("expected 1000 bytes of size")
|
|
}
|
|
}
|
|
|
|
func TestConfigKey(t *testing.T) {
|
|
ipfs, mock := testIPFSConnector(t)
|
|
defer mock.Close()
|
|
defer ipfs.Shutdown()
|
|
|
|
v, err := ipfs.ConfigKey("Datastore/StorageMax")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sto, ok := v.(string)
|
|
if !ok {
|
|
t.Fatal("error converting to string")
|
|
}
|
|
if sto != "10G" {
|
|
t.Error("StorageMax shouold be 10G")
|
|
}
|
|
|
|
v, err = ipfs.ConfigKey("Datastore")
|
|
_, ok = v.(map[string]interface{})
|
|
if !ok {
|
|
t.Error("should have returned the whole Datastore config object")
|
|
}
|
|
|
|
_, err = ipfs.ConfigKey("")
|
|
if err == nil {
|
|
t.Error("should not work with an empty path")
|
|
}
|
|
|
|
_, err = ipfs.ConfigKey("Datastore/abc")
|
|
if err == nil {
|
|
t.Error("should not work with a bad path")
|
|
}
|
|
}
|
|
|
|
func proxyURL(c *Connector) string {
|
|
_, addr, _ := manet.DialArgs(c.config.ProxyAddr)
|
|
return fmt.Sprintf("http://%s/api/v0", addr)
|
|
}
|