| 
 | 1 | +package client_test  | 
 | 2 | + | 
 | 3 | +import (  | 
 | 4 | +	"context"  | 
 | 5 | +	"fmt"  | 
 | 6 | +	"testing"  | 
 | 7 | + | 
 | 8 | +	"github.com/ipfs/go-cid"  | 
 | 9 | +	cidlink "github.com/ipld/go-ipld-prime/linking/cid"  | 
 | 10 | +	"github.com/multiformats/go-multihash"  | 
 | 11 | +	spaceblobcap "github.com/storacha/go-libstoracha/capabilities/space/blob"  | 
 | 12 | +	"github.com/storacha/go-libstoracha/capabilities/types"  | 
 | 13 | +	libtestutil "github.com/storacha/go-libstoracha/testutil"  | 
 | 14 | +	"github.com/storacha/go-ucanto/core/dag/blockstore"  | 
 | 15 | +	"github.com/storacha/go-ucanto/core/invocation"  | 
 | 16 | +	"github.com/storacha/go-ucanto/core/receipt/fx"  | 
 | 17 | +	"github.com/storacha/go-ucanto/core/result"  | 
 | 18 | +	"github.com/storacha/go-ucanto/core/result/failure"  | 
 | 19 | +	ed25519signer "github.com/storacha/go-ucanto/principal/ed25519/signer"  | 
 | 20 | +	"github.com/storacha/go-ucanto/server"  | 
 | 21 | +	uhelpers "github.com/storacha/go-ucanto/testing/helpers"  | 
 | 22 | +	"github.com/storacha/go-ucanto/ucan"  | 
 | 23 | +	"github.com/storacha/guppy/pkg/client"  | 
 | 24 | +	"github.com/storacha/guppy/pkg/client/testutil"  | 
 | 25 | +	"github.com/stretchr/testify/require"  | 
 | 26 | +)  | 
 | 27 | + | 
 | 28 | +func TestSpaceBlobReplicate(t *testing.T) {  | 
 | 29 | +	t.Run("invokes `space/blob/replicate`", func(t *testing.T) {  | 
 | 30 | +		space, err := ed25519signer.Generate()  | 
 | 31 | +		require.NoError(t, err)  | 
 | 32 | + | 
 | 33 | +		invocations := []invocation.Invocation{}  | 
 | 34 | +		invokedCapabilities := []ucan.Capability[spaceblobcap.ReplicateCaveats]{}  | 
 | 35 | + | 
 | 36 | +		connection := testutil.NewTestServerConnection(  | 
 | 37 | +			server.WithServiceMethod(  | 
 | 38 | +				spaceblobcap.Replicate.Can(),  | 
 | 39 | +				server.Provide(  | 
 | 40 | +					spaceblobcap.Replicate,  | 
 | 41 | +					func(  | 
 | 42 | +						ctx context.Context,  | 
 | 43 | +						cap ucan.Capability[spaceblobcap.ReplicateCaveats],  | 
 | 44 | +						inv invocation.Invocation,  | 
 | 45 | +						context server.InvocationContext,  | 
 | 46 | +					) (result.Result[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure], fx.Effects, error) {  | 
 | 47 | +						invocations = append(invocations, inv)  | 
 | 48 | +						invokedCapabilities = append(invokedCapabilities, cap)  | 
 | 49 | +						sitePromises := make([]types.Promise, cap.Nb().Replicas)  | 
 | 50 | +						for i := range sitePromises {  | 
 | 51 | +							siteDigest, err := multihash.Encode(fmt.Appendf(nil, "test-replicated-site-%d", i), multihash.IDENTITY)  | 
 | 52 | +							if err != nil {  | 
 | 53 | +								return nil, nil, fmt.Errorf("encoding site digest: %w", err)  | 
 | 54 | +							}  | 
 | 55 | +							sitePromises[i] = types.Promise{  | 
 | 56 | +								UcanAwait: types.Await{  | 
 | 57 | +									Selector: ".out.ok.site",  | 
 | 58 | +									Link:     cidlink.Link{Cid: cid.NewCidV1(cid.Raw, siteDigest)},  | 
 | 59 | +								},  | 
 | 60 | +							}  | 
 | 61 | +						}  | 
 | 62 | +						return result.Ok[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure](  | 
 | 63 | +							spaceblobcap.ReplicateOk{  | 
 | 64 | +								Site: sitePromises,  | 
 | 65 | +							},  | 
 | 66 | +						), nil, nil  | 
 | 67 | +					},  | 
 | 68 | +				),  | 
 | 69 | +			),  | 
 | 70 | +		)  | 
 | 71 | + | 
 | 72 | +		// Act as the space itself for auth simplicity  | 
 | 73 | +		c := uhelpers.Must(client.NewClient(client.WithConnection(connection), client.WithPrincipal(space)))  | 
 | 74 | + | 
 | 75 | +		digest, err := multihash.Encode([]byte("test-digest"), multihash.IDENTITY)  | 
 | 76 | +		blob := types.Blob{Digest: digest, Size: 123}  | 
 | 77 | + | 
 | 78 | +		location := libtestutil.RandomLocationDelegation(t)  | 
 | 79 | +		replicateOk, _, err := c.SpaceBlobReplicate(t.Context(), space.DID(), blob, 5, location)  | 
 | 80 | +		require.NoError(t, err)  | 
 | 81 | + | 
 | 82 | +		require.Len(t, invocations, 1, "expected exactly one invocation to be made")  | 
 | 83 | +		inv := invocations[0]  | 
 | 84 | +		require.Len(t, invokedCapabilities, 1, "expected exactly one capability to be invoked")  | 
 | 85 | +		capability := invokedCapabilities[0]  | 
 | 86 | + | 
 | 87 | +		nb := uhelpers.Must(spaceblobcap.ReplicateCaveatsReader.Read(capability.Nb()))  | 
 | 88 | +		require.Equal(t, blob, nb.Blob, "expected to replicate the correct blob")  | 
 | 89 | +		require.Equal(t, uint(5), nb.Replicas, "expected to replicate the correct number of replicas")  | 
 | 90 | +		require.Equal(t, location.Link(), nb.Site, "expected to replicate from the correct site")  | 
 | 91 | + | 
 | 92 | +		// Get the location claim from the invocation's extra blocks.  | 
 | 93 | +		br, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(inv.Blocks()))  | 
 | 94 | +		require.NoError(t, err)  | 
 | 95 | +		attachedLocation, err := invocation.NewInvocationView(nb.Site, br)  | 
 | 96 | +		require.NoError(t, err)  | 
 | 97 | +		require.Equal(t, location.Root().Bytes(), attachedLocation.Root().Bytes(), "expected the invocation to be attached to the location commitment")  | 
 | 98 | + | 
 | 99 | +		// This is somewhat testing the test, but we want to make sure we get out  | 
 | 100 | +		// whatever the server sent.  | 
 | 101 | +		require.Len(t, replicateOk.Site, 5, "expected to receive the correct number of site promises")  | 
 | 102 | +		for i, p := range replicateOk.Site {  | 
 | 103 | +			require.Equal(t, ".out.ok.site", p.UcanAwait.Selector, "expected to receive the correct selector")  | 
 | 104 | +			expectedSiteDigest, err := multihash.Encode(fmt.Appendf(nil, "test-replicated-site-%d", i), multihash.IDENTITY)  | 
 | 105 | +			require.NoError(t, err)  | 
 | 106 | +			expectedSite := cidlink.Link{Cid: cid.NewCidV1(cid.Raw, expectedSiteDigest)}  | 
 | 107 | +			require.Equal(t, expectedSite, p.UcanAwait.Link, "expected to receive the correct site promise")  | 
 | 108 | +		}  | 
 | 109 | +	})  | 
 | 110 | +}  | 
0 commit comments