ipfs-cluster/test/ipfs_mock.go
Adrian Lanzafame 22ec210c25
ipfsconn/ipfshttp: handle cid args passed in url path correctly
The extractCid function was added to enable the extraction of
a cid argument from either the url path or query string.
This puts the proxy behaviour on par with the current IPFS API.
The function does rely on the fact that ipfs-cluster doesn't
intercept any command that has more than one subcommand.
If that changes, this function will have to be updated.

License: MIT
Signed-off-by: Adrian Lanzafame <adrianlanzafame92@gmail.com>
2018-04-30 15:21:03 +10:00

290 lines
5.4 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
}
type mockRefsResp struct {
Ref string
Err string
}
type mockSwarmPeersResp struct {
Peers []mockIpfsPeer
}
type mockIpfsPeer struct {
Peer string
}
// 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/")
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":
arg, ok := extractCid(r.URL)
if !ok {
goto ERROR
}
if arg == ErrorCid {
goto ERROR
}
c, err := cid.Decode(arg)
if err != nil {
goto ERROR
}
m.pinMap.Add(api.PinCid(c))
resp := mockPinResp{
Pins: []string{arg},
}
j, _ := json.Marshal(resp)
w.Write(j)
case "pin/rm":
arg, ok := extractCid(r.URL)
if !ok {
goto ERROR
}
c, err := cid.Decode(arg)
if err != nil {
goto ERROR
}
m.pinMap.Rm(c)
resp := mockPinResp{
Pins: []string{arg},
}
j, _ := json.Marshal(resp)
w.Write(j)
case "pin/ls":
arg, ok := extractCid(r.URL)
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
}
cidStr := arg
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":
arg, ok := extractCid(r.URL)
if !ok {
goto ERROR
}
addr := arg
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 "swarm/peers":
peer1 := mockIpfsPeer{
Peer: TestPeerID4.Pretty(),
}
peer2 := mockIpfsPeer{
Peer: TestPeerID5.Pretty(),
}
resp := mockSwarmPeersResp{
Peers: []mockIpfsPeer{peer1, peer2},
}
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 "refs":
arg, ok := extractCid(r.URL)
if !ok {
goto ERROR
}
resp := mockRefsResp{
Ref: arg,
}
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()
}
// extractCid extracts the cid argument from a url.URL, either via
// the query string parameters or from the url path itself.
func extractCid(u *url.URL) (string, bool) {
arg := u.Query().Get("arg")
if arg != "" {
return arg, true
}
p := strings.TrimPrefix(u.Path, "/api/v0/")
segs := strings.Split(p, "/")
if len(segs) > 2 {
return segs[len(segs)-1], true
}
return "", false
}