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

     1  package graph
     2  
     3  import (
     4  	"sort"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  	"golang.org/x/exp/slices"
     9  
    10  	v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1"
    11  )
    12  
    13  func fsubs(subjectIDs ...string) *v1.FoundSubjects {
    14  	subs := make([]*v1.FoundSubject, 0, len(subjectIDs))
    15  	for _, subjectID := range subjectIDs {
    16  		subs = append(subs, fs(subjectID))
    17  	}
    18  	return &v1.FoundSubjects{
    19  		FoundSubjects: subs,
    20  	}
    21  }
    22  
    23  func fs(subjectID string) *v1.FoundSubject {
    24  	return &v1.FoundSubject{
    25  		SubjectId: subjectID,
    26  	}
    27  }
    28  
    29  func TestCreateFilteredAndLimitedResponse(t *testing.T) {
    30  	tcs := []struct {
    31  		name                   string
    32  		subjectIDCursor        string
    33  		input                  map[string]*v1.FoundSubjects
    34  		limit                  uint32
    35  		expected               map[string]*v1.FoundSubjects
    36  		expectedCursorSections []string
    37  	}{
    38  		{
    39  			"basic limit, no filtering",
    40  			"",
    41  			map[string]*v1.FoundSubjects{
    42  				"foo": fsubs("a", "b", "c"),
    43  				"bar": fsubs("a", "b", "d"),
    44  			},
    45  			3,
    46  			map[string]*v1.FoundSubjects{
    47  				"foo": fsubs("a", "b", "c"),
    48  				"bar": fsubs("a", "b"),
    49  			},
    50  			[]string{"c"},
    51  		},
    52  		{
    53  			"basic limit removes key",
    54  			"",
    55  			map[string]*v1.FoundSubjects{
    56  				"foo": fsubs("a", "b", "c"),
    57  				"bar": fsubs("b", "d"),
    58  			},
    59  			1,
    60  			map[string]*v1.FoundSubjects{
    61  				"foo": fsubs("a"),
    62  			},
    63  			[]string{"a"},
    64  		},
    65  		{
    66  			"limit maintains wildcard",
    67  			"",
    68  			map[string]*v1.FoundSubjects{
    69  				"foo": fsubs("a", "b", "c"),
    70  				"bar": fsubs("b", "d", "*"),
    71  			},
    72  			1,
    73  			map[string]*v1.FoundSubjects{
    74  				"foo": fsubs("a"),
    75  				"bar": fsubs("*"),
    76  			},
    77  			[]string{"a"},
    78  		},
    79  		{
    80  			"basic limit, with filtering",
    81  			"a",
    82  			map[string]*v1.FoundSubjects{
    83  				"foo": fsubs("a", "b", "c"),
    84  				"bar": fsubs("a", "b", "d"),
    85  			},
    86  			2,
    87  			map[string]*v1.FoundSubjects{
    88  				"foo": fsubs("b", "c"),
    89  				"bar": fsubs("b"),
    90  			},
    91  			[]string{"c"},
    92  		},
    93  		{
    94  			"basic limit, with filtering includes both",
    95  			"a",
    96  			map[string]*v1.FoundSubjects{
    97  				"foo": fsubs("a", "b", "c"),
    98  				"bar": fsubs("a", "b", "d"),
    99  			},
   100  			3,
   101  			map[string]*v1.FoundSubjects{
   102  				"foo": fsubs("b", "c"),
   103  				"bar": fsubs("b", "d"),
   104  			},
   105  			[]string{"d"},
   106  		},
   107  		{
   108  			"filtered limit maintains wildcard",
   109  			"z",
   110  			map[string]*v1.FoundSubjects{
   111  				"foo": fsubs("a", "b", "*", "c"),
   112  				"bar": fsubs("b", "d", "*"),
   113  			},
   114  			10,
   115  			map[string]*v1.FoundSubjects{
   116  				"foo": fsubs("*"),
   117  				"bar": fsubs("*"),
   118  			},
   119  			[]string{"*"},
   120  		},
   121  	}
   122  
   123  	for _, tc := range tcs {
   124  		t.Run(tc.name, func(t *testing.T) {
   125  			limits := newLimitTracker(tc.limit)
   126  
   127  			var cursor *v1.Cursor
   128  			if tc.subjectIDCursor != "" {
   129  				cursor = &v1.Cursor{
   130  					DispatchVersion: lsDispatchVersion,
   131  					Sections:        []string{tc.subjectIDCursor},
   132  				}
   133  			}
   134  
   135  			ci, err := newCursorInformation(cursor, limits, lsDispatchVersion)
   136  			require.NoError(t, err)
   137  
   138  			resp, _, err := createFilteredAndLimitedResponse(ci, tc.input, emptyMetadata)
   139  			require.NoError(t, err)
   140  			require.Equal(t, tc.expected, resp.FoundSubjectsByResourceId)
   141  			require.Equal(t, tc.expectedCursorSections, resp.AfterResponseCursor.Sections)
   142  		})
   143  	}
   144  }
   145  
   146  func TestFilterSubjectsMap(t *testing.T) {
   147  	tcs := []struct {
   148  		name              string
   149  		input             map[string]*v1.FoundSubjects
   150  		allowedSubjectIds []string
   151  		expected          map[string]*v1.FoundSubjects
   152  	}{
   153  		{
   154  			"filter to empty",
   155  			map[string]*v1.FoundSubjects{
   156  				"foo": fsubs("first"),
   157  			},
   158  			nil,
   159  			map[string]*v1.FoundSubjects{},
   160  		},
   161  		{
   162  			"filter and remove key",
   163  			map[string]*v1.FoundSubjects{
   164  				"foo": fsubs("first", "second", "third"),
   165  				"bar": fsubs("first", "second", "fourth"),
   166  			},
   167  			[]string{"third"},
   168  			map[string]*v1.FoundSubjects{
   169  				"foo": fsubs("third"),
   170  			},
   171  		},
   172  		{
   173  			"filter multiple keys",
   174  			map[string]*v1.FoundSubjects{
   175  				"foo": fsubs("first", "second", "third"),
   176  				"bar": fsubs("first", "second", "fourth"),
   177  			},
   178  			[]string{"first"},
   179  			map[string]*v1.FoundSubjects{
   180  				"foo": fsubs("first"),
   181  				"bar": fsubs("first"),
   182  			},
   183  		},
   184  		{
   185  			"filter multiple keys with multiple values",
   186  			map[string]*v1.FoundSubjects{
   187  				"foo": fsubs("first", "second", "third"),
   188  				"bar": fsubs("first", "second", "fourth"),
   189  			},
   190  			[]string{"first", "second"},
   191  			map[string]*v1.FoundSubjects{
   192  				"foo": fsubs("first", "second"),
   193  				"bar": fsubs("first", "second"),
   194  			},
   195  		},
   196  		{
   197  			"filter remove key with multiple values",
   198  			map[string]*v1.FoundSubjects{
   199  				"foo": fsubs("first", "second", "third"),
   200  				"bar": fsubs("first", "second", "fourth"),
   201  			},
   202  			[]string{"third", "fourth"},
   203  			map[string]*v1.FoundSubjects{
   204  				"foo": fsubs("third"),
   205  				"bar": fsubs("fourth"),
   206  			},
   207  		},
   208  	}
   209  
   210  	for _, tc := range tcs {
   211  		t.Run(tc.name, func(t *testing.T) {
   212  			filtered := filterSubjectsMap(tc.input, tc.allowedSubjectIds)
   213  			require.Equal(t, tc.expected, filtered)
   214  
   215  			for _, values := range filtered {
   216  				sorted := slices.Clone(values.FoundSubjects)
   217  				sort.Sort(bySubjectID(sorted))
   218  				require.Equal(t, sorted, values.FoundSubjects, "found unsorted subjects: %v", values.FoundSubjects)
   219  			}
   220  		})
   221  	}
   222  }