github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/repo/refstore_test.go (about)

     1  package repo
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/qri-io/qfs"
     9  	"github.com/qri-io/qfs/muxfs"
    10  	"github.com/qri-io/qri/dsref"
    11  	"github.com/qri-io/qri/event"
    12  	"github.com/qri-io/qri/profile"
    13  	reporef "github.com/qri-io/qri/repo/ref"
    14  )
    15  
    16  var cases = []struct {
    17  	ref         reporef.DatasetRef
    18  	String      string
    19  	Absolute    string
    20  	AliasString string
    21  }{
    22  	{reporef.DatasetRef{
    23  		Peername: "peername",
    24  	}, "peername", "peername", "peername"},
    25  	{reporef.DatasetRef{
    26  		Peername: "peername",
    27  		Name:     "datasetname",
    28  	}, "peername/datasetname", "peername/datasetname", "peername/datasetname"},
    29  
    30  	{reporef.DatasetRef{
    31  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
    32  	}, "", "@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", ""},
    33  	{reporef.DatasetRef{
    34  		Path: "/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1",
    35  	}, "@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", ""},
    36  
    37  	{reporef.DatasetRef{
    38  		Peername:  "peername",
    39  		Name:      "datasetname",
    40  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
    41  	}, "peername/datasetname", "peername/datasetname@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", "peername/datasetname"},
    42  	{reporef.DatasetRef{
    43  		Peername:  "peername",
    44  		Name:      "datasetname",
    45  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
    46  		Path:      "/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1",
    47  	}, "peername/datasetname@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "peername/datasetname@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "peername/datasetname"},
    48  
    49  	{reporef.DatasetRef{
    50  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
    51  		Peername:  "lucille",
    52  	}, "lucille", "lucille@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", "lucille"},
    53  	{reporef.DatasetRef{
    54  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
    55  		Peername:  "lucille",
    56  		Name:      "ball",
    57  		Path:      "/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1",
    58  	}, "lucille/ball@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "lucille/ball@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "lucille/ball"},
    59  
    60  	{reporef.DatasetRef{
    61  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
    62  		Peername:  "bad_name",
    63  	}, "bad_name", "bad_name@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", "bad_name"},
    64  	// TODO - this used to be me@badId, which isn't very useful, but at least provided coding parity
    65  	// might be worth revisiting
    66  	{reporef.DatasetRef{
    67  		ProfileID: profile.IDRawByteString("badID"),
    68  		Peername:  "me",
    69  	}, "me", "me@C6mUq3y", "me"},
    70  }
    71  
    72  func TestDatasetString(t *testing.T) {
    73  	for i, c := range cases {
    74  		if c.ref.String() != c.String {
    75  			t.Errorf("case %d:\n%s\n%s", i, c.ref.String(), c.String)
    76  			continue
    77  		}
    78  	}
    79  }
    80  
    81  func TestDatasetAbsolute(t *testing.T) {
    82  	for i, c := range cases {
    83  		if c.ref.Absolute() != c.Absolute {
    84  			t.Errorf("case %d:\n%s\n%s", i, c.ref.Absolute(), c.Absolute)
    85  			continue
    86  		}
    87  	}
    88  }
    89  
    90  func TestDatasetAliasString(t *testing.T) {
    91  	for i, c := range cases {
    92  		if c.ref.AliasString() != c.AliasString {
    93  			t.Errorf("case %d:\n%s\n%s", i, c.ref.AliasString(), c.AliasString)
    94  			continue
    95  		}
    96  	}
    97  }
    98  
    99  func TestParseDatasetRef(t *testing.T) {
   100  	peernameDatasetRef := reporef.DatasetRef{
   101  		Peername: "peername",
   102  	}
   103  
   104  	nameDatasetRef := reporef.DatasetRef{
   105  		Peername: "peername",
   106  		Name:     "datasetname",
   107  	}
   108  
   109  	peerIDDatasetRef := reporef.DatasetRef{
   110  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   111  	}
   112  
   113  	idNameDatasetRef := reporef.DatasetRef{
   114  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   115  		Name:      "datasetname",
   116  	}
   117  
   118  	idFullDatasetRef := reporef.DatasetRef{
   119  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   120  		Name:      "datasetname",
   121  		Path:      "/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
   122  	}
   123  
   124  	idFullIPFSDatasetRef := reporef.DatasetRef{
   125  		Name:      "datasetname",
   126  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   127  		Path:      "/ipfs/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
   128  	}
   129  
   130  	fullDatasetRef := reporef.DatasetRef{
   131  		Peername: "peername",
   132  		Name:     "datasetname",
   133  		Path:     "/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
   134  	}
   135  
   136  	fullIPFSDatasetRef := reporef.DatasetRef{
   137  		Peername: "peername",
   138  		Name:     "datasetname",
   139  		Path:     "/ipfs/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
   140  	}
   141  
   142  	pathOnlyDatasetRef := reporef.DatasetRef{
   143  		Path: "/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
   144  	}
   145  
   146  	ipfsOnlyDatasetRef := reporef.DatasetRef{
   147  		Path: "/ipfs/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
   148  	}
   149  
   150  	mapDatasetRef := reporef.DatasetRef{
   151  		Path: "/map/QmcQsi93yUryyWvw6mPyDNoKRb7FcBx8QGBAeJ25kXQjnC",
   152  	}
   153  
   154  	cases := []struct {
   155  		input  string
   156  		expect reporef.DatasetRef
   157  		err    string
   158  	}{
   159  		{"", reporef.DatasetRef{}, "repo: empty dataset reference"},
   160  		{"peername/", peernameDatasetRef, ""},
   161  		{"peername", peernameDatasetRef, ""},
   162  
   163  		{"QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/", peerIDDatasetRef, ""},
   164  		{"/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", peerIDDatasetRef, ""},
   165  
   166  		{"peername/datasetname/", nameDatasetRef, ""},
   167  		{"peername/datasetname", nameDatasetRef, ""},
   168  		{"peername/datasetname/@", nameDatasetRef, ""},
   169  		{"peername/datasetname@", nameDatasetRef, ""},
   170  
   171  		{"/datasetname@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", idNameDatasetRef, ""},
   172  		{"/datasetname@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/", idNameDatasetRef, ""},
   173  		{"/datasetname/@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", idNameDatasetRef, ""},
   174  
   175  		{"peername/datasetname/@/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", fullDatasetRef, ""},
   176  		{"peername/datasetname@/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", fullDatasetRef, ""},
   177  
   178  		{"/datasetname@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", idFullDatasetRef, ""},
   179  		{"/datasetname@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", idFullDatasetRef, ""}, // 15
   180  		{"/datasetname/@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/ipfs/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", idFullIPFSDatasetRef, ""},
   181  		{"/datasetname@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", idFullDatasetRef, ""},
   182  
   183  		{"@/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", pathOnlyDatasetRef, ""},
   184  		{"@/ipfs/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", ipfsOnlyDatasetRef, ""},
   185  		{"@/map/QmcQsi93yUryyWvw6mPyDNoKRb7FcBx8QGBAeJ25kXQjnC", mapDatasetRef, ""},
   186  
   187  		{"peername/datasetname/@/network/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/junk/junk/...", fullDatasetRef, ""},
   188  		{"peername/datasetname/@/ipfs/QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/junk/junk/...", fullIPFSDatasetRef, ""},
   189  	}
   190  
   191  	for i, c := range cases {
   192  		got, err := ParseDatasetRef(c.input)
   193  		if !(err == nil && c.err == "" || err != nil && err.Error() == c.err) {
   194  			t.Errorf("case %d error mismatch. expected: '%s', got: '%s'", i, c.err, err)
   195  			continue
   196  		}
   197  
   198  		if err := CompareDatasetRef(got, c.expect); err != nil {
   199  			t.Errorf("case %d: %s", i, err.Error())
   200  		}
   201  	}
   202  }
   203  
   204  func TestMatch(t *testing.T) {
   205  	cases := []struct {
   206  		a, b  string
   207  		match bool
   208  	}{
   209  		{"a/b@/b/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "a/b@/b/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", true},
   210  		{"a/b@/b/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "a/b@/b/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", true},
   211  		{"QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/b@/b/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/b@/b/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", true},
   212  
   213  		{"a/different_name@/b/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", "a/b@/b/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", true},
   214  		{"different_peername/b@/b/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", "a/b@/b/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", true},
   215  		{"different_peername/b@/b/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", "QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/b@/b/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", true},
   216  	}
   217  
   218  	for i, c := range cases {
   219  		a, err := ParseDatasetRef(c.a)
   220  		if err != nil {
   221  			t.Errorf("case %d error parsing dataset ref a: %s", i, err.Error())
   222  			continue
   223  		}
   224  		b, err := ParseDatasetRef(c.b)
   225  		if err != nil {
   226  			t.Errorf("case %d error parsing dataset ref b: %s", i, err.Error())
   227  			continue
   228  		}
   229  
   230  		gotA := a.Match(b)
   231  		if gotA != c.match {
   232  			t.Errorf("case %d a.Match", i)
   233  			continue
   234  		}
   235  
   236  		gotB := b.Match(a)
   237  		if gotB != c.match {
   238  			t.Errorf("case %d b.Match", i)
   239  			continue
   240  		}
   241  	}
   242  }
   243  
   244  func TestEqual(t *testing.T) {
   245  	cases := []struct {
   246  		a, b  string
   247  		equal bool
   248  	}{
   249  		{"a/b@/b/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "a/b@/b/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", true},
   250  		{"a/b@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "a/b@/ipfs/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", false},
   251  
   252  		{"QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1/b@/b/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1/b@/b/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", true},
   253  		{"QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1/b@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1/b@/ipfs/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", false},
   254  
   255  		{"a/different_name@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "a/b@/ipfs/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", false},
   256  		{"different_peername/b@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "a/b@/ipfs/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", false},
   257  
   258  		{"QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL/different_name@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL/b@/ipfs/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", false},
   259  		{"QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL/b@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "a/b@/ipfs/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", false},
   260  		{"QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL/b@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1/b@/ipfs/QmdJgfxj4rocm88PLeEididS7V2cc9nQosA46RpvAnWvDL", false},
   261  	}
   262  
   263  	for i, c := range cases {
   264  		a, err := ParseDatasetRef(c.a)
   265  		if err != nil {
   266  			t.Errorf("case %d error parsing dataset ref a: %s", i, err.Error())
   267  			continue
   268  		}
   269  		b, err := ParseDatasetRef(c.b)
   270  		if err != nil {
   271  			t.Errorf("case %d error parsing dataset ref b: %s", i, err.Error())
   272  			continue
   273  		}
   274  
   275  		gotA := a.Equal(b)
   276  		if gotA != c.equal {
   277  			t.Errorf("case %d a.Equal", i)
   278  			continue
   279  		}
   280  
   281  		gotB := b.Equal(a)
   282  		if gotB != c.equal {
   283  			t.Errorf("case %d b.Equal", i)
   284  			continue
   285  		}
   286  	}
   287  }
   288  
   289  func TestIsEmpty(t *testing.T) {
   290  	cases := []struct {
   291  		ref   reporef.DatasetRef
   292  		empty bool
   293  	}{
   294  		{reporef.DatasetRef{}, true},
   295  		{reporef.DatasetRef{Peername: "a"}, false},
   296  		{reporef.DatasetRef{Name: "a"}, false},
   297  		{reporef.DatasetRef{Path: "a"}, false},
   298  		{reporef.DatasetRef{ProfileID: profile.IDRawByteString("a")}, false},
   299  	}
   300  
   301  	for i, c := range cases {
   302  		got := c.ref.IsEmpty()
   303  		if got != c.empty {
   304  			t.Errorf("case %d: %s", i, c.ref)
   305  			continue
   306  		}
   307  	}
   308  }
   309  
   310  func TestCompareDatasets(t *testing.T) {
   311  	cases := []struct {
   312  		a, b reporef.DatasetRef
   313  		err  string
   314  	}{
   315  		{reporef.DatasetRef{}, reporef.DatasetRef{}, ""},
   316  		{reporef.DatasetRef{Name: "a"}, reporef.DatasetRef{}, "Name mismatch. a != "},
   317  		{reporef.DatasetRef{Peername: "a"}, reporef.DatasetRef{}, "Peername mismatch. a != "},
   318  		{reporef.DatasetRef{Path: "a"}, reporef.DatasetRef{}, "Path mismatch. a != "},
   319  		{reporef.DatasetRef{ProfileID: profile.IDB58MustDecode("QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1")}, reporef.DatasetRef{}, "PeerID mismatch. QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1 != "},
   320  	}
   321  
   322  	for i, c := range cases {
   323  		err := CompareDatasetRef(c.a, c.b)
   324  		if !(err == nil && c.err == "" || err != nil && err.Error() == c.err) {
   325  			t.Errorf("case %d error mistmatch. expected: '%s', got: '%s'", i, c.err, err)
   326  			continue
   327  		}
   328  	}
   329  }
   330  
   331  func TestCanonicalizeDatasetRef(t *testing.T) {
   332  	ctx := context.Background()
   333  	lucille := &profile.Profile{ID: profile.IDRawByteString("a"), Peername: "lucille", PrivKey: privKey}
   334  	carla := &profile.Profile{ID: profile.IDRawByteString("b"), Peername: "carla"}
   335  
   336  	fs, err := muxfs.New(ctx, []qfs.Config{
   337  		{Type: "mem"},
   338  	})
   339  
   340  	if err != nil {
   341  		t.Fatal(err)
   342  	}
   343  
   344  	memRepo, err := NewMemRepoWithProfile(ctx, lucille, fs, event.NilBus)
   345  	if err != nil {
   346  		t.Errorf("error allocating mem repo: %s", err.Error())
   347  		return
   348  	}
   349  	rs := memRepo.MemRefstore
   350  	for _, r := range []reporef.DatasetRef{
   351  		{ProfileID: lucille.ID, Peername: "lucille", Name: "foo", Path: "/ipfs/QmTest"},
   352  		{ProfileID: carla.ID, Peername: carla.Peername, Name: "hockey_stats", Path: "/ipfs/QmTest2"},
   353  		{ProfileID: lucille.ID, Peername: "lucille", Name: "ball", Path: "/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1"},
   354  	} {
   355  		if err := rs.PutRef(r); err != nil {
   356  			t.Fatal(err)
   357  		}
   358  	}
   359  
   360  	// TODO - this points to a problem in our thinking. placing a reference in the refstore doesn't automatically
   361  	// add it to the profile store. This can lead to bugs higher up the stack, but points to an architectural challenge:
   362  	// the profile store is supposed to be the source of truth for profiles, and that isn't being enforced here.
   363  	// Moreover, what's the correct thing to do when adding a ref to the refstore who's profile information is not in the profile
   364  	// store, or worse, doesn't match the profile store?
   365  	// There's an implied hierarchy of profile store > refstore that isn't being enforced in code, and should be.
   366  	if err := memRepo.Profiles().PutProfile(ctx, carla); err != nil {
   367  		t.Fatal(err.Error())
   368  	}
   369  
   370  	cases := []struct {
   371  		input  string
   372  		expect string
   373  		err    string
   374  	}{
   375  		{"me/foo", "lucille/foo@/ipfs/QmTest", ""},
   376  		{"carla/hockey_stats", "carla/hockey_stats@/ipfs/QmTest2", ""},
   377  		{"lucille/ball", "lucille/ball@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", ""},
   378  		{"me/ball@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "lucille/ball@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", ""},
   379  		{"@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "lucille/ball@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", ""},
   380  		{"renamed/ball@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", "lucille/ball@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", ""},
   381  	}
   382  
   383  	for i, c := range cases {
   384  		ref, err := ParseDatasetRef(c.input)
   385  		if err != nil {
   386  			t.Errorf("case %d unexpected dataset ref parse error: %s", i, err.Error())
   387  			continue
   388  		}
   389  		got := &ref
   390  
   391  		err = canonicalizeDatasetRef(ctx, memRepo, got)
   392  		if !(err == nil && c.err == "" || err != nil && err.Error() == c.err) {
   393  			t.Errorf("case %d error mismatch. expected: '%s', got: '%s'", i, c.err, err)
   394  			continue
   395  		}
   396  
   397  		if got.String() != c.expect {
   398  			t.Errorf("case %d expected: %s, got: %s", i, c.expect, got)
   399  			continue
   400  		}
   401  	}
   402  }
   403  
   404  func TestCanonicalizeDatasetRefFSI(t *testing.T) {
   405  	ctx, cancel := context.WithCancel(context.Background())
   406  	defer cancel()
   407  
   408  	peer := "lucille"
   409  	prof := &profile.Profile{ID: profile.IDRawByteString("a"), Peername: peer, PrivKey: privKey}
   410  	fs, err := muxfs.New(ctx, []qfs.Config{
   411  		{Type: "mem"},
   412  	})
   413  	if err != nil {
   414  		t.Fatal(err)
   415  	}
   416  
   417  	memRepo, err := NewMemRepoWithProfile(ctx, prof, fs, event.NilBus)
   418  	if err != nil {
   419  		t.Fatal(err)
   420  	}
   421  	id := prof.ID
   422  
   423  	rs := memRepo.MemRefstore
   424  	rs.PutRef(reporef.DatasetRef{ProfileID: id, Peername: peer, Name: "apple", Path: "/ipfs/QmTest1"})
   425  	rs.PutRef(reporef.DatasetRef{ProfileID: id, Peername: peer, Name: "banana", Path: "/ipfs/QmTest2", FSIPath: "/path/to/dataset"})
   426  
   427  	goodCases := []struct {
   428  		input      string
   429  		expectPath string
   430  		expectFSI  string
   431  	}{
   432  		{"me/apple", "/ipfs/QmTest1", ""},
   433  		{"me/apple@/ipfs/QmTest1", "/ipfs/QmTest1", ""},
   434  		{"me/apple@/ipfs/QmTest1Prev", "/ipfs/QmTest1Prev", ""},
   435  		{"me/banana", "/ipfs/QmTest2", "/path/to/dataset"},
   436  		{"me/banana@/ipfs/QmTest2", "/ipfs/QmTest2", "/path/to/dataset"},
   437  		{"me/banana@/ipfs/QmTest2Prev", "/ipfs/QmTest2Prev", "/path/to/dataset"},
   438  	}
   439  
   440  	for i, c := range goodCases {
   441  		ref, err := ParseDatasetRef(c.input)
   442  		if err != nil {
   443  			t.Errorf("case %d unexpected dataset ref parse error: %s", i, err)
   444  			continue
   445  		}
   446  		got := &ref
   447  
   448  		err = canonicalizeDatasetRef(ctx, memRepo, got)
   449  		if err != nil {
   450  			t.Errorf("case %d got error: %s", i, err)
   451  			continue
   452  		}
   453  
   454  		if got.Path != c.expectPath {
   455  			t.Errorf("case %d expected path: %s, got: %s", i, c.expectPath, got.Path)
   456  			continue
   457  		}
   458  		if got.FSIPath != c.expectFSI {
   459  			t.Errorf("case %d expected FSI path: %s, got: %s", i, c.expectFSI, got.FSIPath)
   460  			continue
   461  		}
   462  	}
   463  }
   464  
   465  func TestCanonicalizeProfile(t *testing.T) {
   466  	ctx, cancel := context.WithCancel(context.Background())
   467  	defer cancel()
   468  
   469  	prof := &profile.Profile{Peername: "lucille", ID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"), PrivKey: privKey}
   470  	fs, err := muxfs.New(ctx, []qfs.Config{
   471  		{Type: "mem"},
   472  	})
   473  	if err != nil {
   474  		t.Fatal(err)
   475  	}
   476  
   477  	repo, err := NewMemRepoWithProfile(ctx, prof, fs, event.NilBus)
   478  	if err != nil {
   479  		t.Errorf("error allocating mem repo: %s", err.Error())
   480  		return
   481  	}
   482  
   483  	lucille := reporef.DatasetRef{
   484  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   485  		Peername:  "lucille",
   486  	}
   487  
   488  	ball := reporef.DatasetRef{
   489  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   490  		Peername:  "lucille",
   491  		Name:      "ball",
   492  		Path:      "/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1",
   493  	}
   494  
   495  	ballPeer := reporef.DatasetRef{
   496  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   497  		Peername:  "lucille",
   498  		Name:      "ball",
   499  	}
   500  
   501  	renamePeerName := reporef.DatasetRef{
   502  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   503  		Peername:  "lucy",
   504  		Name:      "ball",
   505  		Path:      "/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1",
   506  	}
   507  
   508  	badProfileIDGoodName := reporef.DatasetRef{
   509  		ProfileID: profile.IDRawByteString("badID"),
   510  		Peername:  "me",
   511  	}
   512  
   513  	cases := []struct {
   514  		input        string
   515  		inputDataset reporef.DatasetRef
   516  		expect       reporef.DatasetRef
   517  		err          string
   518  	}{
   519  		{"me", reporef.DatasetRef{}, lucille, ""},
   520  		{"lucille", reporef.DatasetRef{}, lucille, ""},
   521  		{"QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", reporef.DatasetRef{}, lucille, ""},
   522  		{"me/ball@/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", reporef.DatasetRef{}, ball, ""},
   523  		{"/ball@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", reporef.DatasetRef{}, ball, ""},
   524  		{"/ball@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y", reporef.DatasetRef{}, ballPeer, ""},
   525  		{"me/ball", reporef.DatasetRef{}, ballPeer, ""},
   526  		{"", badProfileIDGoodName, lucille, ""},
   527  		{"/ball@QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1", renamePeerName, ball, ""},
   528  		{"", reporef.DatasetRef{}, reporef.DatasetRef{}, ""},
   529  	}
   530  
   531  	for i, c := range cases {
   532  		var (
   533  			ref reporef.DatasetRef
   534  			err error
   535  		)
   536  		if c.input != "" {
   537  			ref, err = ParseDatasetRef(c.input)
   538  			if err != nil {
   539  				t.Errorf("case %d unexpected dataset ref parse error: %s", i, err.Error())
   540  				continue
   541  			}
   542  		} else {
   543  			ref = c.inputDataset
   544  		}
   545  		got := &ref
   546  
   547  		err = canonicalizeProfile(ctx, repo, got)
   548  		if !(err == nil && c.err == "" || err != nil && err.Error() == c.err) {
   549  			t.Errorf("case %d error mismatch. expected: '%s', got: '%s'", i, c.err, err)
   550  			continue
   551  		}
   552  
   553  		if c.err == "" {
   554  			if got.ProfileID != c.expect.ProfileID {
   555  				t.Errorf("case %d ProfileID mismatch. expected: '%s', got: '%s'", i, c.expect.ProfileID, got.ProfileID)
   556  			}
   557  			if got.Peername != c.expect.Peername {
   558  				t.Errorf("case %d Peername mismatch. expected: '%s', got: '%s'", i, c.expect.Peername, got.Peername)
   559  			}
   560  			if got.Name != c.expect.Name {
   561  				t.Errorf("case %d Name mismatch. expected: '%s', got: '%s'", i, c.expect.Name, got.Name)
   562  			}
   563  			if got.Path != c.expect.Path {
   564  				t.Errorf("case %d Path mismatch. expected: '%s', got: '%s'", i, c.expect.Path, got.Path)
   565  			}
   566  		}
   567  	}
   568  }
   569  
   570  func TestConvertToDsref(t *testing.T) {
   571  	ref := reporef.DatasetRef{
   572  		ProfileID: profile.IDB58MustDecode("QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y"),
   573  		Peername:  "lucy",
   574  		Name:      "ball",
   575  		Path:      "/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1",
   576  	}
   577  	expect := dsref.Ref{
   578  		Username:  "lucy",
   579  		Name:      "ball",
   580  		ProfileID: "QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
   581  		Path:      "/ipfs/QmRdexT18WuAKVX3vPusqmJTWLeNSeJgjmMbaF5QLGHna1",
   582  	}
   583  	got := reporef.ConvertToDsref(ref)
   584  
   585  	if diff := cmp.Diff(expect, got); diff != "" {
   586  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   587  	}
   588  }