426 lines
12 KiB
Go
426 lines
12 KiB
Go
/*
|
|
Copyright 2021 The Dapr Authors
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package ipfs
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"log"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/dapr/components-contrib/bindings"
|
|
"github.com/dapr/components-contrib/internal/utils"
|
|
"github.com/dapr/components-contrib/metadata"
|
|
"github.com/dapr/kit/logger"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
if !utils.IsTruthy(os.Getenv("IPFS_TEST")) {
|
|
log.Println("IPFS_TEST env var is not set to a truthy value; skipping tests")
|
|
os.Exit(0)
|
|
}
|
|
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func TestSingleNodeGlobalNetwork(t *testing.T) {
|
|
var b *IPFSBinding
|
|
repoPath := t.TempDir()
|
|
|
|
// CIDS contained in the QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG folder
|
|
folderCids := []string{
|
|
"QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V",
|
|
"QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
|
|
"QmY5heUM5qgRubMDD1og9fhCPA6QdkMp3QCwd4s7gJsyE7",
|
|
"QmdncfsVm2h5Kqq9hPmU7oAVX2zTSVP3L869tgTbPYnsha",
|
|
"QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB",
|
|
"QmTumTjvcYCAvRRwQ8sDRxh8ezmrcr88YFU7iYNroGGTBZ",
|
|
}
|
|
sort.Strings(folderCids)
|
|
|
|
t.Run("init node", func(t *testing.T) {
|
|
b = NewIPFSBinding(logger.NewLogger("tests")).(*IPFSBinding)
|
|
err := b.Init(bindings.Metadata{Base: metadata.Base{
|
|
Properties: map[string]string{
|
|
"repoPath": repoPath,
|
|
"routing": "dhtclient",
|
|
},
|
|
}})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("get operation", func(t *testing.T) {
|
|
t.Run("empty path", func(t *testing.T) {
|
|
_, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "get",
|
|
})
|
|
if assert.Error(t, err) {
|
|
assert.Equal(t, err.Error(), "metadata property 'path' is empty")
|
|
}
|
|
})
|
|
|
|
t.Run("retrieve document by CID", func(t *testing.T) {
|
|
data := getDocument(t, b, "QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB")
|
|
compareHash(t, data, "a48161fca5edd15f4649bb928c10769216fccdf317265fc75d747c1e6892f53c")
|
|
})
|
|
|
|
t.Run("retrieve document by IPLD", func(t *testing.T) {
|
|
// Same document, but different addressing method
|
|
data := getDocument(t, b, "/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme")
|
|
compareHash(t, data, "a48161fca5edd15f4649bb928c10769216fccdf317265fc75d747c1e6892f53c")
|
|
})
|
|
|
|
t.Run("cannot retrieve folder", func(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
_, err := b.Invoke(ctx, &bindings.InvokeRequest{
|
|
Operation: "get",
|
|
Metadata: map[string]string{
|
|
"path": "/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
|
|
},
|
|
})
|
|
|
|
if assert.Error(t, err) {
|
|
assert.Equal(t, err.Error(), "path does not represent a file")
|
|
}
|
|
})
|
|
|
|
// Retrieve files also to speed up pinning later
|
|
t.Run("retrieve files", func(t *testing.T) {
|
|
for _, e := range folderCids {
|
|
getDocument(t, b, e)
|
|
}
|
|
getDocument(t, b, "QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v")
|
|
})
|
|
})
|
|
|
|
t.Run("ls operation", func(t *testing.T) {
|
|
t.Run("empty path", func(t *testing.T) {
|
|
_, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "ls",
|
|
})
|
|
if assert.Error(t, err) {
|
|
assert.Equal(t, err.Error(), "metadata property 'path' is empty")
|
|
}
|
|
})
|
|
|
|
testLsOperationResponse := func(t *testing.T, list lsOperationResponse) {
|
|
cids := make([]string, len(list))
|
|
for i, e := range list {
|
|
cids[i] = e.Cid
|
|
}
|
|
sort.Strings(cids)
|
|
assert.True(t, reflect.DeepEqual(cids, folderCids), "received='%v' expected='%v'", cids, folderCids)
|
|
}
|
|
|
|
t.Run("list by CID", func(t *testing.T) {
|
|
list := listPath(t, b, "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG")
|
|
require.NotEmpty(t, list)
|
|
testLsOperationResponse(t, list)
|
|
})
|
|
|
|
t.Run("list by IPLD", func(t *testing.T) {
|
|
list := listPath(t, b, "/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG")
|
|
require.NotEmpty(t, list)
|
|
testLsOperationResponse(t, list)
|
|
})
|
|
})
|
|
|
|
t.Run("pin operations", func(t *testing.T) {
|
|
t.Run("pin-ls: nothing is pinned on a new node", func(t *testing.T) {
|
|
list := listPins(t, b)
|
|
assert.Empty(t, list)
|
|
})
|
|
|
|
t.Run("pin-add: pin file by CID", func(t *testing.T) {
|
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "pin-add",
|
|
Metadata: map[string]string{
|
|
"path": "QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB",
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.Empty(t, res.Data)
|
|
})
|
|
|
|
t.Run("pin-ls: list added pin", func(t *testing.T) {
|
|
list := listPins(t, b)
|
|
assert.Len(t, list, 1)
|
|
assert.Equal(t, "QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB", list[0].Cid)
|
|
})
|
|
|
|
t.Run("pin-add: pin file by IPLD", func(t *testing.T) {
|
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "pin-add",
|
|
Metadata: map[string]string{
|
|
"path": "/ipfs/QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v",
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.Empty(t, res.Data)
|
|
})
|
|
|
|
t.Run("pin-add: recursively pin folder", func(t *testing.T) {
|
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "pin-add",
|
|
Metadata: map[string]string{
|
|
"path": "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.Empty(t, res.Data)
|
|
})
|
|
|
|
// Add the folder to the list of expected items
|
|
expect := make([]string, len(folderCids)+2)
|
|
expect[0] = "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"
|
|
expect[1] = "QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v"
|
|
copy(expect[2:], folderCids)
|
|
sort.Strings(expect)
|
|
|
|
t.Run("pin-ls: list added folder", func(t *testing.T) {
|
|
cids := listPinnedCids(t, b)
|
|
assert.True(t, reflect.DeepEqual(cids, expect), "received='%v' expected='%v'", cids, expect)
|
|
})
|
|
|
|
t.Run("pin-rm: remove file", func(t *testing.T) {
|
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "pin-rm",
|
|
Metadata: map[string]string{
|
|
"path": "QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v",
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.Empty(t, res.Data)
|
|
})
|
|
|
|
// Remove the un-pinned file
|
|
i := 0
|
|
for _, e := range expect {
|
|
if e != "QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v" {
|
|
expect[i] = e
|
|
i++
|
|
}
|
|
}
|
|
expect = expect[:i]
|
|
|
|
t.Run("pin-ls: updated after removed file", func(t *testing.T) {
|
|
cids := listPinnedCids(t, b)
|
|
assert.True(t, reflect.DeepEqual(cids, expect), "received='%v' expected='%v'", cids, expect)
|
|
})
|
|
|
|
t.Run("pin-rm: recursively remove folder", func(t *testing.T) {
|
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "pin-rm",
|
|
Metadata: map[string]string{
|
|
"path": "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.Empty(t, res.Data)
|
|
})
|
|
|
|
t.Run("pin-rm: remove explicitly-pinned file", func(t *testing.T) {
|
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "pin-rm",
|
|
Metadata: map[string]string{
|
|
"path": "QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB",
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.Empty(t, res.Data)
|
|
})
|
|
|
|
t.Run("pin-ls: updated list is empty", func(t *testing.T) {
|
|
list := listPins(t, b)
|
|
assert.Empty(t, list)
|
|
})
|
|
})
|
|
|
|
t.Run("create operation", func(t *testing.T) {
|
|
expectPins := []string{}
|
|
|
|
t.Run("add with default options", func(t *testing.T) {
|
|
path := addDocument(t, b,
|
|
[]byte("Quel ramo del lago di Como, che volge a mezzogiorno"),
|
|
nil,
|
|
)
|
|
|
|
assert.Equal(t, "/ipfs/QmRW7jvkePyaAFvtapaqZ9kNkziUrmkhi4ue5oNpXS2qUx", path)
|
|
|
|
expectPins = append(expectPins, "QmRW7jvkePyaAFvtapaqZ9kNkziUrmkhi4ue5oNpXS2qUx")
|
|
})
|
|
|
|
t.Run("add with CID v1", func(t *testing.T) {
|
|
path := addDocument(t, b,
|
|
[]byte("Quel ramo del lago di Como, che volge a mezzogiorno"),
|
|
map[string]string{"cidVersion": "1"},
|
|
)
|
|
|
|
assert.Equal(t, "/ipfs/bafkreidhuwuwgycmsbj4sesi3pm6vpxpbm6byt3twex7sc2nadaxksnqeq", path)
|
|
|
|
expectPins = append(expectPins, "bafkreidhuwuwgycmsbj4sesi3pm6vpxpbm6byt3twex7sc2nadaxksnqeq")
|
|
})
|
|
|
|
t.Run("added files are pinned", func(t *testing.T) {
|
|
cids := listPinnedCids(t, b)
|
|
assert.True(t, reflect.DeepEqual(cids, expectPins), "received='%v' expected='%v'", cids, expectPins)
|
|
})
|
|
|
|
t.Run("add without pinning", func(t *testing.T) {
|
|
path := addDocument(t, b,
|
|
[]byte("😁🐶"),
|
|
map[string]string{"pin": "false"},
|
|
)
|
|
|
|
assert.Equal(t, "/ipfs/QmWsLpV1UUD26qHaEJqXfHazSRRZVX82M51EQ87UT7ryiR", path)
|
|
})
|
|
|
|
t.Run("pinned documents haven't changed", func(t *testing.T) {
|
|
cids := listPinnedCids(t, b)
|
|
assert.True(t, reflect.DeepEqual(cids, expectPins), "received='%v' expected='%v'", cids, expectPins)
|
|
})
|
|
|
|
t.Run("add inline", func(t *testing.T) {
|
|
path := addDocument(t, b,
|
|
[]byte("😁🐶"),
|
|
map[string]string{"inline": "true"},
|
|
)
|
|
|
|
assert.Equal(t, "/ipfs/bafyaaeakbyeaeeqi6cpzrapqt6ilmgai", path)
|
|
})
|
|
})
|
|
|
|
if b != nil {
|
|
b.Close()
|
|
}
|
|
}
|
|
|
|
func getDocument(t *testing.T, b *IPFSBinding, path string) []byte {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
res, err := b.Invoke(ctx, &bindings.InvokeRequest{
|
|
Operation: "get",
|
|
Metadata: map[string]string{
|
|
"path": path,
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.NotEmpty(t, res.Data)
|
|
|
|
return res.Data
|
|
}
|
|
|
|
func listPath(t *testing.T, b *IPFSBinding, path string) lsOperationResponse {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
res, err := b.Invoke(ctx, &bindings.InvokeRequest{
|
|
Operation: "ls",
|
|
Metadata: map[string]string{
|
|
"path": path,
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.NotEmpty(t, res.Data)
|
|
|
|
list := lsOperationResponse{}
|
|
err = json.Unmarshal(res.Data, &list)
|
|
require.NoError(t, err)
|
|
|
|
return list
|
|
}
|
|
|
|
func listPins(t *testing.T, b *IPFSBinding) pinLsOperationResponse {
|
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "pin-ls",
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.NotEmpty(t, res.Data)
|
|
|
|
list := pinLsOperationResponse{}
|
|
err = json.Unmarshal(res.Data, &list)
|
|
require.NoError(t, err)
|
|
|
|
return list
|
|
}
|
|
|
|
func listPinnedCids(t *testing.T, b *IPFSBinding) []string {
|
|
list := listPins(t, b)
|
|
require.NotEmpty(t, list)
|
|
|
|
cids := make([]string, len(list))
|
|
for i, e := range list {
|
|
cids[i] = e.Cid
|
|
}
|
|
|
|
sort.Strings(cids)
|
|
return cids
|
|
}
|
|
|
|
func addDocument(t *testing.T, b *IPFSBinding, data []byte, metadata map[string]string) string {
|
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
|
Operation: "create",
|
|
Data: data,
|
|
Metadata: metadata,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.NotEmpty(t, res.Data)
|
|
|
|
o := addOperationResponse{}
|
|
err = json.Unmarshal(res.Data, &o)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, o.Path)
|
|
|
|
return o.Path
|
|
}
|
|
|
|
func compareHash(t *testing.T, data []byte, expect string) {
|
|
require.NotEmpty(t, data)
|
|
h := sha256.Sum256(data)
|
|
digest := hex.EncodeToString(h[:])
|
|
assert.Equal(t, expect, digest)
|
|
}
|