ipfs-cluster/test/ipfs_mock.go
Hector Sanjuan f14f2f4863 Fix #224: Better handling of progress updates when proxy-adding file
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>
2017-11-13 14:09:20 +01:00

248 lines
4.6 KiB
Go

package test
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"github.com/ipfs/ipfs-cluster/api"
"github.com/ipfs/ipfs-cluster/state/mapstate"
cid "github.com/ipfs/go-cid"
)
// IpfsMock is an ipfs daemon mock which should sustain the functionality used by ipfscluster.
type IpfsMock struct {
server *httptest.Server
Addr string
Port int
pinMap *mapstate.MapState
}
type mockPinResp struct {
Pins []string
}
type mockPinType struct {
Type string
}
type mockPinLsResp struct {
Keys map[string]mockPinType
}
type ipfsErr struct {
Code int
Message string
}
type mockIDResp struct {
ID string
Addresses []string
}
type mockRepoStatResp struct {
RepoSize uint64
NumObjects uint64
StorageMax uint64
}
type mockConfigResp struct {
Datastore struct {
StorageMax string
}
}
type mockAddResp struct {
Name string
Hash string
Bytes uint64
}
// NewIpfsMock returns a new mock.
func NewIpfsMock() *IpfsMock {
st := mapstate.NewMapState()
m := &IpfsMock{
pinMap: st,
}
ts := httptest.NewServer(http.HandlerFunc(m.handler))
m.server = ts
url, _ := url.Parse(ts.URL)
h := strings.Split(url.Host, ":")
i, _ := strconv.Atoi(h[1])
m.Port = i
m.Addr = h[0]
return m
}
// FIXME: what if IPFS API changes?
func (m *IpfsMock) handler(w http.ResponseWriter, r *http.Request) {
p := r.URL.Path
endp := strings.TrimPrefix(p, "/api/v0/")
var cidStr string
switch endp {
case "id":
resp := mockIDResp{
ID: TestPeerID1.Pretty(),
Addresses: []string{
"/ip4/0.0.0.0/tcp/1234",
},
}
j, _ := json.Marshal(resp)
w.Write(j)
case "add":
c, _ := cid.Decode(TestCid3)
// add also pins
m.pinMap.Add(api.PinCid(c))
_, fheader, err := r.FormFile("file")
if err != nil {
http.Error(w, "no file in /add", 500)
return
}
query := r.URL.Query()
progress, ok := query["progress"]
if ok && len(progress) > 0 && progress[0] != "false" {
progressResp := mockAddResp{
Name: fheader.Filename,
Bytes: 4,
}
j, _ := json.Marshal(progressResp)
w.Write(j)
}
resp := mockAddResp{
Name: fheader.Filename,
Hash: TestCid3,
}
j, _ := json.Marshal(resp)
w.Write(j)
case "pin/add":
query := r.URL.Query()
arg, ok := query["arg"]
if !ok || len(arg) != 1 {
goto ERROR
}
cidStr = arg[0]
if cidStr == ErrorCid {
goto ERROR
}
c, err := cid.Decode(cidStr)
if err != nil {
goto ERROR
}
m.pinMap.Add(api.PinCid(c))
resp := mockPinResp{
Pins: []string{cidStr},
}
j, _ := json.Marshal(resp)
w.Write(j)
case "pin/rm":
query := r.URL.Query()
arg, ok := query["arg"]
if !ok || len(arg) != 1 {
goto ERROR
}
cidStr = arg[0]
c, err := cid.Decode(cidStr)
if err != nil {
goto ERROR
}
m.pinMap.Rm(c)
resp := mockPinResp{
Pins: []string{cidStr},
}
j, _ := json.Marshal(resp)
w.Write(j)
case "pin/ls":
query := r.URL.Query()
arg, ok := query["arg"]
if !ok {
rMap := make(map[string]mockPinType)
pins := m.pinMap.List()
for _, p := range pins {
rMap[p.Cid.String()] = mockPinType{"recursive"}
}
j, _ := json.Marshal(mockPinLsResp{rMap})
w.Write(j)
break
}
if len(arg) != 1 {
goto ERROR
}
cidStr = arg[0]
c, err := cid.Decode(cidStr)
if err != nil {
goto ERROR
}
ok = m.pinMap.Has(c)
if ok {
rMap := make(map[string]mockPinType)
rMap[cidStr] = mockPinType{"recursive"}
j, _ := json.Marshal(mockPinLsResp{rMap})
w.Write(j)
} else {
w.WriteHeader(http.StatusInternalServerError)
resp := ipfsErr{0, fmt.Sprintf("Path '%s' is not pinned", cidStr)}
j, _ := json.Marshal(resp)
w.Write(j)
}
case "swarm/connect":
query := r.URL.Query()
arg, ok := query["arg"]
if !ok {
goto ERROR
}
addr := arg[0]
splits := strings.Split(addr, "/")
pid := splits[len(splits)-1]
resp := struct {
Strings []string
}{
Strings: []string{fmt.Sprintf("connect %s success", pid)},
}
j, _ := json.Marshal(resp)
w.Write(j)
case "repo/stat":
len := len(m.pinMap.List())
resp := mockRepoStatResp{
RepoSize: uint64(len) * 1000,
NumObjects: uint64(len),
StorageMax: 10000000000, //10 GB
}
j, _ := json.Marshal(resp)
w.Write(j)
case "config/show":
resp := mockConfigResp{
Datastore: struct {
StorageMax string
}{
StorageMax: "10G",
},
}
j, _ := json.Marshal(resp)
w.Write(j)
case "version":
w.Write([]byte("{\"Version\":\"m.o.c.k\"}"))
default:
w.WriteHeader(http.StatusNotFound)
}
return
ERROR:
w.WriteHeader(http.StatusInternalServerError)
}
// Close closes the mock server. It's important to call after each test or
// the listeners are left hanging around.
func (m *IpfsMock) Close() {
m.server.Close()
}