focus_test.go 8.28 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.Prototype__Map{}, 3, func(na fluent.MapAssembler) {
32 33 34 35 36
		na.AssembleEntry("foo").AssignBool(true)
		na.AssembleEntry("bar").AssignBool(false)
		na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) {
			na.AssembleEntry("alink").AssignLink(leafAlphaLnk)
			na.AssembleEntry("nonlink").AssignString("zoo")
37
		})
38
	}))
39
	middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__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.Prototype__Map{}, 4, func(na fluent.MapAssembler) {
46 47 48 49
		na.AssembleEntry("plain").AssignString("olde string")
		na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk)
		na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk)
		na.AssembleEntry("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
			return nil
		})
		Wish(t, err, ShouldEqual, nil)
	})
}

Eric Myhre's avatar
Eric Myhre committed
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
// covers Get used on one already-loaded Node; no link-loading exercised.
// same fixtures as the test for Focus; just has fewer assertions, since Get does no progress tracking.
func TestGetSingleTree(t *testing.T) {
	t.Run("empty path on scalar node returns start node", func(t *testing.T) {
		n, err := traversal.Get(basicnode.NewString("x"), ipld.Path{})
		Wish(t, err, ShouldEqual, nil)
		Wish(t, n, ShouldEqual, basicnode.NewString("x"))
	})
	t.Run("one step path on map node works", func(t *testing.T) {
		n, err := traversal.Get(middleMapNode, ipld.ParsePath("foo"))
		Wish(t, err, ShouldEqual, nil)
		Wish(t, n, ShouldEqual, basicnode.NewBool(true))
	})
	t.Run("two step path on map node works", func(t *testing.T) {
		n, err := traversal.Get(middleMapNode, ipld.ParsePath("nested/nonlink"))
		Wish(t, err, ShouldEqual, nil)
		Wish(t, n, ShouldEqual, basicnode.NewString("zoo"))
	})
}

146 147 148
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
149
			err := traversal.Focus(middleMapNode, ipld.ParsePath("nested/alink"), func(prog traversal.Progress, n ipld.Node) error {
150 151 152
				t.Errorf("should not be reached; no way to load this path")
				return nil
			})
153
			Wish(t, err.Error(), ShouldEqual, `error traversing node at "nested/alink": could not load link "`+leafAlphaLnk.String()+`": no LinkTargetNodePrototypeChooser configured`)
154 155
		})
		t.Run("mid-path link should fail", func(t *testing.T) {
Eric Myhre's avatar
Eric Myhre committed
156
			err := traversal.Focus(rootNode, ipld.ParsePath("linkedMap/nested/nonlink"), func(prog traversal.Progress, n ipld.Node) error {
157 158 159
				t.Errorf("should not be reached; no way to load this path")
				return nil
			})
160
			Wish(t, err.Error(), ShouldEqual, `error traversing node at "linkedMap": could not load link "`+middleMapNodeLnk.String()+`": no LinkTargetNodePrototypeChooser configured`)
161 162 163
		})
	})
	t.Run("link traversal with loader should work", func(t *testing.T) {
164 165
		err := traversal.Progress{
			Cfg: &traversal.Config{
166 167 168
				LinkLoader: func(lnk ipld.Link, _ ipld.LinkContext) (io.Reader, error) {
					return bytes.NewBuffer(storage[lnk]), nil
				},
169 170
				LinkTargetNodePrototypeChooser: func(_ ipld.Link, _ ipld.LinkContext) (ipld.NodePrototype, error) {
					return basicnode.Prototype__Any{}, nil
171
				},
172
			},
Eric Myhre's avatar
Eric Myhre committed
173
		}.Focus(rootNode, ipld.ParsePath("linkedMap/nested/nonlink"), func(prog traversal.Progress, n ipld.Node) error {
174
			Wish(t, n, ShouldEqual, basicnode.NewString("zoo"))
Eric Myhre's avatar
Eric Myhre committed
175 176 177
			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"))
178 179 180 181 182
			return nil
		})
		Wish(t, err, ShouldEqual, nil)
	})
}
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209

func TestGetWithLinkLoading(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) {
			_, err := traversal.Get(middleMapNode, ipld.ParsePath("nested/alink"))
			Wish(t, err.Error(), ShouldEqual, `error traversing node at "nested/alink": could not load link "`+leafAlphaLnk.String()+`": no LinkTargetNodePrototypeChooser configured`)
		})
		t.Run("mid-path link should fail", func(t *testing.T) {
			_, err := traversal.Get(rootNode, ipld.ParsePath("linkedMap/nested/nonlink"))
			Wish(t, err.Error(), ShouldEqual, `error traversing node at "linkedMap": could not load link "`+middleMapNodeLnk.String()+`": no LinkTargetNodePrototypeChooser configured`)
		})
	})
	t.Run("link traversal with loader should work", func(t *testing.T) {
		n, err := traversal.Progress{
			Cfg: &traversal.Config{
				LinkLoader: func(lnk ipld.Link, _ ipld.LinkContext) (io.Reader, error) {
					return bytes.NewBuffer(storage[lnk]), nil
				},
				LinkTargetNodePrototypeChooser: func(_ ipld.Link, _ ipld.LinkContext) (ipld.NodePrototype, error) {
					return basicnode.Prototype__Any{}, nil
				},
			},
		}.Get(rootNode, ipld.ParsePath("linkedMap/nested/nonlink"))
		Wish(t, err, ShouldEqual, nil)
		Wish(t, n, ShouldEqual, basicnode.NewString("zoo"))
	})
}