2024-08-23 14:32:29 -04:00
|
|
|
package selector_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/ipld/go-ipld-prime"
|
|
|
|
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
|
|
|
|
"github.com/ipld/go-ipld-prime/datamodel"
|
|
|
|
|
basicnode "github.com/ipld/go-ipld-prime/node/basic"
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
2024-08-30 22:06:59 +02:00
|
|
|
|
2024-08-30 23:11:51 +02:00
|
|
|
"github.com/ucan-wg/go-ucan/v1/capability/policy/selector"
|
2024-08-23 14:32:29 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestSupported Forms runs tests against the Selector according to the
|
|
|
|
|
// proposed "Supported Forms" presented in this GitHub issue:
|
|
|
|
|
// https://github.com/ucan-wg/delegation/issues/5#issue-2154766496
|
|
|
|
|
func TestSupportedForms(t *testing.T) {
|
2024-09-01 17:04:23 +02:00
|
|
|
type Testcase struct {
|
|
|
|
|
Name string
|
|
|
|
|
Selector string
|
|
|
|
|
Input string
|
|
|
|
|
Output string
|
|
|
|
|
}
|
2024-08-23 14:32:29 -04:00
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
// Pass
|
|
|
|
|
for _, testcase := range []Testcase{
|
|
|
|
|
{Name: "Identity", Selector: `.`, Input: `{"x":1}`, Output: `{"x":1}`},
|
|
|
|
|
{Name: "Iterator", Selector: `.[]`, Input: `[1, 2]`, Output: `[1, 2]`},
|
|
|
|
|
{Name: "Optional Null Iterator", Selector: `.[]?`, Input: `null`, Output: `()`},
|
|
|
|
|
{Name: "Optional Iterator", Selector: `.[][]?`, Input: `[[1], 2, [3]]`, Output: `[1, 3]`},
|
|
|
|
|
{Name: "Object Key", Selector: `.x`, Input: `{"x": 1 }`, Output: `1`},
|
|
|
|
|
{Name: "Quoted Key", Selector: `.["x"]`, Input: `{"x": 1}`, Output: `1`},
|
|
|
|
|
{Name: "Index", Selector: `.[0]`, Input: `[1, 2]`, Output: `1`},
|
|
|
|
|
{Name: "Negative Index", Selector: `.[-1]`, Input: `[1, 2]`, Output: `2`},
|
|
|
|
|
{Name: "String Index", Selector: `.[0]`, Input: `"Hi"`, Output: `"H"`},
|
|
|
|
|
{Name: "Bytes Index", Selector: `.[0]`, Input: `{"/":{"bytes":"AAE"}`, Output: `0`},
|
|
|
|
|
{Name: "Array Slice", Selector: `.[0:2]`, Input: `[0, 1, 2]`, Output: `[0, 1]`},
|
|
|
|
|
{Name: "Array Slice", Selector: `.[1:]`, Input: `[0, 1, 2]`, Output: `[1, 2]`},
|
|
|
|
|
{Name: "Array Slice", Selector: `.[:2]`, Input: `[0, 1, 2]`, Output: `[0, 1]`},
|
|
|
|
|
{Name: "String Slice", Selector: `.[0:2]`, Input: `"hello"`, Output: `"he"`},
|
|
|
|
|
{Name: "Bytes Index", Selector: `.[1:]`, Input: `{"/":{"bytes":"AAEC"}}`, Output: `{"/":{"bytes":"AQI"}}`},
|
|
|
|
|
} {
|
|
|
|
|
tc := testcase
|
|
|
|
|
t.Run("Pass: "+tc.Name, func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
sel, err := selector.Parse(tc.Selector)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// attempt to select
|
|
|
|
|
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotEqual(t, node != nil, len(nodes) > 0) // XOR (only one of node or nodes should be set)
|
|
|
|
|
|
|
|
|
|
// make an IPLD List node from a []datamodel.Node
|
|
|
|
|
if node == nil {
|
|
|
|
|
nb := basicnode.Prototype.List.NewBuilder()
|
|
|
|
|
la, err := nb.BeginList(int64(len(nodes)))
|
|
|
|
|
require.NoError(t, err)
|
2024-08-23 14:32:29 -04:00
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
for _, n := range nodes {
|
|
|
|
|
// TODO: This code is probably not needed if the Select operation properly prunes nil values - e.g.: Optional Iterator
|
|
|
|
|
if n == nil {
|
|
|
|
|
n = datamodel.Null
|
|
|
|
|
}
|
2024-08-23 14:32:29 -04:00
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
require.NoError(t, la.AssembleValue().AssignNode(n))
|
2024-08-23 14:32:29 -04:00
|
|
|
}
|
2024-09-01 17:04:23 +02:00
|
|
|
require.NoError(t, la.Finish())
|
2024-08-23 14:32:29 -04:00
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
node = nb.Build()
|
|
|
|
|
}
|
2024-08-23 14:32:29 -04:00
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
exp := makeNode(t, tc.Output)
|
|
|
|
|
equalIPLD(t, exp, node)
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-08-23 14:32:29 -04:00
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
// null
|
|
|
|
|
for _, testcase := range []Testcase{
|
|
|
|
|
{Name: "Optional Missing Key", Selector: `.x?`, Input: `{}`},
|
|
|
|
|
{Name: "Optional Null Key", Selector: `.x?`, Input: `null`},
|
|
|
|
|
{Name: "Optional Array Key", Selector: `.x?`, Input: `[]`},
|
|
|
|
|
{Name: "Optional Quoted Key", Selector: `.["x"]?`, Input: `{}`},
|
|
|
|
|
{Name: ".length?", Selector: `.length?`, Input: `[1, 2]`},
|
|
|
|
|
{Name: "Optional Index", Selector: `.[4]?`, Input: `[0, 1]`},
|
|
|
|
|
} {
|
|
|
|
|
tc := testcase
|
|
|
|
|
t.Run("Null: "+tc.Name, func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
sel, err := selector.Parse(tc.Selector)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// attempt to select
|
|
|
|
|
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
// TODO: should Select return a single node which is sometimes a list or null?
|
|
|
|
|
// require.Equal(t, datamodel.Null, node)
|
|
|
|
|
assert.Nil(t, node)
|
|
|
|
|
assert.Empty(t, nodes)
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-08-23 14:32:29 -04:00
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
// error
|
|
|
|
|
for _, testcase := range []Testcase{
|
|
|
|
|
{Name: "Null Iterator", Selector: `.[]`, Input: `null`},
|
|
|
|
|
{Name: "Nested Iterator", Selector: `.[][]`, Input: `[[1], 2, [3]]`},
|
|
|
|
|
{Name: "Missing Key", Selector: `.x`, Input: `{}`},
|
|
|
|
|
{Name: "Null Key", Selector: `.x`, Input: `null`},
|
|
|
|
|
{Name: "Array Key", Selector: `.x`, Input: `[]`},
|
|
|
|
|
{Name: ".length", Selector: `.length`, Input: `[1, 2]`},
|
|
|
|
|
{Name: "Out of bound Index", Selector: `.[4]`, Input: `[0, 1]`},
|
|
|
|
|
} {
|
|
|
|
|
tc := testcase
|
|
|
|
|
t.Run("Null: "+tc.Name, func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
sel, err := selector.Parse(tc.Selector)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// attempt to select
|
|
|
|
|
node, nodes, err := selector.Select(sel, makeNode(t, tc.Input))
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
assert.Nil(t, node)
|
|
|
|
|
assert.Empty(t, nodes)
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-08-23 14:32:29 -04:00
|
|
|
}
|
|
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
func equalIPLD(t *testing.T, expected datamodel.Node, actual datamodel.Node) bool {
|
2024-08-23 14:32:29 -04:00
|
|
|
t.Helper()
|
|
|
|
|
|
2024-08-30 23:12:32 +02:00
|
|
|
exp, act := &bytes.Buffer{}, &bytes.Buffer{}
|
|
|
|
|
if err := dagjson.Encode(expected, exp); err != nil {
|
|
|
|
|
return assert.Fail(t, "Failed to encode json for expected IPLD node")
|
|
|
|
|
}
|
2024-08-23 14:32:29 -04:00
|
|
|
|
2024-08-30 23:12:32 +02:00
|
|
|
if err := dagjson.Encode(actual, act); err != nil {
|
|
|
|
|
return assert.Fail(t, "Failed to encode JSON for actual IPLD node")
|
2024-08-23 14:32:29 -04:00
|
|
|
}
|
|
|
|
|
|
2024-08-30 23:12:32 +02:00
|
|
|
require.JSONEq(t, exp.String(), act.String())
|
|
|
|
|
|
2024-08-23 14:32:29 -04:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-01 17:04:23 +02:00
|
|
|
func makeNode(t *testing.T, dagJsonInput string) ipld.Node {
|
2024-08-23 14:32:29 -04:00
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
np := basicnode.Prototype.Any
|
|
|
|
|
nb := np.NewBuilder()
|
2024-09-01 17:04:23 +02:00
|
|
|
require.NoError(t, dagjson.Decode(nb, strings.NewReader(dagJsonInput)))
|
2024-08-23 14:32:29 -04:00
|
|
|
|
|
|
|
|
node := nb.Build()
|
|
|
|
|
require.NotNil(t, node)
|
|
|
|
|
|
|
|
|
|
return node
|
|
|
|
|
}
|