focus_test.go 6.09 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
package traversal_test

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"strings"
	"testing"
	"unicode"

	. "github.com/warpfork/go-wish"

	cid "github.com/ipfs/go-cid"
	ipld "github.com/ipld/go-ipld-prime"
16 17

	_ "github.com/ipld/go-ipld-prime/codec/dagjson"
18 19
	"github.com/ipld/go-ipld-prime/fluent"
	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
20
	basicnode "github.com/ipld/go-ipld-prime/node/basic"
21 22 23 24 25 26 27 28
	"github.com/ipld/go-ipld-prime/traversal"
)

// Do some fixture fabrication.
// We assume all the builders and serialization must Just Work here.

var storage = make(map[ipld.Link][]byte)
var (
29 30
	leafAlpha, leafAlphaLnk         = encode(basicnode.NewString("alpha"))
	leafBeta, leafBetaLnk           = encode(basicnode.NewString("beta"))
31
	middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Style__Map{}, 3, func(na fluent.MapAssembler) {
32 33
		na.AssembleDirectly("foo").AssignBool(true)
		na.AssembleDirectly("bar").AssignBool(false)
34
		na.AssembleDirectly("nested").CreateMap(2, func(na fluent.MapAssembler) {
35 36 37
			na.AssembleDirectly("alink").AssignLink(leafAlphaLnk)
			na.AssembleDirectly("nonlink").AssignString("zoo")
		})
38
	}))
39
	middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Style__List{}, 4, func(na fluent.ListAssembler) {
40 41 42 43
		na.AssembleValue().AssignLink(leafAlphaLnk)
		na.AssembleValue().AssignLink(leafAlphaLnk)
		na.AssembleValue().AssignLink(leafBetaLnk)
		na.AssembleValue().AssignLink(leafAlphaLnk)
44
	}))
45
	rootNode, rootNodeLnk = encode(fluent.MustBuildMap(basicnode.Style__Map{}, 4, func(na fluent.MapAssembler) {
46 47 48 49
		na.AssembleDirectly("plain").AssignString("olde string")
		na.AssembleDirectly("linkedString").AssignLink(leafAlphaLnk)
		na.AssembleDirectly("linkedMap").AssignLink(middleMapNodeLnk)
		na.AssembleDirectly("linkedList").AssignLink(middleListNodeLnk)
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
	}))
)

// encode hardcodes some encoding choices for ease of use in fixture generation;
// just gimme a link and stuff the bytes in a map.
// (also return the node again for convenient assignment.)
func encode(n ipld.Node) (ipld.Node, ipld.Link) {
	lb := cidlink.LinkBuilder{cid.Prefix{
		Version:  1,
		Codec:    0x0129,
		MhType:   0x17,
		MhLength: 4,
	}}
	lnk, err := lb.Build(context.Background(), ipld.LinkContext{}, n,
		func(ipld.LinkContext) (io.Writer, ipld.StoreCommitter, error) {
			buf := bytes.Buffer{}
			return &buf, func(lnk ipld.Link) error {
				storage[lnk] = buf.Bytes()
				return nil
			}, nil
		},
	)
	if err != nil {
		panic(err)
	}
	return n, lnk
}

// Print a quick little table of our fixtures for sanity check purposes.
func init() {
	withoutWhitespace := func(s string) string {
		return strings.Map(func(r rune) rune {
			if !unicode.IsPrint(r) {
				return -1
			} else {
				return r
			}
		}, s)
	}
	fmt.Printf("fixtures:\n"+strings.Repeat("\t%v\t%v\n", 5),
		leafAlphaLnk, withoutWhitespace(string(storage[leafAlphaLnk])),
		leafBetaLnk, withoutWhitespace(string(storage[leafBetaLnk])),
		middleMapNodeLnk, withoutWhitespace(string(storage[middleMapNodeLnk])),
		middleListNodeLnk, withoutWhitespace(string(storage[middleListNodeLnk])),
		rootNodeLnk, withoutWhitespace(string(storage[rootNodeLnk])),
	)
}

