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 }