github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/caveats/run_test.go (about)

     1  package caveats_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/authzed/spicedb/internal/caveats"
    12  	"github.com/authzed/spicedb/internal/datastore/memdb"
    13  	"github.com/authzed/spicedb/internal/testfixtures"
    14  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    15  )
    16  
    17  var (
    18  	caveatexpr   = caveats.CaveatExprForTesting
    19  	caveatAnd    = caveats.And
    20  	caveatOr     = caveats.Or
    21  	caveatInvert = caveats.Invert
    22  )
    23  
    24  func TestRunCaveatExpressions(t *testing.T) {
    25  	tcs := []struct {
    26  		name          string
    27  		expression    *core.CaveatExpression
    28  		context       map[string]any
    29  		expectedValue bool
    30  	}{
    31  		{
    32  			"basic",
    33  			caveatexpr("firstCaveat"),
    34  			map[string]any{
    35  				"first": "42",
    36  			},
    37  			true,
    38  		},
    39  		{
    40  			"another basic",
    41  			caveatexpr("firstCaveat"),
    42  			map[string]any{
    43  				"first": "12",
    44  			},
    45  			false,
    46  		},
    47  		{
    48  			"short circuited or",
    49  			caveatOr(
    50  				caveatexpr("firstCaveat"),
    51  				caveatexpr("secondCaveat"),
    52  			),
    53  			map[string]any{
    54  				"first": "42",
    55  			},
    56  			true,
    57  		},
    58  		{
    59  			"non-short circuited or",
    60  			caveatOr(
    61  				caveatexpr("firstCaveat"),
    62  				caveatexpr("secondCaveat"),
    63  			),
    64  			map[string]any{
    65  				"first":  "12",
    66  				"second": "hello",
    67  			},
    68  			true,
    69  		},
    70  		{
    71  			"false or",
    72  			caveatOr(
    73  				caveatexpr("firstCaveat"),
    74  				caveatexpr("secondCaveat"),
    75  			),
    76  			map[string]any{
    77  				"first":  "12",
    78  				"second": "hi",
    79  			},
    80  			false,
    81  		},
    82  		{
    83  			"short circuited and",
    84  			caveatAnd(
    85  				caveatexpr("firstCaveat"),
    86  				caveatexpr("secondCaveat"),
    87  			),
    88  			map[string]any{
    89  				"first": "12",
    90  			},
    91  			false,
    92  		},
    93  		{
    94  			"non-short circuited and",
    95  			caveatAnd(
    96  				caveatexpr("firstCaveat"),
    97  				caveatexpr("secondCaveat"),
    98  			),
    99  			map[string]any{
   100  				"first":  "12",
   101  				"second": "hello",
   102  			},
   103  			false,
   104  		},
   105  		{
   106  			"false or",
   107  			caveatAnd(
   108  				caveatexpr("firstCaveat"),
   109  				caveatexpr("secondCaveat"),
   110  			),
   111  			map[string]any{
   112  				"first":  "42",
   113  				"second": "hi",
   114  			},
   115  			false,
   116  		},
   117  		{
   118  			"inversion",
   119  			caveatInvert(
   120  				caveatexpr("firstCaveat"),
   121  			),
   122  			map[string]any{
   123  				"first": "12",
   124  			},
   125  			true,
   126  		},
   127  		{
   128  			"nested",
   129  			caveatAnd(
   130  				caveatOr(
   131  					caveatexpr("firstCaveat"),
   132  					caveatexpr("secondCaveat"),
   133  				),
   134  				caveatInvert(
   135  					caveatexpr("thirdCaveat"),
   136  				),
   137  			),
   138  			map[string]any{
   139  				"first":  "12",
   140  				"second": "hi",
   141  				"third":  false,
   142  			},
   143  			false,
   144  		},
   145  		{
   146  			"nested true",
   147  			caveatAnd(
   148  				caveatOr(
   149  					caveatexpr("firstCaveat"),
   150  					caveatexpr("secondCaveat"),
   151  				),
   152  				caveatInvert(
   153  					caveatexpr("thirdCaveat"),
   154  				),
   155  			),
   156  			map[string]any{
   157  				"first":  "42",
   158  				"second": "hi",
   159  				"third":  false,
   160  			},
   161  			true,
   162  		},
   163  	}
   164  
   165  	for _, tc := range tcs {
   166  		tc := tc
   167  		t.Run(tc.name, func(t *testing.T) {
   168  			req := require.New(t)
   169  
   170  			rawDS, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC)
   171  			req.NoError(err)
   172  
   173  			ds, _ := testfixtures.DatastoreFromSchemaAndTestRelationships(rawDS, `
   174  				caveat firstCaveat(first int) {
   175  					first == 42
   176  				}
   177  
   178  				caveat secondCaveat(second string) {
   179  					second == 'hello'
   180  				}
   181  
   182  				caveat thirdCaveat(third bool) {
   183  					third
   184  				}
   185  				`, nil, req)
   186  			headRevision, err := ds.HeadRevision(context.Background())
   187  			req.NoError(err)
   188  
   189  			reader := ds.SnapshotReader(headRevision)
   190  
   191  			for _, debugOption := range []caveats.RunCaveatExpressionDebugOption{
   192  				caveats.RunCaveatExpressionNoDebugging,
   193  				caveats.RunCaveatExpressionWithDebugInformation,
   194  			} {
   195  				debugOption := debugOption
   196  				t.Run(fmt.Sprintf("%v", debugOption), func(t *testing.T) {
   197  					req := require.New(t)
   198  
   199  					result, err := caveats.RunCaveatExpression(context.Background(), tc.expression, tc.context, reader, debugOption)
   200  					req.NoError(err)
   201  					req.Equal(tc.expectedValue, result.Value())
   202  				})
   203  			}
   204  		})
   205  	}
   206  }
   207  
   208  func TestRunCaveatWithMissingMap(t *testing.T) {
   209  	req := require.New(t)
   210  
   211  	rawDS, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC)
   212  	req.NoError(err)
   213  
   214  	ds, _ := testfixtures.DatastoreFromSchemaAndTestRelationships(rawDS, `
   215  				caveat some_caveat(themap map<any>) {
   216  					themap.first == 42
   217  				}
   218  				`, nil, req)
   219  
   220  	headRevision, err := ds.HeadRevision(context.Background())
   221  	req.NoError(err)
   222  
   223  	reader := ds.SnapshotReader(headRevision)
   224  
   225  	result, err := caveats.RunCaveatExpression(
   226  		context.Background(),
   227  		caveatexpr("some_caveat"),
   228  		map[string]any{},
   229  		reader,
   230  		caveats.RunCaveatExpressionNoDebugging,
   231  	)
   232  	req.NoError(err)
   233  	req.True(result.IsPartial())
   234  	req.False(result.Value())
   235  }
   236  
   237  func TestRunCaveatWithEmptyMap(t *testing.T) {
   238  	req := require.New(t)
   239  
   240  	rawDS, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC)
   241  	req.NoError(err)
   242  
   243  	ds, _ := testfixtures.DatastoreFromSchemaAndTestRelationships(rawDS, `
   244  				caveat some_caveat(themap map<any>) {
   245  					themap.first == 42
   246  				}
   247  				`, nil, req)
   248  
   249  	headRevision, err := ds.HeadRevision(context.Background())
   250  	req.NoError(err)
   251  
   252  	reader := ds.SnapshotReader(headRevision)
   253  
   254  	_, err = caveats.RunCaveatExpression(
   255  		context.Background(),
   256  		caveatexpr("some_caveat"),
   257  		map[string]any{
   258  			"themap": map[string]any{},
   259  		},
   260  		reader,
   261  		caveats.RunCaveatExpressionNoDebugging,
   262  	)
   263  	req.Error(err)
   264  	req.True(errors.As(err, &caveats.EvaluationErr{}))
   265  }