// covers Focus used on one already-loaded Node; no link-loading exercised.
func TestFocusSingleTree(t *testing.T) {
	t.Run("empty path on scalar node returns start node", func(t *testing.T) {
101 102
		err := traversal.Focus(basicnode.NewString("x"), ipld.Path{}, func(prog traversal.Progress, n ipld.Node) error {
			Wish(t, n, ShouldEqual, basicnode.NewString("x"))
Eric Myhre's avatar
Eric Myhre committed
103
			Wish(t, prog.Path.String(), ShouldEqual, ipld.Path{}.String())
104 105 106 107 108
			return nil
		})
		Wish(t, err, ShouldEqual, nil)
	})
	t.Run("one step path on map node works", func(t *testing.T) {
Eric Myhre's avatar
Eric Myhre committed
109
		err := traversal.Focus(middleMapNode, ipld.ParsePath("foo"), func(prog traversal.Progress, n ipld.Node) error {
110
			Wish(t, n, ShouldEqual, basicnode.NewBool(true))
Eric Myhre's avatar
Eric Myhre committed
111
			Wish(t, prog.Path, ShouldEqual, ipld.ParsePath("foo"))
112 113 114 115 116
			return nil
		})
		Wish(t, err, ShouldEqual, nil)
	})
	t.Run("two step path on map node works", func(t *testing.T) {
Eric Myhre's avatar
Eric Myhre committed
117
		err := traversal.Focus(middleMapNode, ipld.ParsePath("nested/nonlink"), func(prog traversal.Progress, n ipld.Node) error {
118
			Wish(t, n, ShouldEqual, basicnode.NewString("zoo"))
Eric Myhre's avatar
Eric Myhre committed
119
			Wish(t, prog.Path, ShouldEqual, ipld.ParsePath("nested/nonlink"))
120 121 122 123 124 125 126 127 128
			return nil
		})
		Wish(t, err, ShouldEqual, nil)
	})
}

func TestFocusWithLinkLoading(t *testing.T) {
	t.Run("link traversal with no configured loader should fail", func(t *testing.T) {
		t.Run("terminal link should fail", func(t *testing.T) {
Eric Myhre's avatar
Eric Myhre committed
129
			err := traversal.Focus(middleMapNode, ipld.ParsePath("nested/alink"), func(prog traversal.Progress, n ipld.Node) error {
130 131 132
				t.Errorf("should not be reached; no way to load this path")
				return nil
			})
133
			Wish(t, err.Error(), ShouldEqual, `error traversing node at "nested/alink": could not load link "`+leafAlphaLnk.String()+`": no LinkTargetNodeStyleChooser configured`)
134 135
		})
		t.Run("mid-path link should fail", func(t *testing.T) {
Eric Myhre's avatar
Eric Myhre committed
136
			err := traversal.Focus(rootNode, ipld.ParsePath("linkedMap/nested/nonlink"), func(prog traversal.Progress, n ipld.Node) error {
137 138 139
				t.Errorf("should not be reached; no way to load this path")
				return nil
			})
140
			Wish(t, err.Error(), ShouldEqual, `error traversing node at "linkedMap": could not load link "`+middleMapNodeLnk.String()+`": no LinkTargetNodeStyleChooser configured`)
141 142 143
		})
	})
	t.Run("link traversal with loader should work", func(t *testing.T) {
144 145
		err := traversal.Progress{
			Cfg: &traversal.Config{
146 147 148
				LinkLoader: func(lnk ipld.Link, _ ipld.LinkContext) (io.Reader, error) {
					return bytes.NewBuffer(storage[lnk]), nil
				},
149 150
				LinkTargetNodeStyleChooser: func(_ ipld.Link, _ ipld.LinkContext) (ipld.NodeStyle, error) {
					return basicnode.Style__Any{}, nil
151
				},
152
			},
Eric Myhre's avatar
Eric Myhre committed
153
		}.Focus(rootNode, ipld.ParsePath("linkedMap/nested/nonlink"), func(prog traversal.Progress, n ipld.Node) error {
154
			Wish(t, n, ShouldEqual, basicnode.NewString("zoo"))
Eric Myhre's avatar
Eric Myhre committed
155 156 157
			Wish(t, prog.Path, ShouldEqual, ipld.ParsePath("linkedMap/nested/nonlink"))
			Wish(t, prog.LastBlock.Link, ShouldEqual, middleMapNodeLnk)
			Wish(t, prog.LastBlock.Path, ShouldEqual, ipld.ParsePath("linkedMap"))
158 159 160 161 162
			return nil
		})
		Wish(t, err, ShouldEqual, nil)
	})
}