master
go 743 lines 18.8 KB
Raw
1 package tests
2
3 import (
4 "context"
5 "math"
6 "strings"
7 "testing"
8
9 "github.com/ipfs/boxo/path"
10 "github.com/ipfs/go-cid"
11 ipldcbor "github.com/ipfs/go-ipld-cbor"
12 ipld "github.com/ipfs/go-ipld-format"
13 iface "github.com/ipfs/kubo/core/coreiface"
14 opt "github.com/ipfs/kubo/core/coreiface/options"
15 "github.com/stretchr/testify/require"
16 )
17
18 func (tp *TestSuite) TestPin(t *testing.T) {
19 tp.hasApi(t, func(api iface.CoreAPI) error {
20 if api.Pin() == nil {
21 return errAPINotImplemented
22 }
23 return nil
24 })
25
26 t.Run("TestPinAdd", tp.TestPinAdd)
27 t.Run("TestPinSimple", tp.TestPinSimple)
28 t.Run("TestPinRecursive", tp.TestPinRecursive)
29 t.Run("TestPinLsIndirect", tp.TestPinLsIndirect)
30 t.Run("TestPinLsPrecedence", tp.TestPinLsPrecedence)
31 t.Run("TestPinIsPinned", tp.TestPinIsPinned)
32 t.Run("TestPinNames", tp.TestPinNames)
33 }
34
35 func (tp *TestSuite) TestPinAdd(t *testing.T) {
36 ctx := t.Context()
37 api, err := tp.makeAPI(t, ctx)
38 if err != nil {
39 t.Fatal(err)
40 }
41
42 p, err := api.Unixfs().Add(ctx, strFile("foo")())
43 if err != nil {
44 t.Fatal(err)
45 }
46
47 err = api.Pin().Add(ctx, p)
48 if err != nil {
49 t.Fatal(err)
50 }
51 }
52
53 func (tp *TestSuite) TestPinSimple(t *testing.T) {
54 ctx := t.Context()
55 api, err := tp.makeAPI(t, ctx)
56 if err != nil {
57 t.Fatal(err)
58 }
59
60 p, err := api.Unixfs().Add(ctx, strFile("foo")())
61 if err != nil {
62 t.Fatal(err)
63 }
64
65 err = api.Pin().Add(ctx, p)
66 if err != nil {
67 t.Fatal(err)
68 }
69
70 list, err := accPins(ctx, api)
71 if err != nil {
72 t.Fatal(err)
73 }
74
75 if len(list) != 1 {
76 t.Errorf("unexpected pin list len: %d", len(list))
77 }
78
79 if list[0].Path().RootCid().String() != p.RootCid().String() {
80 t.Error("paths don't match")
81 }
82
83 if list[0].Type() != "recursive" {
84 t.Error("unexpected pin type")
85 }
86
87 assertIsPinned(t, ctx, api, p, "recursive")
88
89 err = api.Pin().Rm(ctx, p)
90 if err != nil {
91 t.Fatal(err)
92 }
93
94 list, err = accPins(ctx, api)
95 if err != nil {
96 t.Fatal(err)
97 }
98
99 if len(list) != 0 {
100 t.Errorf("unexpected pin list len: %d", len(list))
101 }
102 }
103
104 func (tp *TestSuite) TestPinRecursive(t *testing.T) {
105 ctx := t.Context()
106 api, err := tp.makeAPI(t, ctx)
107 if err != nil {
108 t.Fatal(err)
109 }
110
111 p0, err := api.Unixfs().Add(ctx, strFile("foo")())
112 if err != nil {
113 t.Fatal(err)
114 }
115
116 p1, err := api.Unixfs().Add(ctx, strFile("bar")())
117 if err != nil {
118 t.Fatal(err)
119 }
120
121 nd2, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+p0.RootCid().String()+`"}}`), math.MaxUint64, -1)
122 if err != nil {
123 t.Fatal(err)
124 }
125
126 nd3, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+p1.RootCid().String()+`"}}`), math.MaxUint64, -1)
127 if err != nil {
128 t.Fatal(err)
129 }
130
131 if err := api.Dag().AddMany(ctx, []ipld.Node{nd2, nd3}); err != nil {
132 t.Fatal(err)
133 }
134
135 err = api.Pin().Add(ctx, path.FromCid(nd2.Cid()))
136 if err != nil {
137 t.Fatal(err)
138 }
139
140 err = api.Pin().Add(ctx, path.FromCid(nd3.Cid()), opt.Pin.Recursive(false))
141 if err != nil {
142 t.Fatal(err)
143 }
144
145 list, err := accPins(ctx, api)
146 if err != nil {
147 t.Fatal(err)
148 }
149
150 if len(list) != 3 {
151 t.Errorf("unexpected pin list len: %d", len(list))
152 }
153
154 list, err = accPins(ctx, api, opt.Pin.Ls.Direct())
155 if err != nil {
156 t.Fatal(err)
157 }
158
159 if len(list) != 1 {
160 t.Errorf("unexpected pin list len: %d", len(list))
161 }
162
163 if list[0].Path().String() != path.FromCid(nd3.Cid()).String() {
164 t.Errorf("unexpected path, %s != %s", list[0].Path().String(), path.FromCid(nd3.Cid()).String())
165 }
166
167 list, err = accPins(ctx, api, opt.Pin.Ls.Recursive())
168 if err != nil {
169 t.Fatal(err)
170 }
171
172 if len(list) != 1 {
173 t.Errorf("unexpected pin list len: %d", len(list))
174 }
175
176 if list[0].Path().String() != path.FromCid(nd2.Cid()).String() {
177 t.Errorf("unexpected path, %s != %s", list[0].Path().String(), path.FromCid(nd2.Cid()).String())
178 }
179
180 list, err = accPins(ctx, api, opt.Pin.Ls.Indirect())
181 if err != nil {
182 t.Fatal(err)
183 }
184
185 if len(list) != 1 {
186 t.Errorf("unexpected pin list len: %d", len(list))
187 }
188
189 if list[0].Path().RootCid().String() != p0.RootCid().String() {
190 t.Errorf("unexpected path, %s != %s", list[0].Path().RootCid().String(), p0.RootCid().String())
191 }
192
193 res, err := api.Pin().Verify(ctx)
194 if err != nil {
195 t.Fatal(err)
196 }
197 n := 0
198 for r := range res {
199 if err := r.Err(); err != nil {
200 t.Error(err)
201 }
202 if !r.Ok() {
203 t.Error("expected pin to be ok")
204 }
205 n++
206 }
207
208 if n != 1 {
209 t.Errorf("unexpected verify result count: %d", n)
210 }
211
212 // TODO: figure out a way to test verify without touching IpfsNode
213 /*
214 err = api.Block().Rm(ctx, p0, opt.Block.Force(true))
215 if err != nil {
216 t.Fatal(err)
217 }
218
219 res, err = api.Pin().Verify(ctx)
220 if err != nil {
221 t.Fatal(err)
222 }
223 n = 0
224 for r := range res {
225 if r.Ok() {
226 t.Error("expected pin to not be ok")
227 }
228
229 if len(r.BadNodes()) != 1 {
230 t.Fatalf("unexpected badNodes len")
231 }
232
233 if r.BadNodes()[0].Path().Cid().String() != p0.Cid().String() {
234 t.Error("unexpected badNode path")
235 }
236
237 if r.BadNodes()[0].Err().Error() != "merkledag: not found" {
238 t.Errorf("unexpected badNode error: %s", r.BadNodes()[0].Err().Error())
239 }
240 n++
241 }
242
243 if n != 1 {
244 t.Errorf("unexpected verify result count: %d", n)
245 }
246 */
247 }
248
249 // TestPinLsIndirect verifies that indirect nodes are listed by pin ls even if a parent node is directly pinned
250 func (tp *TestSuite) TestPinLsIndirect(t *testing.T) {
251 ctx := t.Context()
252 api, err := tp.makeAPI(t, ctx)
253 if err != nil {
254 t.Fatal(err)
255 }
256
257 leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "foo")
258
259 err = api.Pin().Add(ctx, path.FromCid(grandparent.Cid()))
260 if err != nil {
261 t.Fatal(err)
262 }
263
264 err = api.Pin().Add(ctx, path.FromCid(parent.Cid()), opt.Pin.Recursive(false))
265 if err != nil {
266 t.Fatal(err)
267 }
268
269 assertPinTypes(t, ctx, api, []cidContainer{grandparent}, []cidContainer{parent}, []cidContainer{leaf})
270 }
271
272 // TestPinLsPrecedence verifies the precedence of pins (recursive > direct > indirect)
273 func (tp *TestSuite) TestPinLsPrecedence(t *testing.T) {
274 // Testing precedence of recursive, direct and indirect pins
275 // Results should be recursive > indirect, direct > indirect, and recursive > direct
276
277 t.Run("TestPinLsPredenceRecursiveIndirect", tp.TestPinLsPredenceRecursiveIndirect)
278 t.Run("TestPinLsPrecedenceDirectIndirect", tp.TestPinLsPrecedenceDirectIndirect)
279 t.Run("TestPinLsPrecedenceRecursiveDirect", tp.TestPinLsPrecedenceRecursiveDirect)
280 }
281
282 func (tp *TestSuite) TestPinLsPredenceRecursiveIndirect(t *testing.T) {
283 ctx := t.Context()
284 api, err := tp.makeAPI(t, ctx)
285 if err != nil {
286 t.Fatal(err)
287 }
288
289 // Test recursive > indirect
290 leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "recursive > indirect")
291
292 err = api.Pin().Add(ctx, path.FromCid(grandparent.Cid()))
293 if err != nil {
294 t.Fatal(err)
295 }
296
297 err = api.Pin().Add(ctx, path.FromCid(parent.Cid()))
298 if err != nil {
299 t.Fatal(err)
300 }
301
302 assertPinTypes(t, ctx, api, []cidContainer{grandparent, parent}, []cidContainer{}, []cidContainer{leaf})
303 }
304
305 func (tp *TestSuite) TestPinLsPrecedenceDirectIndirect(t *testing.T) {
306 ctx := t.Context()
307 api, err := tp.makeAPI(t, ctx)
308 if err != nil {
309 t.Fatal(err)
310 }
311
312 // Test direct > indirect
313 leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "direct > indirect")
314
315 err = api.Pin().Add(ctx, path.FromCid(grandparent.Cid()))
316 if err != nil {
317 t.Fatal(err)
318 }
319
320 err = api.Pin().Add(ctx, path.FromCid(parent.Cid()), opt.Pin.Recursive(false))
321 if err != nil {
322 t.Fatal(err)
323 }
324
325 assertPinTypes(t, ctx, api, []cidContainer{grandparent}, []cidContainer{parent}, []cidContainer{leaf})
326 }
327
328 func (tp *TestSuite) TestPinLsPrecedenceRecursiveDirect(t *testing.T) {
329 ctx := t.Context()
330 api, err := tp.makeAPI(t, ctx)
331 if err != nil {
332 t.Fatal(err)
333 }
334
335 // Test recursive > direct
336 leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "recursive + direct = error")
337
338 err = api.Pin().Add(ctx, path.FromCid(parent.Cid()))
339 if err != nil {
340 t.Fatal(err)
341 }
342
343 err = api.Pin().Add(ctx, path.FromCid(parent.Cid()), opt.Pin.Recursive(false))
344 if err == nil {
345 t.Fatal("expected error directly pinning a recursively pinned node")
346 }
347
348 assertPinTypes(t, ctx, api, []cidContainer{parent}, []cidContainer{}, []cidContainer{leaf})
349
350 err = api.Pin().Add(ctx, path.FromCid(grandparent.Cid()), opt.Pin.Recursive(false))
351 if err != nil {
352 t.Fatal(err)
353 }
354
355 err = api.Pin().Add(ctx, path.FromCid(grandparent.Cid()))
356 if err != nil {
357 t.Fatal(err)
358 }
359
360 assertPinTypes(t, ctx, api, []cidContainer{grandparent, parent}, []cidContainer{}, []cidContainer{leaf})
361 }
362
363 func (tp *TestSuite) TestPinIsPinned(t *testing.T) {
364 ctx := t.Context()
365 api, err := tp.makeAPI(t, ctx)
366 if err != nil {
367 t.Fatal(err)
368 }
369
370 leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "foofoo")
371
372 assertNotPinned(t, ctx, api, newIPLDPath(t, grandparent.Cid()))
373 assertNotPinned(t, ctx, api, newIPLDPath(t, parent.Cid()))
374 assertNotPinned(t, ctx, api, newIPLDPath(t, leaf.Cid()))
375
376 err = api.Pin().Add(ctx, newIPLDPath(t, parent.Cid()), opt.Pin.Recursive(true))
377 if err != nil {
378 t.Fatal(err)
379 }
380
381 assertNotPinned(t, ctx, api, newIPLDPath(t, grandparent.Cid()))
382 assertIsPinned(t, ctx, api, newIPLDPath(t, parent.Cid()), "recursive")
383 assertIsPinned(t, ctx, api, newIPLDPath(t, leaf.Cid()), "indirect")
384
385 err = api.Pin().Add(ctx, newIPLDPath(t, grandparent.Cid()), opt.Pin.Recursive(false))
386 if err != nil {
387 t.Fatal(err)
388 }
389
390 assertIsPinned(t, ctx, api, newIPLDPath(t, grandparent.Cid()), "direct")
391 assertIsPinned(t, ctx, api, newIPLDPath(t, parent.Cid()), "recursive")
392 assertIsPinned(t, ctx, api, newIPLDPath(t, leaf.Cid()), "indirect")
393 }
394
395 type cidContainer interface {
396 Cid() cid.Cid
397 }
398
399 type immutablePathCidContainer struct {
400 path.ImmutablePath
401 }
402
403 func (i immutablePathCidContainer) Cid() cid.Cid {
404 return i.RootCid()
405 }
406
407 func getThreeChainedNodes(t *testing.T, ctx context.Context, api iface.CoreAPI, leafData string) (cidContainer, cidContainer, cidContainer) {
408 leaf, err := api.Unixfs().Add(ctx, strFile(leafData)())
409 if err != nil {
410 t.Fatal(err)
411 }
412
413 parent, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+leaf.RootCid().String()+`"}}`), math.MaxUint64, -1)
414 if err != nil {
415 t.Fatal(err)
416 }
417
418 grandparent, err := ipldcbor.FromJSON(strings.NewReader(`{"lnk": {"/": "`+parent.Cid().String()+`"}}`), math.MaxUint64, -1)
419 if err != nil {
420 t.Fatal(err)
421 }
422
423 if err := api.Dag().AddMany(ctx, []ipld.Node{parent, grandparent}); err != nil {
424 t.Fatal(err)
425 }
426
427 return immutablePathCidContainer{leaf}, parent, grandparent
428 }
429
430 func assertPinTypes(t *testing.T, ctx context.Context, api iface.CoreAPI, recursive, direct, indirect []cidContainer) {
431 assertPinLsAllConsistency(t, ctx, api)
432
433 list, err := accPins(ctx, api, opt.Pin.Ls.Recursive())
434 if err != nil {
435 t.Fatal(err)
436 }
437
438 assertPinCids(t, list, recursive...)
439
440 list, err = accPins(ctx, api, opt.Pin.Ls.Direct())
441 if err != nil {
442 t.Fatal(err)
443 }
444
445 assertPinCids(t, list, direct...)
446
447 list, err = accPins(ctx, api, opt.Pin.Ls.Indirect())
448 if err != nil {
449 t.Fatal(err)
450 }
451
452 assertPinCids(t, list, indirect...)
453 }
454
455 // assertPinCids verifies that the pins match the expected cids
456 func assertPinCids(t *testing.T, pins []iface.Pin, cids ...cidContainer) {
457 t.Helper()
458
459 if expected, actual := len(cids), len(pins); expected != actual {
460 t.Fatalf("expected pin list to have len %d, was %d", expected, actual)
461 }
462
463 cSet := cid.NewSet()
464 for _, c := range cids {
465 cSet.Add(c.Cid())
466 }
467
468 valid := true
469 for _, p := range pins {
470 c := p.Path().RootCid()
471 if cSet.Has(c) {
472 cSet.Remove(c)
473 } else {
474 valid = false
475 break
476 }
477 }
478
479 valid = valid && cSet.Len() == 0
480
481 if !valid {
482 pinStrs := make([]string, len(pins))
483 for i, p := range pins {
484 pinStrs[i] = p.Path().RootCid().String()
485 }
486 pathStrs := make([]string, len(cids))
487 for i, c := range cids {
488 pathStrs[i] = c.Cid().String()
489 }
490 t.Fatalf("expected: %s \nactual: %s", strings.Join(pathStrs, ", "), strings.Join(pinStrs, ", "))
491 }
492 }
493
494 // assertPinLsAllConsistency verifies that listing all pins gives the same result as listing the pin types individually
495 func assertPinLsAllConsistency(t *testing.T, ctx context.Context, api iface.CoreAPI) {
496 t.Helper()
497 allPins, err := accPins(ctx, api)
498 if err != nil {
499 t.Fatal(err)
500 }
501
502 type pinTypeProps struct {
503 *cid.Set
504 opt.PinLsOption
505 }
506
507 all, recursive, direct, indirect := cid.NewSet(), cid.NewSet(), cid.NewSet(), cid.NewSet()
508 typeMap := map[string]*pinTypeProps{
509 "recursive": {recursive, opt.Pin.Ls.Recursive()},
510 "direct": {direct, opt.Pin.Ls.Direct()},
511 "indirect": {indirect, opt.Pin.Ls.Indirect()},
512 }
513
514 for _, p := range allPins {
515 if !all.Visit(p.Path().RootCid()) {
516 t.Fatalf("pin ls returned the same cid multiple times")
517 }
518
519 typeStr := p.Type()
520 if typeSet, ok := typeMap[p.Type()]; ok {
521 typeSet.Add(p.Path().RootCid())
522 } else {
523 t.Fatalf("unknown pin type: %s", typeStr)
524 }
525 }
526
527 for typeStr, pinProps := range typeMap {
528 pins, err := accPins(ctx, api, pinProps.PinLsOption)
529 if err != nil {
530 t.Fatal(err)
531 }
532
533 if expected, actual := len(pins), pinProps.Set.Len(); expected != actual {
534 t.Fatalf("pin ls all has %d pins of type %s, but pin ls for the type has %d", expected, typeStr, actual)
535 }
536
537 for _, p := range pins {
538 if pinType := p.Type(); pinType != typeStr {
539 t.Fatalf("returned wrong pin type: expected %s, got %s", typeStr, pinType)
540 }
541
542 if c := p.Path().RootCid(); !pinProps.Has(c) {
543 t.Fatalf("%s expected to be in pin ls all as type %s", c.String(), typeStr)
544 }
545 }
546 }
547 }
548
549 func assertIsPinned(t *testing.T, ctx context.Context, api iface.CoreAPI, p path.Path, typeStr string) {
550 t.Helper()
551 withType, err := opt.Pin.IsPinned.Type(typeStr)
552 if err != nil {
553 t.Fatal("unhandled pin type")
554 }
555
556 whyPinned, pinned, err := api.Pin().IsPinned(ctx, p, withType)
557 if err != nil {
558 t.Fatal(err)
559 }
560
561 if !pinned {
562 t.Fatalf("%s expected to be pinned with type %s", p, typeStr)
563 }
564
565 switch typeStr {
566 case "recursive", "direct":
567 if typeStr != whyPinned {
568 t.Fatalf("reason for pinning expected to be %s for %s, got %s", typeStr, p, whyPinned)
569 }
570 case "indirect":
571 if whyPinned == "" {
572 t.Fatalf("expected to have a pin reason for %s", p)
573 }
574 }
575 }
576
577 func (tp *TestSuite) TestPinNames(t *testing.T) {
578 ctx := t.Context()
579 api, err := tp.makeAPI(t, ctx)
580 require.NoError(t, err)
581
582 // Create test content
583 p1, err := api.Unixfs().Add(ctx, strFile("content1")())
584 require.NoError(t, err)
585
586 p2, err := api.Unixfs().Add(ctx, strFile("content2")())
587 require.NoError(t, err)
588
589 p3, err := api.Unixfs().Add(ctx, strFile("content3")())
590 require.NoError(t, err)
591
592 p4, err := api.Unixfs().Add(ctx, strFile("content4")())
593 require.NoError(t, err)
594
595 // Test 1: Pin with name
596 err = api.Pin().Add(ctx, p1, opt.Pin.Name("test-pin-1"))
597 require.NoError(t, err, "failed to add pin with name")
598
599 // Test 2: Pin without name
600 err = api.Pin().Add(ctx, p2)
601 require.NoError(t, err, "failed to add pin without name")
602
603 // Test 3: List pins with detailed option to get names
604 pins := make(chan iface.Pin)
605 go func() {
606 err = api.Pin().Ls(ctx, pins, opt.Pin.Ls.Detailed(true))
607 }()
608
609 pinMap := make(map[string]string)
610 for pin := range pins {
611 pinMap[pin.Path().String()] = pin.Name()
612 }
613 require.NoError(t, err, "failed to list pins with names")
614
615 // Verify pin names
616 name1, ok := pinMap[p1.String()]
617 require.True(t, ok, "pin for %s not found", p1)
618 require.Equal(t, "test-pin-1", name1, "unexpected pin name for %s", p1)
619
620 name2, ok := pinMap[p2.String()]
621 require.True(t, ok, "pin for %s not found", p2)
622 require.Empty(t, name2, "expected empty pin name for %s, got '%s'", p2, name2)
623
624 // Test 4: Pin update preserves name
625 err = api.Pin().Add(ctx, p3, opt.Pin.Name("updatable-pin"))
626 require.NoError(t, err, "failed to add pin with name for update test")
627
628 err = api.Pin().Update(ctx, p3, p4)
629 require.NoError(t, err, "failed to update pin")
630
631 // Verify name was preserved after update
632 pins2 := make(chan iface.Pin)
633 go func() {
634 err = api.Pin().Ls(ctx, pins2, opt.Pin.Ls.Detailed(true))
635 }()
636
637 updatedPinMap := make(map[string]string)
638 for pin := range pins2 {
639 updatedPinMap[pin.Path().String()] = pin.Name()
640 }
641 require.NoError(t, err, "failed to list pins after update")
642
643 // Old pin should not exist
644 _, oldExists := updatedPinMap[p3.String()]
645 require.False(t, oldExists, "old pin %s should not exist after update", p3)
646
647 // New pin should have the preserved name
648 name4, ok := updatedPinMap[p4.String()]
649 require.True(t, ok, "updated pin for %s not found", p4)
650 require.Equal(t, "updatable-pin", name4, "pin name not preserved after update from %s to %s", p3, p4)
651
652 // Test 5: Re-pinning with different name updates the name
653 err = api.Pin().Add(ctx, p1, opt.Pin.Name("new-name-for-p1"))
654 require.NoError(t, err, "failed to re-pin with new name")
655
656 // Verify name was updated
657 pins3 := make(chan iface.Pin)
658 go func() {
659 err = api.Pin().Ls(ctx, pins3, opt.Pin.Ls.Detailed(true))
660 }()
661
662 repinMap := make(map[string]string)
663 for pin := range pins3 {
664 repinMap[pin.Path().String()] = pin.Name()
665 }
666 require.NoError(t, err, "failed to list pins after re-pin")
667
668 rePinnedName, ok := repinMap[p1.String()]
669 require.True(t, ok, "re-pinned content %s not found", p1)
670 require.Equal(t, "new-name-for-p1", rePinnedName, "pin name not updated after re-pinning %s", p1)
671
672 // Test 6: Direct pin with name
673 p5, err := api.Unixfs().Add(ctx, strFile("direct-content")())
674 require.NoError(t, err)
675
676 err = api.Pin().Add(ctx, p5, opt.Pin.Recursive(false), opt.Pin.Name("direct-pin-name"))
677 require.NoError(t, err, "failed to add direct pin with name")
678
679 // Verify direct pin has name
680 directPins := make(chan iface.Pin)
681 typeOpt, err := opt.Pin.Ls.Type("direct")
682 require.NoError(t, err, "failed to create type option")
683 go func() {
684 err = api.Pin().Ls(ctx, directPins, typeOpt, opt.Pin.Ls.Detailed(true))
685 }()
686
687 directPinMap := make(map[string]string)
688 for pin := range directPins {
689 directPinMap[pin.Path().String()] = pin.Name()
690 }
691 require.NoError(t, err, "failed to list direct pins")
692
693 directName, ok := directPinMap[p5.String()]
694 require.True(t, ok, "direct pin %s not found", p5)
695 require.Equal(t, "direct-pin-name", directName, "unexpected name for direct pin %s", p5)
696
697 // Test 7: List without detailed option doesn't return names
698 pinsNoDetails := make(chan iface.Pin)
699 go func() {
700 err = api.Pin().Ls(ctx, pinsNoDetails)
701 }()
702
703 noDetailsMap := make(map[string]string)
704 for pin := range pinsNoDetails {
705 noDetailsMap[pin.Path().String()] = pin.Name()
706 }
707 require.NoError(t, err, "failed to list pins without detailed option")
708
709 // All names should be empty without detailed option
710 for path, name := range noDetailsMap {
711 require.Empty(t, name, "expected empty name for %s without detailed option, got '%s'", path, name)
712 }
713 }
714
715 func assertNotPinned(t *testing.T, ctx context.Context, api iface.CoreAPI, p path.Path) {
716 t.Helper()
717
718 _, pinned, err := api.Pin().IsPinned(ctx, p)
719 if err != nil {
720 t.Fatal(err)
721 }
722
723 if pinned {
724 t.Fatalf("%s expected to not be pinned", p)
725 }
726 }
727
728 func accPins(ctx context.Context, api iface.CoreAPI, opts ...opt.PinLsOption) ([]iface.Pin, error) {
729 var err error
730 pins := make(chan iface.Pin)
731 go func() {
732 err = api.Pin().Ls(ctx, pins, opts...)
733 }()
734
735 var results []iface.Pin
736 for pin := range pins {
737 results = append(results, pin)
738 }
739 if err != nil {
740 return nil, err
741 }
742 return results, nil
743 }