@@ -271,3 +271,275 @@ func TestAllReachableRelations(t *testing.T) {
271271 })
272272 }
273273}
274+
275+ func TestLookupArrowsWithComputedUserset (t * testing.T ) {
276+ t .Parallel ()
277+
278+ type expectedArrow struct {
279+ parentNamespace string
280+ parentRelation string
281+ arrowExpression string
282+ }
283+
284+ type testcase struct {
285+ name string
286+ schemaText string
287+
288+ targetNamespace string
289+ targetRelation string
290+ expected []expectedArrow
291+ }
292+
293+ tcs := []testcase {
294+ {
295+ name : "basic arrow" ,
296+ schemaText : `
297+ definition user {}
298+
299+ definition organization {
300+ relation member: user
301+ }
302+
303+ definition resource {
304+ relation org: organization
305+ permission view = org->member
306+ }
307+ ` ,
308+ targetNamespace : "organization" ,
309+ targetRelation : "member" ,
310+ expected : []expectedArrow {
311+ {
312+ parentNamespace : "resource" ,
313+ parentRelation : "view" ,
314+ arrowExpression : "org->member" ,
315+ },
316+ },
317+ },
318+ {
319+ name : "multiple arrows to same relation" ,
320+ schemaText : `
321+ definition user {}
322+
323+ definition organization {
324+ relation member: user
325+ }
326+
327+ definition resource {
328+ relation org: organization
329+ permission view = org->member
330+ permission another_view = org->member
331+ }
332+
333+ definition another_resource {
334+ relation org: organization
335+ permission view = org->member
336+ }
337+ ` ,
338+ targetNamespace : "organization" ,
339+ targetRelation : "member" ,
340+ expected : []expectedArrow {
341+ {
342+ parentNamespace : "resource" ,
343+ parentRelation : "view" ,
344+ arrowExpression : "org->member" ,
345+ },
346+ {
347+ parentNamespace : "resource" ,
348+ parentRelation : "another_view" ,
349+ arrowExpression : "org->member" ,
350+ },
351+ {
352+ parentNamespace : "another_resource" ,
353+ parentRelation : "view" ,
354+ arrowExpression : "org->member" ,
355+ },
356+ },
357+ },
358+ {
359+ name : "arrows across different namespaces" ,
360+ schemaText : `
361+ definition user {}
362+
363+ definition organization {
364+ relation member: user
365+ }
366+
367+ definition project {
368+ relation org: organization
369+ permission view = org->member
370+ }
371+
372+ definition document {
373+ relation org: organization
374+ permission view = org->member
375+ }
376+ ` ,
377+ targetNamespace : "organization" ,
378+ targetRelation : "member" ,
379+ expected : []expectedArrow {
380+ {
381+ parentNamespace : "project" ,
382+ parentRelation : "view" ,
383+ arrowExpression : "org->member" ,
384+ },
385+ {
386+ parentNamespace : "document" ,
387+ parentRelation : "view" ,
388+ arrowExpression : "org->member" ,
389+ },
390+ },
391+ },
392+ {
393+ name : "arrow in union and exclusion expression" ,
394+ schemaText : `
395+ definition user {}
396+
397+ definition organization {
398+ relation member: user
399+ }
400+
401+ definition resource {
402+ relation reader: user
403+ relation writer: user
404+ relation banned: user
405+ relation org: organization
406+ permission view = reader + writer - banned + org->member
407+ }
408+ ` ,
409+ targetNamespace : "organization" ,
410+ targetRelation : "member" ,
411+ expected : []expectedArrow {
412+ {
413+ parentNamespace : "resource" ,
414+ parentRelation : "view" ,
415+ arrowExpression : "org->member" ,
416+ },
417+ },
418+ },
419+ {
420+ name : "chained arrow through intermediate type" ,
421+ schemaText : `
422+ definition user {}
423+
424+ definition team {
425+ relation member: user
426+ }
427+
428+ definition organization {
429+ relation team: team
430+ permission member = team->member
431+ }
432+
433+ definition resource {
434+ relation org: organization
435+ permission view = org->member
436+ }
437+ ` ,
438+ targetNamespace : "team" ,
439+ targetRelation : "member" ,
440+ expected : []expectedArrow {
441+ {
442+ parentNamespace : "organization" ,
443+ parentRelation : "member" ,
444+ arrowExpression : "team->member" ,
445+ },
446+ },
447+ },
448+ {
449+ name : "multiple arrows in nested expression" ,
450+ schemaText : `
451+ definition user {}
452+
453+ definition team {
454+ relation member: user
455+ }
456+
457+ definition organization {
458+ relation admin: user
459+ relation team: team
460+ permission member = admin + team->member
461+ }
462+
463+ definition resource {
464+ relation org: organization
465+ relation team: team
466+ permission view = org->member + team->member
467+ }
468+ ` ,
469+ targetNamespace : "team" ,
470+ targetRelation : "member" ,
471+ expected : []expectedArrow {
472+ {
473+ parentNamespace : "organization" ,
474+ parentRelation : "member" ,
475+ arrowExpression : "team->member" ,
476+ },
477+ {
478+ parentNamespace : "resource" ,
479+ parentRelation : "view" ,
480+ arrowExpression : "team->member" ,
481+ },
482+ },
483+ },
484+ {
485+ name : "arrow with intersection and exclusion" ,
486+ schemaText : `
487+ definition user {}
488+
489+ definition organization {
490+ relation member: user
491+ relation admin: user
492+ }
493+
494+ definition resource {
495+ relation org: organization
496+ relation blocked: user
497+ permission view = org->member & org->admin - blocked
498+ }
499+ ` ,
500+ targetNamespace : "organization" ,
501+ targetRelation : "member" ,
502+ expected : []expectedArrow {
503+ {
504+ parentNamespace : "resource" ,
505+ parentRelation : "view" ,
506+ arrowExpression : "org->member" ,
507+ },
508+ },
509+ },
510+ }
511+
512+ for _ , tc := range tcs {
513+ t .Run (tc .name , func (t * testing.T ) {
514+ tc := tc
515+ t .Parallel ()
516+
517+ schema , err := compiler .Compile (compiler.InputSchema {
518+ Source : "" ,
519+ SchemaString : tc .schemaText ,
520+ }, compiler .AllowUnprefixedObjectType ())
521+ require .NoError (t , err )
522+
523+ res := ResolverForCompiledSchema (* schema )
524+ arrowSet , err := buildArrowSet (t .Context (), res )
525+ require .NoError (t , err )
526+
527+ arrows := arrowSet .LookupArrowsWithComputedUserset (tc .targetNamespace , tc .targetRelation )
528+ require .Len (t , arrows , len (tc .expected ))
529+
530+ for _ , expected := range tc .expected {
531+ found := false
532+ for _ , actual := range arrows {
533+ actualExpression := actual .Arrow .Tupleset .Relation + "->" + actual .Arrow .ComputedUserset .Relation
534+ if actual .ParentNamespace == expected .parentNamespace &&
535+ actual .ParentRelationName == expected .parentRelation &&
536+ actualExpression == expected .arrowExpression {
537+ found = true
538+ break
539+ }
540+ }
541+ require .True (t , found , "expected arrow %v not found in %v" , expected , arrows )
542+ }
543+ })
544+ }
545+ }
0 commit comments