additional tests for optional selectors

This commit is contained in:
Fabio Bozzo
2024-11-01 13:07:46 +01:00
parent 7662fe34db
commit 6d85b2ba3c
4 changed files with 159 additions and 1 deletions

View File

@@ -2,6 +2,8 @@
package literal
import (
"fmt"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
@@ -24,3 +26,58 @@ func Null() ipld.Node {
nb.AssignNull()
return nb.Build()
}
// Map creates an IPLD node from a map[string]interface{}
func Map(v interface{}) (ipld.Node, error) {
m, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("expected map[string]interface{}, got %T", v)
}
nb := basicnode.Prototype.Map.NewBuilder()
ma, err := nb.BeginMap(int64(len(m)))
if err != nil {
return nil, err
}
for k, v := range m {
if err := ma.AssembleKey().AssignString(k); err != nil {
return nil, err
}
switch x := v.(type) {
case string:
if err := ma.AssembleValue().AssignString(x); err != nil {
return nil, err
}
case []interface{}:
lb := basicnode.Prototype.List.NewBuilder()
la, err := lb.BeginList(int64(len(x)))
if err != nil {
return nil, err
}
if err := la.Finish(); err != nil {
return nil, err
}
if err := ma.AssembleValue().AssignNode(lb.Build()); err != nil {
return nil, err
}
case map[string]interface{}:
nestedNode, err := Map(x) // recursive call for nested maps
if err != nil {
return nil, err
}
if err := ma.AssembleValue().AssignNode(nestedNode); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported value type: %T", v)
}
}
if err := ma.Finish(); err != nil {
return nil, err
}
return nb.Build(), nil
}

View File

@@ -72,6 +72,9 @@ func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Stat
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil || res == nil {
if s.selector.IsOptional() {
return matchResultTrue, nil
}
return matchResultNoData, cur
}
return boolToRes(datamodel.DeepEqual(s.value, res))
@@ -80,6 +83,9 @@ func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Stat
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil || res == nil {
if s.selector.IsOptional() {
return matchResultTrue, nil
}
return matchResultNoData, cur
}
return boolToRes(isOrdered(s.value, res, gt))
@@ -88,6 +94,9 @@ func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Stat
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil || res == nil {
if s.selector.IsOptional() {
return matchResultTrue, nil
}
return matchResultNoData, cur
}
return boolToRes(isOrdered(s.value, res, gte))
@@ -96,6 +105,9 @@ func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Stat
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil || res == nil {
if s.selector.IsOptional() {
return matchResultTrue, nil
}
return matchResultNoData, cur
}
return boolToRes(isOrdered(s.value, res, lt))
@@ -104,6 +116,9 @@ func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Stat
if s, ok := cur.(equality); ok {
res, err := s.selector.Select(node)
if err != nil || res == nil {
if s.selector.IsOptional() {
return matchResultTrue, nil
}
return matchResultNoData, cur
}
return boolToRes(isOrdered(s.value, res, lte))

View File

@@ -9,6 +9,7 @@ import (
"github.com/ipld/go-ipld-prime/codec/dagjson"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
@@ -457,7 +458,7 @@ func TestPolicyExamples(t *testing.T) {
require.False(t, evaluate(`["all", ".a", [">", ".b", 0]]`, data))
})
t.Run("Any", func(t *testing.T) {
t.Run("Map", func(t *testing.T) {
data := makeNode(`{"a": [{"b": 1}, {"b": 2}, {"z": [7, 8, 9]}]}`)
require.True(t, evaluate(`["any", ".a", ["==", ".b", 2]]`, data))
@@ -512,3 +513,80 @@ func FuzzMatch(f *testing.F) {
policy.Match(dataNode)
})
}
func TestOptionalSelectors(t *testing.T) {
tests := []struct {
name string
policy Policy
data interface{}
expected bool
}{
{
name: "missing optional field returns true",
policy: MustConstruct(Equal(".field?", literal.String("value"))),
data: map[string]interface{}{},
expected: true,
},
{
name: "present optional field with matching value returns true",
policy: MustConstruct(Equal(".field?", literal.String("value"))),
data: map[string]interface{}{"field": "value"},
expected: true,
},
{
name: "present optional field with non-matching value returns false",
policy: MustConstruct(Equal(".field?", literal.String("value"))),
data: map[string]interface{}{"field": "other"},
expected: false,
},
{
name: "missing non-optional field returns false",
policy: MustConstruct(Equal(".field", literal.String("value"))),
data: map[string]interface{}{},
expected: false,
},
{
name: "nested missing non-optional field returns false",
policy: MustConstruct(Equal(".outer?.inner", literal.String("value"))),
data: map[string]interface{}{"outer": map[string]interface{}{}},
expected: false,
},
{
name: "completely missing nested optional path returns true",
policy: MustConstruct(Equal(".outer?.inner?", literal.String("value"))),
data: map[string]interface{}{},
expected: true,
},
{
name: "partially present nested optional path with missing end returns true",
policy: MustConstruct(Equal(".outer?.inner?", literal.String("value"))),
data: map[string]interface{}{"outer": map[string]interface{}{}},
expected: true,
},
{
name: "optional array index returns true when array is empty",
policy: MustConstruct(Equal(".array[0]?", literal.String("value"))),
data: map[string]interface{}{"array": []interface{}{}},
expected: true,
},
{
name: "non-optional array index returns false when array is empty",
policy: MustConstruct(Equal(".array[0]", literal.String("value"))),
data: map[string]interface{}{"array": []interface{}{}},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
nb := basicnode.Prototype.Map.NewBuilder()
n, err := literal.Map(tt.data)
assert.NoError(t, err)
err = nb.AssignNode(n)
assert.NoError(t, err)
result := tt.policy.Match(nb.Build())
assert.Equal(t, tt.expected, result)
})
}
}

View File

@@ -33,6 +33,14 @@ func (s Selector) String() string {
return res.String()
}
func (s Selector) IsOptional() bool {
if len(s) == 0 {
return false
}
return s[len(s)-1].optional
}
type segment struct {
str string
identity bool