2017-03-10 14:29:11 +00:00
|
|
|
package ipfshttp
|
2016-12-08 16:24:38 +00:00
|
|
|
|
|
|
|
import (
|
2017-03-29 20:52:13 +00:00
|
|
|
"bytes"
|
2018-04-16 09:01:20 +00:00
|
|
|
"context"
|
2017-01-26 21:49:53 +00:00
|
|
|
"encoding/json"
|
2016-12-08 16:24:38 +00:00
|
|
|
"fmt"
|
2017-01-26 21:49:53 +00:00
|
|
|
"io/ioutil"
|
2017-03-29 20:52:13 +00:00
|
|
|
"mime/multipart"
|
2016-12-08 16:24:38 +00:00
|
|
|
"net/http"
|
2017-03-29 20:52:13 +00:00
|
|
|
"net/url"
|
2016-12-08 16:24:38 +00:00
|
|
|
"testing"
|
2017-03-23 18:34:33 +00:00
|
|
|
"time"
|
2016-12-08 16:24:38 +00:00
|
|
|
|
2017-10-13 16:44:43 +00:00
|
|
|
cid "github.com/ipfs/go-cid"
|
|
|
|
logging "github.com/ipfs/go-log"
|
|
|
|
ma "github.com/multiformats/go-multiaddr"
|
2018-04-20 04:09:07 +00:00
|
|
|
|
|
|
|
"github.com/ipfs/ipfs-cluster/api"
|
|
|
|
"github.com/ipfs/ipfs-cluster/test"
|
2016-12-08 16:24:38 +00:00
|
|
|
)
|
|
|
|
|
2017-03-23 18:34:33 +00:00
|
|
|
func init() {
|
|
|
|
_ = logging.Logger
|
|
|
|
}
|
|
|
|
|
2017-03-14 15:37:29 +00:00
|
|
|
func testIPFSConnector(t *testing.T) (*Connector, *test.IpfsMock) {
|
2017-02-09 15:29:17 +00:00
|
|
|
mock := test.NewIpfsMock()
|
2017-03-10 14:29:11 +00:00
|
|
|
nodeMAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d",
|
|
|
|
mock.Addr, mock.Port))
|
2018-01-24 17:52:27 +00:00
|
|
|
proxyMAddr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0")
|
2016-12-08 16:24:38 +00:00
|
|
|
|
Issue #162: Rework configuration format
The following commit reimplements ipfs-cluster configuration under
the following premises:
* Each component is initialized with a configuration object
defined by its module
* Each component decides how the JSON representation of its
configuration looks like
* Each component parses and validates its own configuration
* Each component exposes its own defaults
* Component configurations are make the sections of a
central JSON configuration file (which replaces the current
JSON format)
* Component configurations implement a common interface
(config.ComponentConfig) with a set of common operations
* The central configuration file is managed by a
config.ConfigManager which:
* Registers ComponentConfigs
* Assigns the correspondent sections from the JSON file to each
component and delegates the parsing
* Delegates the JSON generation for each section
* Can be notified when the configuration is updated and must be
saved to disk
The new service.json would then look as follows:
```json
{
"cluster": {
"id": "QmTVW8NoRxC5wBhV7WtAYtRn7itipEESfozWN5KmXUQnk2",
"private_key": "<...>",
"secret": "00224102ae6aaf94f2606abf69a0e278251ecc1d64815b617ff19d6d2841f786",
"peers": [],
"bootstrap": [],
"leave_on_shutdown": false,
"listen_multiaddress": "/ip4/0.0.0.0/tcp/9096",
"state_sync_interval": "1m0s",
"ipfs_sync_interval": "2m10s",
"replication_factor": -1,
"monitor_ping_interval": "15s"
},
"consensus": {
"raft": {
"heartbeat_timeout": "1s",
"election_timeout": "1s",
"commit_timeout": "50ms",
"max_append_entries": 64,
"trailing_logs": 10240,
"snapshot_interval": "2m0s",
"snapshot_threshold": 8192,
"leader_lease_timeout": "500ms"
}
},
"api": {
"restapi": {
"listen_multiaddress": "/ip4/127.0.0.1/tcp/9094",
"read_timeout": "30s",
"read_header_timeout": "5s",
"write_timeout": "1m0s",
"idle_timeout": "2m0s"
}
},
"ipfs_connector": {
"ipfshttp": {
"proxy_listen_multiaddress": "/ip4/127.0.0.1/tcp/9095",
"node_multiaddress": "/ip4/127.0.0.1/tcp/5001",
"connect_swarms_delay": "7s",
"proxy_read_timeout": "10m0s",
"proxy_read_header_timeout": "5s",
"proxy_write_timeout": "10m0s",
"proxy_idle_timeout": "1m0s"
}
},
"monitor": {
"monbasic": {
"check_interval": "15s"
}
},
"informer": {
"disk": {
"metric_ttl": "30s",
"metric_type": "freespace"
},
"numpin": {
"metric_ttl": "10s"
}
}
}
```
This new format aims to be easily extensible per component. As such,
it already surfaces quite a few new options which were hardcoded
before.
Additionally, since Go API have changed, some redundant methods have been
removed and small refactoring has happened to take advantage of the new
way.
License: MIT
Signed-off-by: Hector Sanjuan <hector@protocol.ai>
2017-10-11 18:23:03 +00:00
|
|
|
cfg := &Config{}
|
|
|
|
cfg.Default()
|
|
|
|
cfg.NodeAddr = nodeMAddr
|
|
|
|
cfg.ProxyAddr = proxyMAddr
|
|
|
|
cfg.ConnectSwarmsDelay = 0
|
|
|
|
|
|
|
|
ipfs, err := NewConnector(cfg)
|
2016-12-08 16:24:38 +00:00
|
|
|
if err != nil {
|
2016-12-08 18:04:28 +00:00
|
|
|
t.Fatal("creating an IPFSConnector should work: ", err)
|
2016-12-08 16:24:38 +00:00
|
|
|
}
|
2017-02-09 15:29:17 +00:00
|
|
|
ipfs.SetClient(test.NewMockRPCClient(t))
|
2016-12-16 16:22:37 +00:00
|
|
|
return ipfs, mock
|
2016-12-08 18:04:28 +00:00
|
|
|
}
|
|
|
|
|
2017-03-14 15:37:29 +00:00
|
|
|
func TestNewConnector(t *testing.T) {
|
2016-12-23 18:35:37 +00:00
|
|
|
ipfs, mock := testIPFSConnector(t)
|
2016-12-16 16:22:37 +00:00
|
|
|
defer mock.Close()
|
2016-12-08 18:04:28 +00:00
|
|
|
defer ipfs.Shutdown()
|
|
|
|
}
|
2016-12-08 16:24:38 +00:00
|
|
|
|
2017-01-26 18:59:31 +00:00
|
|
|
func TestIPFSID(t *testing.T) {
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer ipfs.Shutdown()
|
|
|
|
id, err := ipfs.ID()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-09 15:29:17 +00:00
|
|
|
if id.ID != test.TestPeerID1 {
|
2017-01-26 18:59:31 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 10:35:29 +00:00
|
|
|
func testPin(t *testing.T, method string) {
|
2018-04-16 09:01:20 +00:00
|
|
|
ctx := context.Background()
|
2016-12-23 18:35:37 +00:00
|
|
|
ipfs, mock := testIPFSConnector(t)
|
2016-12-16 16:22:37 +00:00
|
|
|
defer mock.Close()
|
2016-12-08 18:04:28 +00:00
|
|
|
defer ipfs.Shutdown()
|
2016-12-16 16:22:37 +00:00
|
|
|
|
2018-03-08 10:35:29 +00:00
|
|
|
ipfs.config.PinMethod = method
|
2018-03-07 13:51:37 +00:00
|
|
|
|
|
|
|
c, _ := cid.Decode(test.TestCid1)
|
2018-04-16 09:01:20 +00:00
|
|
|
err := ipfs.Pin(ctx, c, true)
|
2018-03-07 13:51:37 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Error("expected success pinning cid")
|
|
|
|
}
|
2018-04-16 09:01:20 +00:00
|
|
|
pinSt, err := ipfs.PinLsCid(ctx, c)
|
2018-03-07 13:51:37 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal("expected success doing ls")
|
|
|
|
}
|
|
|
|
if !pinSt.IsPinned() {
|
|
|
|
t.Error("cid should have been pinned")
|
|
|
|
}
|
|
|
|
|
|
|
|
c2, _ := cid.Decode(test.ErrorCid)
|
2018-04-16 09:01:20 +00:00
|
|
|
err = ipfs.Pin(ctx, c2, true)
|
2018-03-07 13:51:37 +00:00
|
|
|
if err == nil {
|
|
|
|
t.Error("expected error pinning cid")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 10:35:29 +00:00
|
|
|
func TestIPFSPin(t *testing.T) {
|
|
|
|
t.Run("method=pin", func(t *testing.T) { testPin(t, "pin") })
|
|
|
|
t.Run("method=refs", func(t *testing.T) { testPin(t, "refs") })
|
|
|
|
}
|
|
|
|
|
2016-12-15 13:07:19 +00:00
|
|
|
func TestIPFSUnpin(t *testing.T) {
|
2018-04-16 09:01:20 +00:00
|
|
|
ctx := context.Background()
|
2016-12-23 18:35:37 +00:00
|
|
|
ipfs, mock := testIPFSConnector(t)
|
2016-12-16 16:22:37 +00:00
|
|
|
defer mock.Close()
|
2016-12-08 18:04:28 +00:00
|
|
|
defer ipfs.Shutdown()
|
2017-02-09 15:29:17 +00:00
|
|
|
c, _ := cid.Decode(test.TestCid1)
|
2018-04-16 09:01:20 +00:00
|
|
|
err := ipfs.Unpin(ctx, c)
|
2016-12-08 16:24:38 +00:00
|
|
|
if err != nil {
|
2016-12-16 16:22:37 +00:00
|
|
|
t.Error("expected success unpinning non-pinned cid")
|
2016-12-08 16:24:38 +00:00
|
|
|
}
|
2018-04-16 09:01:20 +00:00
|
|
|
ipfs.Pin(ctx, c, true)
|
|
|
|
err = ipfs.Unpin(ctx, c)
|
2016-12-08 16:24:38 +00:00
|
|
|
if err != nil {
|
2016-12-16 16:22:37 +00:00
|
|
|
t.Error("expected success unpinning pinned cid")
|
2016-12-08 16:24:38 +00:00
|
|
|
}
|
2016-12-08 18:04:28 +00:00
|
|
|
}
|
|
|
|
|
2017-01-25 17:07:19 +00:00
|
|
|
func TestIPFSPinLsCid(t *testing.T) {
|
2018-04-16 09:01:20 +00:00
|
|
|
ctx := context.Background()
|
2016-12-23 18:35:37 +00:00
|
|
|
ipfs, mock := testIPFSConnector(t)
|
2016-12-16 16:22:37 +00:00
|
|
|
defer mock.Close()
|
2016-12-08 18:04:28 +00:00
|
|
|
defer ipfs.Shutdown()
|
2017-02-09 15:29:17 +00:00
|
|
|
c, _ := cid.Decode(test.TestCid1)
|
|
|
|
c2, _ := cid.Decode(test.TestCid2)
|
2016-12-08 16:24:38 +00:00
|
|
|
|
2018-04-16 09:01:20 +00:00
|
|
|
ipfs.Pin(ctx, c, true)
|
|
|
|
ips, err := ipfs.PinLsCid(ctx, c)
|
2017-01-25 17:07:19 +00:00
|
|
|
if err != nil || !ips.IsPinned() {
|
2016-12-08 16:24:38 +00:00
|
|
|
t.Error("c should appear pinned")
|
|
|
|
}
|
|
|
|
|
2018-04-16 09:01:20 +00:00
|
|
|
ips, err = ipfs.PinLsCid(ctx, c2)
|
2017-02-08 17:04:08 +00:00
|
|
|
if err != nil || ips != api.IPFSPinStatusUnpinned {
|
2016-12-08 16:24:38 +00:00
|
|
|
t.Error("c2 should appear unpinned")
|
|
|
|
}
|
2016-12-08 18:04:28 +00:00
|
|
|
}
|
|
|
|
|
2017-01-25 17:07:19 +00:00
|
|
|
func TestIPFSPinLs(t *testing.T) {
|
2018-04-16 09:01:20 +00:00
|
|
|
ctx := context.Background()
|
2017-01-25 17:07:19 +00:00
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer mock.Close()
|
|
|
|
defer ipfs.Shutdown()
|
2017-02-09 15:29:17 +00:00
|
|
|
c, _ := cid.Decode(test.TestCid1)
|
|
|
|
c2, _ := cid.Decode(test.TestCid2)
|
2017-01-25 17:07:19 +00:00
|
|
|
|
2018-04-16 09:01:20 +00:00
|
|
|
ipfs.Pin(ctx, c, true)
|
|
|
|
ipfs.Pin(ctx, c2, true)
|
|
|
|
ipsMap, err := ipfs.PinLs(ctx, "")
|
2017-01-25 17:07:19 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Error("should not error")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(ipsMap) != 2 {
|
|
|
|
t.Fatal("the map does not contain expected keys")
|
|
|
|
}
|
|
|
|
|
2017-02-09 15:29:17 +00:00
|
|
|
if !ipsMap[test.TestCid1].IsPinned() || !ipsMap[test.TestCid2].IsPinned() {
|
2017-01-25 17:07:19 +00:00
|
|
|
t.Error("c1 and c2 should appear pinned")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-26 21:49:53 +00:00
|
|
|
func TestIPFSProxyVersion(t *testing.T) {
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer mock.Close()
|
|
|
|
defer ipfs.Shutdown()
|
|
|
|
|
2017-10-13 16:38:18 +00:00
|
|
|
res, err := http.Post(fmt.Sprintf("%s/version", proxyURL(ipfs)), "", nil)
|
2017-01-26 21:49:53 +00:00
|
|
|
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()
|
2017-03-28 09:09:05 +00:00
|
|
|
|
2017-01-26 21:49:53 +00:00
|
|
|
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) {
|
2016-12-23 18:35:37 +00:00
|
|
|
ipfs, mock := testIPFSConnector(t)
|
2016-12-16 16:22:37 +00:00
|
|
|
defer mock.Close()
|
2016-12-08 18:04:28 +00:00
|
|
|
defer ipfs.Shutdown()
|
2016-12-08 16:24:38 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
type args struct {
|
|
|
|
urlPath string
|
|
|
|
testCid string
|
|
|
|
statusCode int
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
want string
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"pin good cid query arg",
|
|
|
|
args{
|
|
|
|
"/pin/add?arg=",
|
|
|
|
test.TestCid1,
|
|
|
|
http.StatusOK,
|
|
|
|
},
|
|
|
|
test.TestCid1,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"pin good cid url arg",
|
|
|
|
args{
|
|
|
|
"/pin/add/",
|
|
|
|
test.TestCid1,
|
|
|
|
http.StatusOK,
|
|
|
|
},
|
|
|
|
test.TestCid1,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"pin bad cid query arg",
|
|
|
|
args{
|
|
|
|
"/pin/add?arg=",
|
|
|
|
test.ErrorCid,
|
|
|
|
http.StatusInternalServerError,
|
|
|
|
},
|
|
|
|
"",
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"pin bad cid url arg",
|
|
|
|
args{
|
|
|
|
"/pin/add/",
|
|
|
|
test.ErrorCid,
|
|
|
|
http.StatusInternalServerError,
|
|
|
|
},
|
|
|
|
"",
|
|
|
|
true,
|
|
|
|
},
|
2017-01-26 21:49:53 +00:00
|
|
|
}
|
2018-04-20 04:09:07 +00:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
u := fmt.Sprintf("%s%s%s", proxyURL(ipfs), tt.args.urlPath, tt.args.testCid)
|
|
|
|
res, err := http.Post(u, "", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("should have succeeded: ", err)
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
if res.StatusCode != tt.args.statusCode {
|
|
|
|
t.Errorf("statusCode: got = %v, want %v", res.StatusCode, tt.args.statusCode)
|
|
|
|
}
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
resBytes, _ := ioutil.ReadAll(res.Body)
|
|
|
|
|
|
|
|
switch tt.wantErr {
|
|
|
|
case false:
|
|
|
|
var resp ipfsPinOpResp
|
|
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Pins) != 1 {
|
|
|
|
t.Fatalf("wrong number of pins: got = %d, want %d", len(resp.Pins), 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Pins[0] != tt.want {
|
|
|
|
t.Errorf("wrong pin cid: got = %s, want = %s", resp.Pins[0], tt.want)
|
|
|
|
}
|
|
|
|
case true:
|
|
|
|
var respErr ipfsError
|
|
|
|
err = json.Unmarshal(resBytes, &respErr)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if respErr.Message != test.ErrBadCid.Error() {
|
|
|
|
t.Errorf("wrong response: got = %s, want = %s", respErr.Message, test.ErrBadCid.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2017-01-26 21:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIPFSProxyUnpin(t *testing.T) {
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer mock.Close()
|
|
|
|
defer ipfs.Shutdown()
|
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
type args struct {
|
|
|
|
urlPath string
|
|
|
|
testCid string
|
|
|
|
statusCode int
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
want string
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"unpin good cid query arg",
|
|
|
|
args{
|
|
|
|
"/pin/rm?arg=",
|
|
|
|
test.TestCid1,
|
|
|
|
http.StatusOK,
|
|
|
|
},
|
|
|
|
test.TestCid1,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"unpin good cid url arg",
|
|
|
|
args{
|
|
|
|
"/pin/rm/",
|
|
|
|
test.TestCid1,
|
|
|
|
http.StatusOK,
|
|
|
|
},
|
|
|
|
test.TestCid1,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"unpin bad cid query arg",
|
|
|
|
args{
|
|
|
|
"/pin/rm?arg=",
|
|
|
|
test.ErrorCid,
|
|
|
|
http.StatusInternalServerError,
|
|
|
|
},
|
|
|
|
"",
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"unpin bad cid url arg",
|
|
|
|
args{
|
|
|
|
"/pin/rm/",
|
|
|
|
test.ErrorCid,
|
|
|
|
http.StatusInternalServerError,
|
|
|
|
},
|
|
|
|
"",
|
|
|
|
true,
|
|
|
|
},
|
2017-01-26 21:49:53 +00:00
|
|
|
}
|
2018-04-20 04:09:07 +00:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
u := fmt.Sprintf("%s%s%s", proxyURL(ipfs), tt.args.urlPath, tt.args.testCid)
|
|
|
|
res, err := http.Post(u, "", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("should have succeeded: ", err)
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
if res.StatusCode != tt.args.statusCode {
|
|
|
|
t.Errorf("statusCode: got = %v, want %v", res.StatusCode, tt.args.statusCode)
|
|
|
|
}
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
resBytes, _ := ioutil.ReadAll(res.Body)
|
|
|
|
|
|
|
|
switch tt.wantErr {
|
|
|
|
case false:
|
|
|
|
var resp ipfsPinOpResp
|
|
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Pins) != 1 {
|
|
|
|
t.Fatalf("wrong number of pins: got = %d, want %d", len(resp.Pins), 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Pins[0] != tt.want {
|
|
|
|
t.Errorf("wrong pin cid: got = %s, want = %s", resp.Pins[0], tt.want)
|
|
|
|
}
|
|
|
|
case true:
|
|
|
|
var respErr ipfsError
|
|
|
|
err = json.Unmarshal(resBytes, &respErr)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if respErr.Message != test.ErrBadCid.Error() {
|
|
|
|
t.Errorf("wrong response: got = %s, want = %s", respErr.Message, test.ErrBadCid.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2017-01-26 21:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIPFSProxyPinLs(t *testing.T) {
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer mock.Close()
|
|
|
|
defer ipfs.Shutdown()
|
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
t.Run("pin/ls query arg", func(t *testing.T) {
|
|
|
|
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")
|
|
|
|
}
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
resBytes, _ := ioutil.ReadAll(res.Body)
|
|
|
|
var resp ipfsPinLsResp
|
|
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
_, ok := resp.Keys[test.TestCid1]
|
|
|
|
if len(resp.Keys) != 1 || !ok {
|
|
|
|
t.Error("wrong response")
|
|
|
|
}
|
|
|
|
})
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
t.Run("pin/ls url arg", func(t *testing.T) {
|
|
|
|
res, err := http.Post(fmt.Sprintf("%s/pin/ls/%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")
|
|
|
|
}
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
resBytes, _ := ioutil.ReadAll(res.Body)
|
|
|
|
var resp ipfsPinLsResp
|
|
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
_, ok := resp.Keys[test.TestCid1]
|
|
|
|
if len(resp.Keys) != 1 || !ok {
|
|
|
|
t.Error("wrong response")
|
|
|
|
}
|
|
|
|
})
|
2017-01-26 21:49:53 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
t.Run("pin/ls all no arg", func(t *testing.T) {
|
|
|
|
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")
|
|
|
|
}
|
2017-04-06 02:27:02 +00:00
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
resBytes, _ := ioutil.ReadAll(res2.Body)
|
|
|
|
var resp ipfsPinLsResp
|
|
|
|
err = json.Unmarshal(resBytes, &resp)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Keys) != 3 {
|
|
|
|
t.Error("wrong response")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("pin/ls bad cid query arg", func(t *testing.T) {
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
})
|
2017-03-28 09:09:05 +00:00
|
|
|
}
|
|
|
|
|
2017-03-29 20:52:13 +00:00
|
|
|
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))
|
2017-12-06 13:56:25 +00:00
|
|
|
req, _ := http.NewRequest("POST", url, body)
|
2017-03-29 20:52:13 +00:00
|
|
|
req.Header.Set("Content-Type", w.FormDataContentType())
|
|
|
|
reqs[i] = req
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(urlQueries); i++ {
|
2018-04-20 04:09:07 +00:00
|
|
|
t.Run(urlQueries[i], func(t *testing.T) {
|
|
|
|
res, err := http.DefaultClient.Do(reqs[i])
|
2017-11-13 12:52:33 +00:00
|
|
|
if err != nil {
|
2018-04-20 04:09:07 +00:00
|
|
|
t.Fatal("should have succeeded: ", err)
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
t.Fatalf("Bad response status: got = %d, want = %d", res.StatusCode, http.StatusOK)
|
2017-11-13 12:52:33 +00:00
|
|
|
}
|
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
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
|
|
|
|
}
|
2017-11-13 12:52:33 +00:00
|
|
|
}
|
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
})
|
2017-03-29 20:52:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProxyAddError(t *testing.T) {
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer mock.Close()
|
|
|
|
defer ipfs.Shutdown()
|
2017-10-13 16:38:18 +00:00
|
|
|
res, err := http.Post(fmt.Sprintf("%s/add?recursive=true", proxyURL(ipfs)), "", nil)
|
2017-03-29 20:52:13 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
res.Body.Close()
|
|
|
|
if res.StatusCode != http.StatusInternalServerError {
|
2018-04-20 04:09:07 +00:00
|
|
|
t.Errorf("wrong status code: got = %d, want = %d", res.StatusCode, http.StatusInternalServerError)
|
2017-03-29 20:52:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDecideRecursivePins(t *testing.T) {
|
|
|
|
type testcases struct {
|
|
|
|
addResps []ipfsAddResp
|
|
|
|
query url.Values
|
|
|
|
expect []string
|
|
|
|
}
|
|
|
|
|
|
|
|
tcs := []testcases{
|
|
|
|
{
|
2017-11-13 12:52:33 +00:00
|
|
|
[]ipfsAddResp{{"a", "cida", 0}},
|
2017-03-29 20:52:13 +00:00
|
|
|
url.Values{},
|
|
|
|
[]string{"cida"},
|
|
|
|
},
|
|
|
|
{
|
2017-11-13 12:52:33 +00:00
|
|
|
[]ipfsAddResp{{"a/b", "cidb", 0}, {"a", "cida", 0}},
|
2017-03-29 20:52:13 +00:00
|
|
|
url.Values{},
|
|
|
|
[]string{"cida"},
|
|
|
|
},
|
|
|
|
{
|
2017-11-13 12:52:33 +00:00
|
|
|
[]ipfsAddResp{{"a/b", "cidb", 0}, {"c", "cidc", 0}, {"a", "cida", 0}},
|
2017-03-29 20:52:13 +00:00
|
|
|
url.Values{},
|
|
|
|
[]string{"cidc", "cida"},
|
|
|
|
},
|
|
|
|
{
|
2017-11-13 12:52:33 +00:00
|
|
|
[]ipfsAddResp{{"/a", "cida", 0}},
|
2017-03-29 20:52:13 +00:00
|
|
|
url.Values{},
|
|
|
|
[]string{"cida"},
|
|
|
|
},
|
|
|
|
{
|
2017-11-13 12:52:33 +00:00
|
|
|
[]ipfsAddResp{{"a/b/c/d", "cidd", 0}},
|
2017-03-29 20:52:13 +00:00
|
|
|
url.Values{},
|
|
|
|
[]string{"cidd"},
|
|
|
|
},
|
|
|
|
{
|
2017-11-13 12:52:33 +00:00
|
|
|
[]ipfsAddResp{{"a", "cida", 0}, {"b", "cidb", 0}, {"c", "cidc", 0}, {"d", "cidd", 0}},
|
2017-03-29 20:52:13 +00:00
|
|
|
url.Values{},
|
|
|
|
[]string{"cida", "cidb", "cidc", "cidd"},
|
|
|
|
},
|
|
|
|
{
|
2017-11-13 12:52:33 +00:00
|
|
|
[]ipfsAddResp{{"a", "cida", 0}, {"b", "cidb", 0}, {"", "cidwrap", 0}},
|
2017-03-29 20:52:13 +00:00
|
|
|
url.Values{
|
|
|
|
"wrap-in-directory": []string{"true"},
|
|
|
|
},
|
|
|
|
[]string{"cidwrap"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
|
2017-11-13 12:52:33 +00:00
|
|
|
[]ipfsAddResp{{"b", "", 0}, {"a", "cida", 0}},
|
2017-03-29 20:52:13 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-28 09:09:05 +00:00
|
|
|
func TestProxyError(t *testing.T) {
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer mock.Close()
|
|
|
|
defer ipfs.Shutdown()
|
|
|
|
|
2017-10-13 16:38:18 +00:00
|
|
|
res, err := http.Post(fmt.Sprintf("%s/bad/command", proxyURL(ipfs)), "", nil)
|
2017-03-28 09:09:05 +00:00
|
|
|
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")
|
|
|
|
}
|
2016-12-08 18:04:28 +00:00
|
|
|
}
|
2016-12-08 16:24:38 +00:00
|
|
|
|
2016-12-15 13:07:19 +00:00
|
|
|
func TestIPFSShutdown(t *testing.T) {
|
2016-12-23 18:35:37 +00:00
|
|
|
ipfs, mock := testIPFSConnector(t)
|
2016-12-16 16:22:37 +00:00
|
|
|
defer mock.Close()
|
2016-12-08 16:24:38 +00:00
|
|
|
if err := ipfs.Shutdown(); err != nil {
|
|
|
|
t.Error("expected a clean shutdown")
|
|
|
|
}
|
2016-12-16 16:22:37 +00:00
|
|
|
if err := ipfs.Shutdown(); err != nil {
|
|
|
|
t.Error("expected a second clean shutdown")
|
|
|
|
}
|
2016-12-08 16:24:38 +00:00
|
|
|
}
|
2017-03-16 14:51:24 +00:00
|
|
|
|
2017-03-23 18:34:33 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
func TestSwarmPeers(t *testing.T) {
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer mock.Close()
|
|
|
|
defer ipfs.Shutdown()
|
|
|
|
|
|
|
|
swarmPeers, err := ipfs.SwarmPeers()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(swarmPeers) != 2 {
|
|
|
|
t.Fatal("expected 2 swarm peers")
|
|
|
|
}
|
|
|
|
if swarmPeers[0] != test.TestPeerID4 {
|
|
|
|
t.Error("unexpected swarm peer")
|
|
|
|
}
|
|
|
|
if swarmPeers[1] != test.TestPeerID5 {
|
|
|
|
t.Error("unexpected swarm peer")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-14 04:44:43 +00:00
|
|
|
func TestBlockPut(t *testing.T) {
|
|
|
|
ipfs, mock := testIPFSConnector(t)
|
|
|
|
defer mock.Close()
|
|
|
|
defer ipfs.Shutdown()
|
|
|
|
|
|
|
|
data := []byte(test.TestCid4Data)
|
2018-03-07 03:29:32 +00:00
|
|
|
resp, err := ipfs.BlockPut(api.NodeWithMeta{
|
2018-02-27 01:27:53 +00:00
|
|
|
Data: data,
|
|
|
|
Format: "protobuf",
|
|
|
|
})
|
2018-02-14 04:44:43 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-02-15 04:59:57 +00:00
|
|
|
if resp != test.TestCid4 {
|
2018-02-14 04:44:43 +00:00
|
|
|
t.Fatal("Unexpected resulting cid")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-27 13:07:12 +00:00
|
|
|
func TestRepoSize(t *testing.T) {
|
2018-04-16 09:01:20 +00:00
|
|
|
ctx := context.Background()
|
2017-03-27 13:07:12 +00:00
|
|
|
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)
|
2018-04-16 09:01:20 +00:00
|
|
|
err = ipfs.Pin(ctx, c, true)
|
2017-03-27 13:07:12 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-16 14:51:24 +00:00
|
|
|
func proxyURL(c *Connector) string {
|
2018-01-24 17:52:27 +00:00
|
|
|
addr := c.listener.Addr()
|
|
|
|
return fmt.Sprintf("http://%s/api/v0", addr.String())
|
2017-03-16 14:51:24 +00:00
|
|
|
}
|
2018-04-20 04:09:07 +00:00
|
|
|
|
|
|
|
func Test_extractArgument(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
handlePath string
|
|
|
|
u *url.URL
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
want string
|
|
|
|
want1 bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"pin/add url arg",
|
|
|
|
args{
|
|
|
|
"add",
|
|
|
|
mustParseURL(fmt.Sprintf("/api/v0/pin/add/%s", test.TestCid1)),
|
|
|
|
},
|
|
|
|
test.TestCid1,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"pin/add query arg",
|
|
|
|
args{
|
|
|
|
"add",
|
|
|
|
mustParseURL(fmt.Sprintf("/api/v0/pin/add?arg=%s", test.TestCid1)),
|
|
|
|
},
|
|
|
|
test.TestCid1,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"pin/ls url arg",
|
|
|
|
args{
|
|
|
|
"pin/ls",
|
|
|
|
mustParseURL(fmt.Sprintf("/api/v0/pin/ls/%s", test.TestCid1)),
|
|
|
|
},
|
|
|
|
test.TestCid1,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"pin/ls query arg",
|
|
|
|
args{
|
|
|
|
"pin/ls",
|
|
|
|
mustParseURL(fmt.Sprintf("/api/v0/pin/ls?arg=%s", test.TestCid1)),
|
|
|
|
},
|
|
|
|
test.TestCid1,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
got, got1 := extractArgument(tt.args.u)
|
|
|
|
if got != tt.want {
|
|
|
|
t.Errorf("extractCid() got = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
if got1 != tt.want1 {
|
|
|
|
t.Errorf("extractCid() got1 = %v, want %v", got1, tt.want1)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustParseURL(rawurl string) *url.URL {
|
|
|
|
u, err := url.Parse(rawurl)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return u
|
|
|
|
}
|