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

     1  package graph
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/require"
     7  	"google.golang.org/protobuf/types/known/structpb"
     8  
     9  	"github.com/authzed/spicedb/internal/caveats"
    10  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    11  	"github.com/authzed/spicedb/pkg/tuple"
    12  )
    13  
    14  var invert = caveats.Invert
    15  
    16  func caveat(name string, context map[string]any) *core.CaveatExpression {
    17  	s, _ := structpb.NewStruct(context)
    18  	return wrapCaveat(
    19  		&core.ContextualizedCaveat{
    20  			CaveatName: name,
    21  			Context:    s,
    22  		})
    23  }
    24  
    25  func TestMembershipSetAddDirectMember(t *testing.T) {
    26  	tcs := []struct {
    27  		name                string
    28  		existingMembers     map[string]*core.CaveatExpression
    29  		directMemberID      string
    30  		directMemberCaveat  *core.CaveatExpression
    31  		expectedMembers     map[string]*core.CaveatExpression
    32  		hasDeterminedMember bool
    33  	}{
    34  		{
    35  			"add determined member to empty set",
    36  			nil,
    37  			"somedoc",
    38  			nil,
    39  			map[string]*core.CaveatExpression{
    40  				"somedoc": nil,
    41  			},
    42  			true,
    43  		},
    44  		{
    45  			"add caveated member to empty set",
    46  			nil,
    47  			"somedoc",
    48  			caveat("somecaveat", nil),
    49  			map[string]*core.CaveatExpression{
    50  				"somedoc": caveat("somecaveat", nil),
    51  			},
    52  			false,
    53  		},
    54  		{
    55  			"add caveated member to set with other members",
    56  			map[string]*core.CaveatExpression{
    57  				"somedoc": caveat("somecaveat", nil),
    58  			},
    59  			"anotherdoc",
    60  			caveat("anothercaveat", nil),
    61  			map[string]*core.CaveatExpression{
    62  				"somedoc":    caveat("somecaveat", nil),
    63  				"anotherdoc": caveat("anothercaveat", nil),
    64  			},
    65  			false,
    66  		},
    67  		{
    68  			"add non-caveated member to caveated member",
    69  			map[string]*core.CaveatExpression{
    70  				"somedoc": caveat("somecaveat", nil),
    71  			},
    72  			"somedoc",
    73  			nil,
    74  			map[string]*core.CaveatExpression{
    75  				"somedoc": nil,
    76  			},
    77  			true,
    78  		},
    79  		{
    80  			"add caveated member to non-caveated member",
    81  			map[string]*core.CaveatExpression{
    82  				"somedoc": nil,
    83  			},
    84  			"somedoc",
    85  			caveat("somecaveat", nil),
    86  			map[string]*core.CaveatExpression{
    87  				"somedoc": nil,
    88  			},
    89  			true,
    90  		},
    91  		{
    92  			"add caveated member to caveated member",
    93  			map[string]*core.CaveatExpression{
    94  				"somedoc": caveat("c1", nil),
    95  			},
    96  			"somedoc",
    97  			caveat("c2", nil),
    98  			map[string]*core.CaveatExpression{
    99  				"somedoc": caveatOr(
   100  					caveat("c1", nil),
   101  					caveat("c2", nil),
   102  				),
   103  			},
   104  			false,
   105  		},
   106  		{
   107  			"add caveats with the same name, different args",
   108  			map[string]*core.CaveatExpression{
   109  				"somedoc": caveat("c1", nil),
   110  			},
   111  			"somedoc",
   112  			caveat("c1", map[string]any{
   113  				"hi": "hello",
   114  			}),
   115  			map[string]*core.CaveatExpression{
   116  				"somedoc": caveatOr(
   117  					caveat("c1", nil),
   118  					caveat("c1", map[string]any{
   119  						"hi": "hello",
   120  					}),
   121  				),
   122  			},
   123  			false,
   124  		},
   125  	}
   126  
   127  	for _, tc := range tcs {
   128  		tc := tc
   129  		t.Run(tc.name, func(t *testing.T) {
   130  			ms := membershipSetFromMap(tc.existingMembers)
   131  			ms.AddDirectMember(tc.directMemberID, unwrapCaveat(tc.directMemberCaveat))
   132  			require.Equal(t, tc.expectedMembers, ms.membersByID)
   133  			require.Equal(t, tc.hasDeterminedMember, ms.HasDeterminedMember())
   134  			require.False(t, ms.IsEmpty())
   135  		})
   136  	}
   137  }
   138  
   139  func TestMembershipSetAddMemberViaRelationship(t *testing.T) {
   140  	tcs := []struct {
   141  		name                     string
   142  		existingMembers          map[string]*core.CaveatExpression
   143  		resourceID               string
   144  		resourceCaveatExpression *core.CaveatExpression
   145  		parentRelationship       *core.RelationTuple
   146  		expectedMembers          map[string]*core.CaveatExpression
   147  		hasDeterminedMember      bool
   148  	}{
   149  		{
   150  			"add determined member to empty set",
   151  			nil,
   152  			"somedoc",
   153  			nil,
   154  			tuple.MustParse("document:foo#viewer@user:tom"),
   155  			map[string]*core.CaveatExpression{
   156  				"somedoc": nil,
   157  			},
   158  			true,
   159  		},
   160  		{
   161  			"add caveated member to empty set",
   162  			nil,
   163  			"somedoc",
   164  			caveat("somecaveat", nil),
   165  			tuple.MustParse("document:foo#viewer@user:tom"),
   166  			map[string]*core.CaveatExpression{
   167  				"somedoc": caveat("somecaveat", nil),
   168  			},
   169  			false,
   170  		},
   171  		{
   172  			"add determined member, via caveated relationship, to empty set",
   173  			nil,
   174  			"somedoc",
   175  			nil,
   176  			withCaveat(tuple.MustParse("document:foo#viewer@user:tom"), caveat("somecaveat", nil)),
   177  			map[string]*core.CaveatExpression{
   178  				"somedoc": caveat("somecaveat", nil),
   179  			},
   180  			false,
   181  		},
   182  		{
   183  			"add caveated member, via caveated relationship, to empty set",
   184  			nil,
   185  			"somedoc",
   186  			caveat("c1", nil),
   187  			withCaveat(tuple.MustParse("document:foo#viewer@user:tom"), caveat("c2", nil)),
   188  			map[string]*core.CaveatExpression{
   189  				"somedoc": caveatAnd(
   190  					caveat("c2", nil),
   191  					caveat("c1", nil),
   192  				),
   193  			},
   194  			false,
   195  		},
   196  		{
   197  			"add caveated member, via caveated relationship, to determined set",
   198  			map[string]*core.CaveatExpression{
   199  				"somedoc": nil,
   200  			},
   201  			"somedoc",
   202  			caveat("c1", nil),
   203  			withCaveat(tuple.MustParse("document:foo#viewer@user:tom"), caveat("c2", nil)),
   204  			map[string]*core.CaveatExpression{
   205  				"somedoc": nil,
   206  			},
   207  			true,
   208  		},
   209  		{
   210  			"add caveated member, via caveated relationship, to caveated set",
   211  			map[string]*core.CaveatExpression{
   212  				"somedoc": caveat("c0", nil),
   213  			},
   214  			"somedoc",
   215  			caveat("c1", nil),
   216  			withCaveat(tuple.MustParse("document:foo#viewer@user:tom"), caveat("c2", nil)),
   217  			map[string]*core.CaveatExpression{
   218  				"somedoc": caveatOr(
   219  					caveat("c0", nil),
   220  					caveatAnd(
   221  						caveat("c2", nil),
   222  						caveat("c1", nil),
   223  					),
   224  				),
   225  			},
   226  			false,
   227  		},
   228  	}
   229  
   230  	for _, tc := range tcs {
   231  		tc := tc
   232  		t.Run(tc.name, func(t *testing.T) {
   233  			ms := membershipSetFromMap(tc.existingMembers)
   234  			ms.AddMemberViaRelationship(tc.resourceID, tc.resourceCaveatExpression, tc.parentRelationship)
   235  			require.Equal(t, tc.expectedMembers, ms.membersByID)
   236  			require.Equal(t, tc.hasDeterminedMember, ms.HasDeterminedMember())
   237  		})
   238  	}
   239  }
   240  
   241  func TestMembershipSetUnionWith(t *testing.T) {
   242  	tcs := []struct {
   243  		name                string
   244  		set1                map[string]*core.CaveatExpression
   245  		set2                map[string]*core.CaveatExpression
   246  		expected            map[string]*core.CaveatExpression
   247  		hasDeterminedMember bool
   248  		isEmpty             bool
   249  	}{
   250  		{
   251  			"empty with empty",
   252  			nil,
   253  			nil,
   254  			map[string]*core.CaveatExpression{},
   255  			false,
   256  			true,
   257  		},
   258  		{
   259  			"set with empty",
   260  			map[string]*core.CaveatExpression{
   261  				"somedoc": nil,
   262  			},
   263  			nil,
   264  			map[string]*core.CaveatExpression{
   265  				"somedoc": nil,
   266  			},
   267  			true,
   268  			false,
   269  		},
   270  		{
   271  			"empty with set",
   272  			nil,
   273  			map[string]*core.CaveatExpression{
   274  				"somedoc": nil,
   275  			},
   276  			map[string]*core.CaveatExpression{
   277  				"somedoc": nil,
   278  			},
   279  			true,
   280  			false,
   281  		},
   282  		{
   283  			"non-overlapping",
   284  			map[string]*core.CaveatExpression{
   285  				"somedoc": nil,
   286  			},
   287  			map[string]*core.CaveatExpression{
   288  				"anotherdoc": nil,
   289  			},
   290  			map[string]*core.CaveatExpression{
   291  				"somedoc":    nil,
   292  				"anotherdoc": nil,
   293  			},
   294  			true,
   295  			false,
   296  		},
   297  		{
   298  			"non-overlapping with caveats",
   299  			map[string]*core.CaveatExpression{
   300  				"somedoc": nil,
   301  			},
   302  			map[string]*core.CaveatExpression{
   303  				"anotherdoc": caveat("c1", nil),
   304  			},
   305  			map[string]*core.CaveatExpression{
   306  				"somedoc":    nil,
   307  				"anotherdoc": caveat("c1", nil),
   308  			},
   309  			true,
   310  			false,
   311  		},
   312  		{
   313  			"overlapping without caveats",
   314  			map[string]*core.CaveatExpression{
   315  				"somedoc": nil,
   316  			},
   317  			map[string]*core.CaveatExpression{
   318  				"somedoc": nil,
   319  			},
   320  			map[string]*core.CaveatExpression{
   321  				"somedoc": nil,
   322  			},
   323  			true,
   324  			false,
   325  		},
   326  		{
   327  			"overlapping with single caveat",
   328  			map[string]*core.CaveatExpression{
   329  				"somedoc": nil,
   330  			},
   331  			map[string]*core.CaveatExpression{
   332  				"somedoc": caveat("c1", nil),
   333  			},
   334  			map[string]*core.CaveatExpression{
   335  				"somedoc": nil,
   336  			},
   337  			true,
   338  			false,
   339  		},
   340  		{
   341  			"overlapping with multiple caveats",
   342  			map[string]*core.CaveatExpression{
   343  				"somedoc": caveat("c1", nil),
   344  			},
   345  			map[string]*core.CaveatExpression{
   346  				"somedoc": caveat("c2", nil),
   347  			},
   348  			map[string]*core.CaveatExpression{
   349  				"somedoc": caveatOr(caveat("c1", nil), caveat("c2", nil)),
   350  			},
   351  			false,
   352  			false,
   353  		},
   354  		{
   355  			"overlapping with multiple caveats and a determined member",
   356  			map[string]*core.CaveatExpression{
   357  				"somedoc":    caveat("c1", nil),
   358  				"anotherdoc": nil,
   359  			},
   360  			map[string]*core.CaveatExpression{
   361  				"somedoc": caveat("c2", nil),
   362  			},
   363  			map[string]*core.CaveatExpression{
   364  				"anotherdoc": nil,
   365  				"somedoc":    caveatOr(caveat("c1", nil), caveat("c2", nil)),
   366  			},
   367  			true,
   368  			false,
   369  		},
   370  	}
   371  
   372  	for _, tc := range tcs {
   373  		tc := tc
   374  		t.Run(tc.name, func(t *testing.T) {
   375  			ms1 := membershipSetFromMap(tc.set1)
   376  			ms2 := membershipSetFromMap(tc.set2)
   377  			ms1.UnionWith(ms2.AsCheckResultsMap())
   378  			require.Equal(t, tc.expected, ms1.membersByID)
   379  			require.Equal(t, tc.hasDeterminedMember, ms1.HasDeterminedMember())
   380  			require.Equal(t, tc.isEmpty, ms1.IsEmpty())
   381  		})
   382  	}
   383  }
   384  
   385  func TestMembershipSetIntersectWith(t *testing.T) {
   386  	tcs := []struct {
   387  		name                string
   388  		set1                map[string]*core.CaveatExpression
   389  		set2                map[string]*core.CaveatExpression
   390  		expected            map[string]*core.CaveatExpression
   391  		hasDeterminedMember bool
   392  		isEmpty             bool
   393  	}{
   394  		{
   395  			"empty with empty",
   396  			nil,
   397  			nil,
   398  			map[string]*core.CaveatExpression{},
   399  			false,
   400  			true,
   401  		},
   402  		{
   403  			"set with empty",
   404  			map[string]*core.CaveatExpression{
   405  				"somedoc": nil,
   406  			},
   407  			nil,
   408  			map[string]*core.CaveatExpression{},
   409  			false,
   410  			true,
   411  		},
   412  		{
   413  			"empty with set",
   414  			nil,
   415  			map[string]*core.CaveatExpression{
   416  				"somedoc": nil,
   417  			},
   418  			map[string]*core.CaveatExpression{},
   419  			false,
   420  			true,
   421  		},
   422  		{
   423  			"basic set with set",
   424  			map[string]*core.CaveatExpression{
   425  				"somedoc": nil,
   426  			},
   427  			map[string]*core.CaveatExpression{
   428  				"somedoc": nil,
   429  			},
   430  			map[string]*core.CaveatExpression{
   431  				"somedoc": nil,
   432  			},
   433  			true,
   434  			false,
   435  		},
   436  		{
   437  			"non-overlapping set with set",
   438  			map[string]*core.CaveatExpression{
   439  				"somedoc": nil,
   440  			},
   441  			map[string]*core.CaveatExpression{
   442  				"anotherdoc": nil,
   443  			},
   444  			map[string]*core.CaveatExpression{},
   445  			false,
   446  			true,
   447  		},
   448  		{
   449  			"partially overlapping set with set",
   450  			map[string]*core.CaveatExpression{
   451  				"somedoc":    nil,
   452  				"anotherdoc": nil,
   453  			},
   454  			map[string]*core.CaveatExpression{
   455  				"anotherdoc": nil,
   456  			},
   457  			map[string]*core.CaveatExpression{
   458  				"anotherdoc": nil,
   459  			},
   460  			true,
   461  			false,
   462  		},
   463  		{
   464  			"set with partially overlapping set",
   465  			map[string]*core.CaveatExpression{
   466  				"anotherdoc": nil,
   467  			},
   468  			map[string]*core.CaveatExpression{
   469  				"somedoc":    nil,
   470  				"anotherdoc": nil,
   471  			},
   472  			map[string]*core.CaveatExpression{
   473  				"anotherdoc": nil,
   474  			},
   475  			true,
   476  			false,
   477  		},
   478  		{
   479  			"partially overlapping sets with one caveat",
   480  			map[string]*core.CaveatExpression{
   481  				"anotherdoc": nil,
   482  			},
   483  			map[string]*core.CaveatExpression{
   484  				"somedoc":    nil,
   485  				"anotherdoc": caveat("c2", nil),
   486  			},
   487  			map[string]*core.CaveatExpression{
   488  				"anotherdoc": caveat("c2", nil),
   489  			},
   490  			false,
   491  			false,
   492  		},
   493  		{
   494  			"partially overlapping sets with one caveat (other side)",
   495  			map[string]*core.CaveatExpression{
   496  				"anotherdoc": caveat("c1", nil),
   497  			},
   498  			map[string]*core.CaveatExpression{
   499  				"somedoc":    nil,
   500  				"anotherdoc": nil,
   501  			},
   502  			map[string]*core.CaveatExpression{
   503  				"anotherdoc": caveat("c1", nil),
   504  			},
   505  			false,
   506  			false,
   507  		},
   508  		{
   509  			"partially overlapping sets with caveats",
   510  			map[string]*core.CaveatExpression{
   511  				"anotherdoc": caveat("c1", nil),
   512  			},
   513  			map[string]*core.CaveatExpression{
   514  				"somedoc":    nil,
   515  				"anotherdoc": caveat("c2", nil),
   516  			},
   517  			map[string]*core.CaveatExpression{
   518  				"anotherdoc": caveatAnd(
   519  					caveat("c1", nil),
   520  					caveat("c2", nil),
   521  				),
   522  			},
   523  			false,
   524  			false,
   525  		},
   526  		{
   527  			"overlapping sets with caveats and a determined member",
   528  			map[string]*core.CaveatExpression{
   529  				"somedoc":    nil,
   530  				"thirddoc":   nil,
   531  				"anotherdoc": caveat("c1", nil),
   532  			},
   533  			map[string]*core.CaveatExpression{
   534  				"somedoc":    nil,
   535  				"anotherdoc": caveat("c2", nil),
   536  			},
   537  			map[string]*core.CaveatExpression{
   538  				"somedoc": nil,
   539  				"anotherdoc": caveatAnd(
   540  					caveat("c1", nil),
   541  					caveat("c2", nil),
   542  				),
   543  			},
   544  			true,
   545  			false,
   546  		},
   547  	}
   548  
   549  	for _, tc := range tcs {
   550  		tc := tc
   551  		t.Run(tc.name, func(t *testing.T) {
   552  			ms1 := membershipSetFromMap(tc.set1)
   553  			ms2 := membershipSetFromMap(tc.set2)
   554  			ms1.IntersectWith(ms2.AsCheckResultsMap())
   555  			require.Equal(t, tc.expected, ms1.membersByID)
   556  			require.Equal(t, tc.hasDeterminedMember, ms1.HasDeterminedMember())
   557  			require.Equal(t, tc.isEmpty, ms1.IsEmpty())
   558  		})
   559  	}
   560  }
   561  
   562  func TestMembershipSetSubtract(t *testing.T) {
   563  	tcs := []struct {
   564  		name                string
   565  		set1                map[string]*core.CaveatExpression
   566  		set2                map[string]*core.CaveatExpression
   567  		expected            map[string]*core.CaveatExpression
   568  		hasDeterminedMember bool
   569  		isEmpty             bool
   570  	}{
   571  		{
   572  			"empty with empty",
   573  			nil,
   574  			nil,
   575  			map[string]*core.CaveatExpression{},
   576  			false,
   577  			true,
   578  		},
   579  		{
   580  			"empty with set",
   581  			nil,
   582  			map[string]*core.CaveatExpression{
   583  				"somedoc": nil,
   584  			},
   585  			map[string]*core.CaveatExpression{},
   586  			false,
   587  			true,
   588  		},
   589  		{
   590  			"set with empty",
   591  			map[string]*core.CaveatExpression{
   592  				"somedoc": nil,
   593  			},
   594  			nil,
   595  			map[string]*core.CaveatExpression{
   596  				"somedoc": nil,
   597  			},
   598  			true,
   599  			false,
   600  		},
   601  		{
   602  			"non overlapping sets",
   603  			map[string]*core.CaveatExpression{
   604  				"somedoc": nil,
   605  			},
   606  			map[string]*core.CaveatExpression{
   607  				"anotherdoc": nil,
   608  			},
   609  			map[string]*core.CaveatExpression{
   610  				"somedoc": nil,
   611  			},
   612  			true,
   613  			false,
   614  		},
   615  		{
   616  			"overlapping sets with no caveats",
   617  			map[string]*core.CaveatExpression{
   618  				"somedoc": nil,
   619  			},
   620  			map[string]*core.CaveatExpression{
   621  				"somedoc": nil,
   622  			},
   623  			map[string]*core.CaveatExpression{},
   624  			false,
   625  			true,
   626  		},
   627  		{
   628  			"overlapping sets with first having a caveat",
   629  			map[string]*core.CaveatExpression{
   630  				"somedoc": caveat("c1", nil),
   631  			},
   632  			map[string]*core.CaveatExpression{
   633  				"somedoc": nil,
   634  			},
   635  			map[string]*core.CaveatExpression{},
   636  			false,
   637  			true,
   638  		},
   639  		{
   640  			"overlapping sets with second having a caveat",
   641  			map[string]*core.CaveatExpression{
   642  				"somedoc": nil,
   643  			},
   644  			map[string]*core.CaveatExpression{
   645  				"somedoc": caveat("c2", nil),
   646  			},
   647  			map[string]*core.CaveatExpression{
   648  				"somedoc": invert(caveat("c2", nil)),
   649  			},
   650  			false,
   651  			false,
   652  		},
   653  		{
   654  			"overlapping sets with both having caveats",
   655  			map[string]*core.CaveatExpression{
   656  				"somedoc": caveat("c1", nil),
   657  			},
   658  			map[string]*core.CaveatExpression{
   659  				"somedoc": caveat("c2", nil),
   660  			},
   661  			map[string]*core.CaveatExpression{
   662  				"somedoc": caveatAnd(
   663  					caveat("c1", nil),
   664  					invert(caveat("c2", nil)),
   665  				),
   666  			},
   667  			false,
   668  			false,
   669  		},
   670  		{
   671  			"overlapping sets with both having caveats and determined member",
   672  			map[string]*core.CaveatExpression{
   673  				"somedoc":    caveat("c1", nil),
   674  				"anotherdoc": nil,
   675  			},
   676  			map[string]*core.CaveatExpression{
   677  				"somedoc": caveat("c2", nil),
   678  			},
   679  			map[string]*core.CaveatExpression{
   680  				"anotherdoc": nil,
   681  				"somedoc": caveatAnd(
   682  					caveat("c1", nil),
   683  					invert(caveat("c2", nil)),
   684  				),
   685  			},
   686  			true,
   687  			false,
   688  		},
   689  		{
   690  			"overlapping sets with both having caveats and determined members",
   691  			map[string]*core.CaveatExpression{
   692  				"somedoc":    caveat("c1", nil),
   693  				"anotherdoc": nil,
   694  			},
   695  			map[string]*core.CaveatExpression{
   696  				"somedoc":    caveat("c2", nil),
   697  				"anotherdoc": nil,
   698  			},
   699  			map[string]*core.CaveatExpression{
   700  				"somedoc": caveatAnd(
   701  					caveat("c1", nil),
   702  					invert(caveat("c2", nil)),
   703  				),
   704  			},
   705  			false,
   706  			false,
   707  		},
   708  	}
   709  
   710  	for _, tc := range tcs {
   711  		tc := tc
   712  		t.Run(tc.name, func(t *testing.T) {
   713  			ms1 := membershipSetFromMap(tc.set1)
   714  			ms2 := membershipSetFromMap(tc.set2)
   715  			ms1.Subtract(ms2.AsCheckResultsMap())
   716  			require.Equal(t, tc.expected, ms1.membersByID)
   717  			require.Equal(t, tc.hasDeterminedMember, ms1.HasDeterminedMember())
   718  			require.Equal(t, tc.isEmpty, ms1.IsEmpty())
   719  		})
   720  	}
   721  }
   722  
   723  func unwrapCaveat(ce *core.CaveatExpression) *core.ContextualizedCaveat {
   724  	if ce == nil {
   725  		return nil
   726  	}
   727  	return ce.GetCaveat()
   728  }
   729  
   730  func withCaveat(tple *core.RelationTuple, ce *core.CaveatExpression) *core.RelationTuple {
   731  	tple.Caveat = unwrapCaveat(ce)
   732  	return tple
   733  }