github.com/openfga/openfga@v1.5.4-rc1/internal/graph/graph_test.go (about)

     1  package graph
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/google/go-cmp/cmp/cmpopts"
    10  	openfgav1 "github.com/openfga/api/proto/openfga/v1"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/openfga/openfga/pkg/testutils"
    14  	"github.com/openfga/openfga/pkg/typesystem"
    15  )
    16  
    17  var (
    18  	RelationshipEdgeTransformer = cmp.Transformer("Sort", func(in []*RelationshipEdge) []*RelationshipEdge {
    19  		out := append([]*RelationshipEdge(nil), in...) // Copy input to avoid mutating it
    20  
    21  		// Sort by Type and then by edge and then by tupleset relation
    22  		sort.SliceStable(out, func(i, j int) bool {
    23  			if out[i].Type != out[j].Type {
    24  				return out[i].Type < out[j].Type
    25  			}
    26  
    27  			if typesystem.GetRelationReferenceAsString(out[i].TargetReference) != typesystem.GetRelationReferenceAsString(out[j].TargetReference) {
    28  				return typesystem.GetRelationReferenceAsString(out[i].TargetReference) < typesystem.GetRelationReferenceAsString(out[j].TargetReference)
    29  			}
    30  
    31  			if out[i].TuplesetRelation != out[j].TuplesetRelation {
    32  				return out[i].TuplesetRelation < out[j].TuplesetRelation
    33  			}
    34  
    35  			return true
    36  		})
    37  
    38  		return out
    39  	})
    40  )
    41  
    42  func TestRelationshipEdge_String(t *testing.T) {
    43  	for _, tc := range []struct {
    44  		name             string
    45  		expected         string
    46  		relationshipEdge RelationshipEdge
    47  	}{
    48  		{
    49  			name:     "TupleToUsersetEdge",
    50  			expected: "userset type:\"document\" relation:\"viewer\", type ttu, tupleset parent",
    51  			relationshipEdge: RelationshipEdge{
    52  				Type:             TupleToUsersetEdge,
    53  				TargetReference:  typesystem.DirectRelationReference("document", "viewer"),
    54  				TuplesetRelation: "parent",
    55  				TargetReferenceInvolvesIntersectionOrExclusion: false,
    56  			},
    57  		},
    58  		{
    59  			name:     "ComputedUsersetEdge",
    60  			expected: "userset type:\"document\" relation:\"viewer\", type computed_userset",
    61  			relationshipEdge: RelationshipEdge{
    62  				Type:            ComputedUsersetEdge,
    63  				TargetReference: typesystem.DirectRelationReference("document", "viewer"),
    64  				TargetReferenceInvolvesIntersectionOrExclusion: false,
    65  			},
    66  		},
    67  		{
    68  			name:     "DirectEdge",
    69  			expected: "userset type:\"document\" relation:\"viewer\", type direct",
    70  			relationshipEdge: RelationshipEdge{
    71  				Type:            DirectEdge,
    72  				TargetReference: typesystem.DirectRelationReference("document", "viewer"),
    73  				TargetReferenceInvolvesIntersectionOrExclusion: false,
    74  			},
    75  		},
    76  	} {
    77  		t.Run(tc.name, func(t *testing.T) {
    78  			require.Equal(t, tc.expected, tc.relationshipEdge.String())
    79  		})
    80  	}
    81  }
    82  
    83  func TestRelationshipEdgeType_String(t *testing.T) {
    84  	require.Equal(t, "direct", DirectEdge.String())
    85  	require.Equal(t, "computed_userset", ComputedUsersetEdge.String())
    86  	require.Equal(t, "ttu", TupleToUsersetEdge.String())
    87  	require.Equal(t, "undefined", RelationshipEdgeType(4).String())
    88  }
    89  
    90  func TestPrunedRelationshipEdges(t *testing.T) {
    91  	tests := []struct {
    92  		name     string
    93  		model    string
    94  		target   *openfgav1.RelationReference
    95  		source   *openfgav1.RelationReference
    96  		expected []*RelationshipEdge
    97  	}{
    98  		{
    99  			name: "basic_intersection",
   100  			model: `model
   101  	schema 1.1
   102  type user
   103  
   104  type document
   105    relations
   106  	define allowed: [user]
   107  	define viewer: [user] and allowed`,
   108  			target: typesystem.DirectRelationReference("document", "viewer"),
   109  			source: typesystem.DirectRelationReference("user", ""),
   110  			expected: []*RelationshipEdge{
   111  				{
   112  					Type:            DirectEdge,
   113  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
   114  					TargetReferenceInvolvesIntersectionOrExclusion: true,
   115  				},
   116  			},
   117  		},
   118  		{
   119  			name: "basic_intersection_through_ttu_1",
   120  			model: `model
   121  	schema 1.1
   122  type user
   123  
   124  type folder
   125    relations
   126  	define allowed: [user]
   127  	define viewer: [user] and allowed
   128  
   129  type document
   130    relations
   131  	define parent: [folder]
   132  	define viewer: viewer from parent`,
   133  			target: typesystem.DirectRelationReference("document", "viewer"),
   134  			source: typesystem.DirectRelationReference("user", ""),
   135  			expected: []*RelationshipEdge{
   136  				{
   137  					Type:            DirectEdge,
   138  					TargetReference: typesystem.DirectRelationReference("folder", "viewer"),
   139  					TargetReferenceInvolvesIntersectionOrExclusion: true,
   140  				},
   141  			},
   142  		},
   143  		{
   144  			name: "basic_intersection_through_ttu_2",
   145  			model: `model
   146  	schema 1.1
   147  type user
   148  
   149  type organization
   150    relations
   151  	define allowed: [user]
   152  	define viewer: [user] and allowed
   153  
   154  type folder
   155    relations
   156  	define parent: [organization]
   157  	define viewer: viewer from parent
   158  
   159  type document
   160    relations
   161  	define parent: [folder]
   162  	define viewer: viewer from parent`,
   163  			target: typesystem.DirectRelationReference("document", "viewer"),
   164  			source: typesystem.DirectRelationReference("folder", "viewer"),
   165  			expected: []*RelationshipEdge{
   166  				{
   167  					Type:             TupleToUsersetEdge,
   168  					TargetReference:  typesystem.DirectRelationReference("document", "viewer"),
   169  					TuplesetRelation: "parent",
   170  					TargetReferenceInvolvesIntersectionOrExclusion: true,
   171  				},
   172  			},
   173  		},
   174  		{
   175  			name: "basic_exclusion_through_ttu_1",
   176  			model: `model
   177  	schema 1.1
   178  type user
   179  
   180  type folder
   181    relations
   182  	define writer: [user]
   183  	define editor: [user]
   184  	define viewer: writer but not editor
   185  
   186  type document
   187    relations
   188  	define parent: [folder]
   189  	define viewer: viewer from parent`,
   190  			target: typesystem.DirectRelationReference("document", "viewer"),
   191  			source: typesystem.DirectRelationReference("user", ""),
   192  			expected: []*RelationshipEdge{
   193  				{
   194  					Type:            DirectEdge,
   195  					TargetReference: typesystem.DirectRelationReference("folder", "writer"),
   196  					TargetReferenceInvolvesIntersectionOrExclusion: true,
   197  				},
   198  			},
   199  		},
   200  		{
   201  			name: "basic_exclusion_through_ttu_2",
   202  			model: `model
   203  	schema 1.1
   204  type user
   205  
   206  type folder
   207    relations
   208  	define writer: [user]
   209  	define editor: [user]
   210  	define viewer: writer but not editor
   211  
   212  type document
   213    relations
   214  	define parent: [folder]
   215  	define viewer: viewer from parent`,
   216  			target: typesystem.DirectRelationReference("document", "viewer"),
   217  			source: typesystem.DirectRelationReference("folder", "viewer"),
   218  			expected: []*RelationshipEdge{
   219  				{
   220  					Type:             TupleToUsersetEdge,
   221  					TargetReference:  typesystem.DirectRelationReference("document", "viewer"),
   222  					TuplesetRelation: "parent",
   223  					TargetReferenceInvolvesIntersectionOrExclusion: true,
   224  				},
   225  			},
   226  		},
   227  		{
   228  			name: "ttu_with_indirect",
   229  			model: `model
   230  	schema 1.1
   231  type user
   232  type repo
   233    relations
   234      define admin: [user] or repo_admin from owner
   235  	define owner: [organization]
   236  type organization
   237    relations
   238      define member: [user] or owner
   239  	define owner: [user]
   240  	define repo_admin: [user, organization#member]`,
   241  			target: typesystem.DirectRelationReference("repo", "admin"),
   242  			source: typesystem.DirectRelationReference("organization", "member"),
   243  			expected: []*RelationshipEdge{
   244  				{
   245  					Type:            DirectEdge,
   246  					TargetReference: typesystem.DirectRelationReference("organization", "repo_admin"),
   247  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   248  				},
   249  			},
   250  		},
   251  	}
   252  
   253  	for _, test := range tests {
   254  		t.Run(test.name, func(t *testing.T) {
   255  			model := testutils.MustTransformDSLToProtoWithID(test.model)
   256  			typesys := typesystem.New(model)
   257  
   258  			g := New(typesys)
   259  
   260  			edges, err := g.GetPrunedRelationshipEdges(test.target, test.source)
   261  			require.NoError(t, err)
   262  
   263  			cmpOpts := []cmp.Option{
   264  				cmpopts.IgnoreUnexported(openfgav1.RelationReference{}),
   265  				RelationshipEdgeTransformer,
   266  			}
   267  			if diff := cmp.Diff(test.expected, edges, cmpOpts...); diff != "" {
   268  				t.Errorf("mismatch (-want +got):\n%s", diff)
   269  			}
   270  		})
   271  	}
   272  }
   273  
   274  func TestRelationshipEdges(t *testing.T) {
   275  	tests := []struct {
   276  		name      string
   277  		model     string
   278  		authModel *openfgav1.AuthorizationModel // for models that have "self" or "this" at the end of the relation definition
   279  		target    *openfgav1.RelationReference
   280  		source    *openfgav1.RelationReference
   281  		expected  []*RelationshipEdge
   282  	}{
   283  		{
   284  			name: "direct_edge_through_ComputedUserset_with_multiple_type_restrictions",
   285  			model: `model
   286  	schema 1.1
   287  type user
   288  
   289  type group
   290    relations
   291  	define member: [user, group#member]
   292  
   293  type document
   294    relations
   295  	define editor: [user, group#member]
   296  	define viewer: editor`,
   297  			target: typesystem.DirectRelationReference("document", "viewer"),
   298  			source: typesystem.DirectRelationReference("user", ""),
   299  			expected: []*RelationshipEdge{
   300  				{
   301  					Type:            DirectEdge,
   302  					TargetReference: typesystem.DirectRelationReference("document", "editor"),
   303  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   304  				},
   305  				{
   306  					Type:            DirectEdge,
   307  					TargetReference: typesystem.DirectRelationReference("group", "member"),
   308  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   309  				},
   310  			},
   311  		},
   312  		{
   313  			name: "direct_edge_through_ComputedUserset",
   314  			model: `model
   315  	schema 1.1
   316  type user
   317  
   318  type document
   319    relations
   320  	define editor: [user]
   321  	define viewer: editor`,
   322  			target: typesystem.DirectRelationReference("document", "viewer"),
   323  			source: typesystem.DirectRelationReference("user", ""),
   324  			expected: []*RelationshipEdge{
   325  				{
   326  					Type:            DirectEdge,
   327  					TargetReference: typesystem.DirectRelationReference("document", "editor"),
   328  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   329  				},
   330  			},
   331  		},
   332  		{
   333  			name: "direct_edge_through_TupleToUserset_with_multiple_type_restrictions",
   334  			model: `model
   335  	schema 1.1
   336  type user
   337  
   338  type group
   339    relations
   340  	define member: [user]
   341  
   342  type folder
   343    relations
   344  	define viewer: [user, group#member]
   345  
   346  type document
   347    relations
   348  	define parent: [folder]
   349  	define viewer: [user] or viewer from parent`,
   350  			target: typesystem.DirectRelationReference("document", "viewer"),
   351  			source: typesystem.DirectRelationReference("user", ""),
   352  			expected: []*RelationshipEdge{
   353  				{
   354  					Type:            DirectEdge,
   355  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
   356  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   357  				},
   358  				{
   359  					Type:            DirectEdge,
   360  					TargetReference: typesystem.DirectRelationReference("folder", "viewer"),
   361  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   362  				},
   363  				{
   364  					Type:            DirectEdge,
   365  					TargetReference: typesystem.DirectRelationReference("group", "member"),
   366  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   367  				},
   368  			},
   369  		},
   370  		{
   371  			name: "direct_edge_with_union_involving_self_and_computed_userset",
   372  			model: `model
   373  	schema 1.1
   374  type user
   375  
   376  type group
   377    relations
   378  	define member: [user, group#member]
   379  
   380  type document
   381    relations
   382  	define editor: [user, group#member]
   383  	define viewer: [user] or editor`,
   384  			target: typesystem.DirectRelationReference("document", "viewer"),
   385  			source: typesystem.DirectRelationReference("user", ""),
   386  			expected: []*RelationshipEdge{
   387  				{
   388  					Type:            DirectEdge,
   389  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
   390  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   391  				},
   392  				{
   393  					Type:            DirectEdge,
   394  					TargetReference: typesystem.DirectRelationReference("document", "editor"),
   395  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   396  				},
   397  				{
   398  					Type:            DirectEdge,
   399  					TargetReference: typesystem.DirectRelationReference("group", "member"),
   400  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   401  				},
   402  			},
   403  		},
   404  		{
   405  			name: "circular_reference",
   406  			model: `model
   407  	schema 1.1
   408  type user
   409  
   410  type team
   411    relations
   412  	define member: [group#member]
   413  
   414  type group
   415    relations
   416  	define member: [user, team#member]`,
   417  			target: typesystem.DirectRelationReference("team", "member"),
   418  			source: typesystem.DirectRelationReference("user", ""),
   419  			expected: []*RelationshipEdge{
   420  				{
   421  					Type:            DirectEdge,
   422  					TargetReference: typesystem.DirectRelationReference("group", "member"),
   423  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   424  				},
   425  			},
   426  		},
   427  		{
   428  			name: "cyclical_parent/child_definition",
   429  			model: `model
   430  	schema 1.1
   431  type user
   432  
   433  type folder
   434    relations
   435  	define parent: [folder]
   436  	define viewer: [user] or viewer from parent`,
   437  			target: typesystem.DirectRelationReference("folder", "viewer"),
   438  			source: typesystem.DirectRelationReference("user", ""),
   439  			expected: []*RelationshipEdge{
   440  				{
   441  					Type:            DirectEdge,
   442  					TargetReference: typesystem.DirectRelationReference("folder", "viewer"),
   443  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   444  				},
   445  			},
   446  		},
   447  		{
   448  			name: "no_graph_relationship_connectivity",
   449  			model: `model
   450  	schema 1.1
   451  type user
   452  
   453  type team
   454    relations
   455  	define member: [team#member]`,
   456  			target:   typesystem.DirectRelationReference("team", "member"),
   457  			source:   typesystem.DirectRelationReference("user", ""),
   458  			expected: []*RelationshipEdge{},
   459  		},
   460  		{
   461  			name: "test1",
   462  			model: `model
   463  	schema 1.1
   464  type user
   465  
   466  type group
   467    relations
   468  	define member: [user]
   469  
   470  type folder
   471    relations
   472  	define viewer: [user, group#member]
   473  
   474  type document
   475    relations
   476  	define parent: [folder]
   477  	define viewer: viewer from parent`,
   478  			target: typesystem.DirectRelationReference("document", "viewer"),
   479  			source: typesystem.DirectRelationReference("user", ""),
   480  			expected: []*RelationshipEdge{
   481  				{
   482  					Type:            DirectEdge,
   483  					TargetReference: typesystem.DirectRelationReference("folder", "viewer"),
   484  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   485  				},
   486  				{
   487  					Type:            DirectEdge,
   488  					TargetReference: typesystem.DirectRelationReference("group", "member"),
   489  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   490  				},
   491  			},
   492  		},
   493  		{
   494  			name: "test2",
   495  			model: `model
   496  	schema 1.1
   497  type user
   498  
   499  type group
   500    relations
   501  	define member: [user]
   502  
   503  type folder
   504    relations
   505  	define viewer: [user, group#member]
   506  
   507  type document
   508    relations
   509  	define parent: [folder]
   510  	define viewer: viewer from parent`,
   511  			target: typesystem.DirectRelationReference("document", "viewer"),
   512  			source: typesystem.DirectRelationReference("group", "member"),
   513  			expected: []*RelationshipEdge{
   514  				{
   515  					Type:            DirectEdge,
   516  					TargetReference: typesystem.DirectRelationReference("folder", "viewer"),
   517  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   518  				},
   519  			},
   520  		},
   521  		{
   522  			name: "test3",
   523  			model: `model
   524  	schema 1.1
   525  type user
   526  
   527  type folder
   528    relations
   529  	define viewer: [user]
   530  
   531  type document
   532    relations
   533  	define parent: [folder]
   534  	define viewer: viewer from parent`,
   535  			target: typesystem.DirectRelationReference("document", "viewer"),
   536  			source: typesystem.DirectRelationReference("folder", "viewer"),
   537  			expected: []*RelationshipEdge{
   538  				{
   539  					Type:             TupleToUsersetEdge,
   540  					TargetReference:  typesystem.DirectRelationReference("document", "viewer"),
   541  					TuplesetRelation: "parent",
   542  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   543  				},
   544  			},
   545  		},
   546  		{
   547  			name: "undefined_relation_on_one_type_involved_in_a_ttu",
   548  			model: `model
   549  	schema 1.1
   550  type user
   551  type organization
   552  
   553  type folder
   554    relations
   555  	define viewer: [user]
   556  
   557  type document
   558    relations
   559  	define parent: [folder, organization]
   560  	define viewer: viewer from parent`,
   561  			target: typesystem.DirectRelationReference("document", "viewer"),
   562  			source: typesystem.DirectRelationReference("user", ""),
   563  			expected: []*RelationshipEdge{
   564  				{
   565  					Type:            DirectEdge,
   566  					TargetReference: typesystem.DirectRelationReference("folder", "viewer"),
   567  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   568  				},
   569  			},
   570  		},
   571  		{
   572  			name: "nested_group_membership_returns_only_top-level_edge",
   573  			model: `model
   574  	schema 1.1
   575  type user
   576  
   577  type group
   578    relations
   579  	define member: [user, group#member]`,
   580  			target: typesystem.DirectRelationReference("group", "member"),
   581  			source: typesystem.DirectRelationReference("user", ""),
   582  			expected: []*RelationshipEdge{
   583  				{
   584  					Type:            DirectEdge,
   585  					TargetReference: typesystem.DirectRelationReference("group", "member"),
   586  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   587  				},
   588  			},
   589  		},
   590  		{
   591  			name: "edges_for_non-assignable_relation",
   592  			model: `model
   593  	schema 1.1
   594  type organization
   595    relations
   596  	define viewer: [organization]
   597  	define can_view: viewer
   598  
   599  type document
   600    relations
   601  	define parent: [organization]
   602  	define view: can_view from parent`,
   603  			target: typesystem.DirectRelationReference("document", "view"),
   604  			source: typesystem.DirectRelationReference("organization", ""),
   605  			expected: []*RelationshipEdge{
   606  				{
   607  					Type:            DirectEdge,
   608  					TargetReference: typesystem.DirectRelationReference("organization", "viewer"),
   609  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   610  				},
   611  			},
   612  		},
   613  		{
   614  			name: "user_is_a_subset_of_user_*",
   615  			model: `model
   616  	schema 1.1
   617  type user
   618  
   619  type document
   620    relations
   621  	define viewer: [user:*]`,
   622  			target: typesystem.DirectRelationReference("document", "viewer"),
   623  			source: typesystem.DirectRelationReference("user", ""),
   624  			expected: []*RelationshipEdge{
   625  				{
   626  					Type:            DirectEdge,
   627  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
   628  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   629  				},
   630  			},
   631  		},
   632  		{
   633  			name: "user_*_is_not_a_subset_of_user",
   634  			model: `model
   635  	schema 1.1
   636  type user
   637  
   638  type document
   639    relations
   640  	define viewer: [user]`,
   641  			target:   typesystem.DirectRelationReference("document", "viewer"),
   642  			source:   typesystem.WildcardRelationReference("user"),
   643  			expected: []*RelationshipEdge{},
   644  		},
   645  		{
   646  			name: "user_*_is_related_to_user_*",
   647  			model: `model
   648  	schema 1.1
   649  type user
   650  
   651  type document
   652    relations
   653  	define viewer: [user:*]`,
   654  			target: typesystem.DirectRelationReference("document", "viewer"),
   655  			source: typesystem.WildcardRelationReference("user"),
   656  			expected: []*RelationshipEdge{
   657  				{
   658  					Type:            DirectEdge,
   659  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
   660  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   661  				},
   662  			},
   663  		},
   664  		{
   665  			name: "edges_involving_wildcard_in_types",
   666  			model: `model
   667  	schema 1.1
   668  type user
   669  
   670  type document
   671    relations
   672  	define editor: [user:*]
   673  	define viewer: editor`,
   674  			target: typesystem.DirectRelationReference("document", "viewer"),
   675  			source: typesystem.DirectRelationReference("user", ""),
   676  			expected: []*RelationshipEdge{
   677  				{
   678  					Type:            DirectEdge,
   679  					TargetReference: typesystem.DirectRelationReference("document", "editor"),
   680  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   681  				},
   682  			},
   683  		},
   684  		{
   685  			name: "edges_involving_wildcard_in_source",
   686  			model: `model
   687  	schema 1.1
   688  type user
   689  
   690  type document
   691    relations
   692  	define editor: [user]
   693  	define viewer: editor`,
   694  			target:   typesystem.DirectRelationReference("document", "viewer"),
   695  			source:   typesystem.WildcardRelationReference("user"),
   696  			expected: []*RelationshipEdge{},
   697  		},
   698  		{
   699  			name: "edges_involving_wildcards_1",
   700  			model: `model
   701  	schema 1.1
   702  type user
   703  type employee
   704  type group
   705  
   706  type document
   707    relations
   708  	define relation1: [user:*] or relation2 or relation3 or relation4
   709  	define relation2: [group:*]
   710  	define relation3: [employee:*]
   711  	define relation4: [user]`,
   712  			target: typesystem.DirectRelationReference("document", "relation1"),
   713  			source: typesystem.DirectRelationReference("user", ""),
   714  			expected: []*RelationshipEdge{
   715  				{
   716  					Type:            DirectEdge,
   717  					TargetReference: typesystem.DirectRelationReference("document", "relation1"),
   718  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   719  				},
   720  				{
   721  					Type:            DirectEdge,
   722  					TargetReference: typesystem.DirectRelationReference("document", "relation4"),
   723  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   724  				},
   725  			},
   726  		},
   727  		{
   728  			name: "edges_involving_wildcards_2",
   729  			model: `model
   730  	schema 1.1
   731  type user
   732  
   733  type document
   734    relations
   735  	define relation1: [user] or relation2
   736  	define relation2: [user:*]`,
   737  			target: typesystem.DirectRelationReference("document", "relation1"),
   738  			source: typesystem.WildcardRelationReference("user"),
   739  			expected: []*RelationshipEdge{
   740  				{
   741  					Type:            DirectEdge,
   742  					TargetReference: typesystem.DirectRelationReference("document", "relation2"),
   743  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   744  				},
   745  			},
   746  		},
   747  		{
   748  			name: "indirect_typed_wildcard",
   749  			model: `model
   750  	schema 1.1
   751  type user
   752  
   753  type group
   754    relations
   755  	define member: [user:*]
   756  
   757  type document
   758    relations
   759  	define viewer: [group#member]`,
   760  			target: typesystem.DirectRelationReference("document", "viewer"),
   761  			source: typesystem.DirectRelationReference("user", ""),
   762  			expected: []*RelationshipEdge{
   763  				{
   764  					Type:            DirectEdge,
   765  					TargetReference: typesystem.DirectRelationReference("group", "member"),
   766  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   767  				},
   768  			},
   769  		},
   770  		{
   771  			name: "indirect_relationship_multiple_levels_deep",
   772  			model: `model
   773  	schema 1.1
   774  type user
   775  
   776  type team
   777    relations
   778  	define member: [user]
   779  
   780  type group
   781    relations
   782  	define member: [user, team#member]
   783  
   784  type document
   785    relations
   786  	define viewer: [user:*, group#member]`,
   787  			target: typesystem.DirectRelationReference("document", "viewer"),
   788  			source: typesystem.DirectRelationReference("user", ""),
   789  			expected: []*RelationshipEdge{
   790  				{
   791  					Type:            DirectEdge,
   792  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
   793  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   794  				},
   795  				{
   796  					Type:            DirectEdge,
   797  					TargetReference: typesystem.DirectRelationReference("group", "member"),
   798  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   799  				},
   800  				{
   801  					Type:            DirectEdge,
   802  					TargetReference: typesystem.DirectRelationReference("team", "member"),
   803  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   804  				},
   805  			},
   806  		},
   807  		{
   808  			name: "indirect_relationship_multiple_levels_deep_no_connectivity",
   809  			model: `model
   810  	schema 1.1
   811  type user
   812  type employee
   813  
   814  type team
   815    relations
   816  	define member: [employee]
   817  
   818  type group
   819    relations
   820  	define member: [team#member]
   821  
   822  type document
   823    relations
   824  	define viewer: [group#member]`,
   825  			target:   typesystem.DirectRelationReference("document", "viewer"),
   826  			source:   typesystem.DirectRelationReference("user", ""),
   827  			expected: []*RelationshipEdge{},
   828  		},
   829  		{
   830  			name: "edge_through_ttu_on_non-assignable_relation",
   831  			model: `model
   832  	schema 1.1
   833  type organization
   834    relations
   835  	define viewer: [organization]
   836  	define can_view: viewer
   837  
   838  type document
   839    relations
   840  	define parent: [organization]
   841  	define view: can_view from parent`,
   842  			target: typesystem.DirectRelationReference("document", "view"),
   843  			source: typesystem.DirectRelationReference("organization", "can_view"),
   844  			expected: []*RelationshipEdge{
   845  				{
   846  					Type:             TupleToUsersetEdge,
   847  					TargetReference:  typesystem.DirectRelationReference("document", "view"),
   848  					TuplesetRelation: "parent",
   849  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   850  				},
   851  			},
   852  		},
   853  		{
   854  			name: "indirect_relation_through_ttu_on_non-assignable_relation",
   855  			model: `model
   856  	schema 1.1
   857  type organization
   858    relations
   859  	define viewer: [organization]
   860  	define can_view: viewer
   861  
   862  type document
   863    relations
   864  	define parent: [organization]
   865  	define view: can_view from parent`,
   866  			target: typesystem.DirectRelationReference("document", "view"),
   867  			source: typesystem.DirectRelationReference("organization", "viewer"),
   868  			expected: []*RelationshipEdge{
   869  				{
   870  					Type:            ComputedUsersetEdge,
   871  					TargetReference: typesystem.DirectRelationReference("organization", "can_view"),
   872  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   873  				},
   874  			},
   875  		},
   876  		{
   877  			name: "ttu_on_non-assignable_relation",
   878  			model: `model
   879  	schema 1.1
   880  type organization
   881    relations
   882  	define viewer: [organization]
   883  	define can_view: viewer
   884  
   885  type document
   886    relations
   887  	define parent: [organization]
   888  	define view: can_view from parent`,
   889  			target: typesystem.DirectRelationReference("document", "view"),
   890  			source: typesystem.DirectRelationReference("organization", "can_view"),
   891  			expected: []*RelationshipEdge{
   892  				{
   893  					Type:             TupleToUsersetEdge,
   894  					TargetReference:  typesystem.DirectRelationReference("document", "view"),
   895  					TuplesetRelation: "parent",
   896  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   897  				},
   898  			},
   899  		},
   900  		{
   901  			name: "multiple_indirect_non-assignable_relations_through_ttu",
   902  			model: `model
   903  	schema 1.1
   904  type organization
   905    relations
   906  	define viewer: [organization]
   907  	define view: viewer
   908  
   909  type folder
   910    relations
   911  	define parent: [organization]
   912  	define view: view from parent
   913  
   914  type other
   915  
   916  type document
   917    relations
   918  	define parent: [folder, other]
   919  	define view: view from parent`,
   920  			target: typesystem.DirectRelationReference("document", "view"),
   921  			source: typesystem.DirectRelationReference("organization", ""),
   922  			expected: []*RelationshipEdge{
   923  				{
   924  					Type:            DirectEdge,
   925  					TargetReference: typesystem.DirectRelationReference("organization", "viewer"),
   926  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   927  				},
   928  			},
   929  		},
   930  		{
   931  			name: "multiple_directly_assignable_relationships_through_unions",
   932  			model: `model
   933  	schema 1.1
   934  type user
   935  
   936  type team
   937    relations
   938  	define admin: [user]
   939  	define member: [user, team#member] or admin
   940  
   941  type trial
   942    relations
   943  	define editor: [user, team#member] or owner
   944  	define owner: [user]
   945  	define viewer: [user, team#member] or editor`,
   946  			target: typesystem.DirectRelationReference("trial", "viewer"),
   947  			source: typesystem.DirectRelationReference("user", ""),
   948  			expected: []*RelationshipEdge{
   949  				{
   950  					Type:            DirectEdge,
   951  					TargetReference: typesystem.DirectRelationReference("trial", "viewer"),
   952  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   953  				},
   954  				{
   955  					Type:            DirectEdge,
   956  					TargetReference: typesystem.DirectRelationReference("trial", "editor"),
   957  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   958  				},
   959  				{
   960  					Type:            DirectEdge,
   961  					TargetReference: typesystem.DirectRelationReference("trial", "owner"),
   962  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   963  				},
   964  				{
   965  					Type:            DirectEdge,
   966  					TargetReference: typesystem.DirectRelationReference("team", "member"),
   967  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   968  				},
   969  				{
   970  					Type:            DirectEdge,
   971  					TargetReference: typesystem.DirectRelationReference("team", "admin"),
   972  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   973  				},
   974  			},
   975  		},
   976  		{
   977  			name: "multiple_assignable_and_non-assignable_computed_usersets",
   978  			model: `model
   979  	schema 1.1
   980  type user
   981  
   982  type team
   983    relations
   984  	define admin: [user]
   985  	define member: [user, team#member] or admin
   986  
   987  type trial
   988    relations
   989  	define editor: [user, team#member] or owner
   990  	define owner: [user]
   991  	define viewer: [user, team#member] or editor`,
   992  			target: typesystem.DirectRelationReference("trial", "viewer"),
   993  			source: typesystem.DirectRelationReference("team", "admin"),
   994  			expected: []*RelationshipEdge{
   995  				{
   996  					Type:            ComputedUsersetEdge,
   997  					TargetReference: typesystem.DirectRelationReference("team", "member"),
   998  					TargetReferenceInvolvesIntersectionOrExclusion: false,
   999  				},
  1000  			},
  1001  		},
  1002  		{
  1003  			name: "indirect_relationship_through_assignable_computed_userset",
  1004  			model: `model
  1005  	schema 1.1
  1006  type user
  1007  
  1008  type team
  1009    relations
  1010  	define admin: [user]
  1011  	define member: [team#member] or admin`,
  1012  			target: typesystem.DirectRelationReference("team", "member"),
  1013  			source: typesystem.DirectRelationReference("team", "admin"),
  1014  			expected: []*RelationshipEdge{
  1015  				{
  1016  					Type:            ComputedUsersetEdge,
  1017  					TargetReference: typesystem.DirectRelationReference("team", "member"),
  1018  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1019  				},
  1020  			},
  1021  		},
  1022  		{
  1023  			name: "indirect_relationship_through_non-assignable_computed_userset",
  1024  			model: `model
  1025  	schema 1.1
  1026  type user
  1027  
  1028  type group
  1029    relations
  1030  	define manager: [user]
  1031  	define member: manager
  1032  
  1033  type document
  1034    relations
  1035  	define viewer: [group#member]`,
  1036  			target: typesystem.DirectRelationReference("document", "viewer"),
  1037  			source: typesystem.DirectRelationReference("group", "manager"),
  1038  			expected: []*RelationshipEdge{
  1039  				{
  1040  					Type:            ComputedUsersetEdge,
  1041  					TargetReference: typesystem.DirectRelationReference("group", "member"),
  1042  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1043  				},
  1044  			},
  1045  		},
  1046  		{
  1047  			name: "indirect_relationship_through_non-assignable_ttu_1",
  1048  			model: `model
  1049  	schema 1.1
  1050  type user
  1051  
  1052  type org
  1053    relations
  1054  	define dept: [group]
  1055  	define dept_member: member from dept
  1056  
  1057  type group
  1058    relations
  1059  	define member: [user]
  1060  
  1061  type resource
  1062    relations
  1063  	define writer: [org#dept_member]`,
  1064  			target: typesystem.DirectRelationReference("resource", "writer"),
  1065  			source: typesystem.DirectRelationReference("user", ""),
  1066  			expected: []*RelationshipEdge{
  1067  				{
  1068  					Type:            DirectEdge,
  1069  					TargetReference: typesystem.DirectRelationReference("group", "member"),
  1070  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1071  				},
  1072  			},
  1073  		},
  1074  		{
  1075  			name: "indirect_relationship_through_non-assignable_ttu_2",
  1076  			model: `model
  1077  	schema 1.1
  1078  type user
  1079  
  1080  type org
  1081    relations
  1082  	define dept: [group]
  1083  	define dept_member: member from dept
  1084  
  1085  type group
  1086    relations
  1087  	define member: [user]
  1088  
  1089  type resource
  1090    relations
  1091  	define writer: [org#dept_member]`,
  1092  			target: typesystem.DirectRelationReference("resource", "writer"),
  1093  			source: typesystem.DirectRelationReference("group", "member"),
  1094  			expected: []*RelationshipEdge{
  1095  				{
  1096  					Type:             TupleToUsersetEdge,
  1097  					TargetReference:  typesystem.DirectRelationReference("org", "dept_member"),
  1098  					TuplesetRelation: "dept",
  1099  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1100  				},
  1101  			},
  1102  		},
  1103  		{
  1104  			name: "indirect_relationship_through_non-assignable_ttu_3",
  1105  			model: `model
  1106  	schema 1.1
  1107  type user
  1108  
  1109  type org
  1110    relations
  1111  	define dept: [group]
  1112  	define dept_member: member from dept
  1113  
  1114  type group
  1115    relations
  1116  	define member: [user]
  1117  
  1118  type resource
  1119    relations
  1120  	define writer: [org#dept_member]`,
  1121  			target: typesystem.DirectRelationReference("resource", "writer"),
  1122  			source: typesystem.DirectRelationReference("org", "dept_member"),
  1123  			expected: []*RelationshipEdge{
  1124  				{
  1125  					Type:            DirectEdge,
  1126  					TargetReference: typesystem.DirectRelationReference("resource", "writer"),
  1127  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1128  				},
  1129  			},
  1130  		},
  1131  		{
  1132  			name: "unrelated_source_and_target_relationship_involving_ttu",
  1133  			model: `model
  1134  	schema 1.1
  1135  type user
  1136  
  1137  type folder
  1138  	relations
  1139  		define viewer: [user]
  1140  
  1141  type document
  1142  	relations
  1143  		define can_read: viewer from parent
  1144  		define parent: [document,folder]
  1145  		define viewer: [user]`,
  1146  			target:   typesystem.DirectRelationReference("document", "can_read"),
  1147  			source:   typesystem.DirectRelationReference("document", ""),
  1148  			expected: []*RelationshipEdge{},
  1149  		},
  1150  		{
  1151  			name: "simple_computeduserset_indirect_ref",
  1152  			model: `model
  1153  	schema 1.1
  1154  type user
  1155  
  1156  type document
  1157    relations
  1158  	define parent: [document]
  1159  	define viewer: [user] or viewer from parent
  1160  	define can_view: viewer`,
  1161  			target: typesystem.DirectRelationReference("document", "can_view"),
  1162  			source: typesystem.DirectRelationReference("document", "viewer"),
  1163  			expected: []*RelationshipEdge{
  1164  				{
  1165  					Type:            ComputedUsersetEdge,
  1166  					TargetReference: typesystem.DirectRelationReference("document", "can_view"),
  1167  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1168  				},
  1169  				{
  1170  					Type:             TupleToUsersetEdge,
  1171  					TargetReference:  typesystem.DirectRelationReference("document", "viewer"),
  1172  					TuplesetRelation: "parent",
  1173  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1174  				},
  1175  			},
  1176  		},
  1177  		{
  1178  			name: "follow_computed_relation_of_ttu_to_computed_userset",
  1179  			model: `model
  1180  	schema 1.1
  1181  type user
  1182  type folder
  1183    relations
  1184  	define owner: [user]
  1185  	define viewer: [user] or owner
  1186  type document
  1187    relations
  1188  	define can_read: viewer from parent
  1189  	define parent: [document, folder]
  1190  	define viewer: [user]`,
  1191  			target: typesystem.DirectRelationReference("document", "can_read"),
  1192  			source: typesystem.DirectRelationReference("folder", "owner"),
  1193  			expected: []*RelationshipEdge{
  1194  				{
  1195  					Type:            ComputedUsersetEdge,
  1196  					TargetReference: typesystem.DirectRelationReference("folder", "viewer"),
  1197  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1198  				},
  1199  			},
  1200  		},
  1201  		{
  1202  			name: "computed_target_of_ttu_related_to_same_type",
  1203  			model: `model
  1204  	schema 1.1
  1205  type folder
  1206    relations
  1207  	define viewer: [folder]
  1208  
  1209  type document
  1210    relations
  1211  	define parent: [folder]
  1212  	define viewer: viewer from parent`,
  1213  			target: typesystem.DirectRelationReference("document", "viewer"),
  1214  			source: typesystem.DirectRelationReference("folder", "viewer"),
  1215  			expected: []*RelationshipEdge{
  1216  				{
  1217  					Type:             TupleToUsersetEdge,
  1218  					TargetReference:  typesystem.DirectRelationReference("document", "viewer"),
  1219  					TuplesetRelation: "parent",
  1220  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1221  				},
  1222  			},
  1223  		},
  1224  		{
  1225  			name: "basic_relation_with_intersection_1",
  1226  			model: `model
  1227  	schema 1.1
  1228  type user
  1229  
  1230  type document
  1231    relations
  1232  	define allowed: [user]
  1233  	define viewer: [user] and allowed`,
  1234  			target: typesystem.DirectRelationReference("document", "viewer"),
  1235  			source: typesystem.DirectRelationReference("user", ""),
  1236  			expected: []*RelationshipEdge{
  1237  				{
  1238  					Type:            DirectEdge,
  1239  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
  1240  					TargetReferenceInvolvesIntersectionOrExclusion: true,
  1241  				},
  1242  				{
  1243  					Type:            DirectEdge,
  1244  					TargetReference: typesystem.DirectRelationReference("document", "allowed"),
  1245  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1246  				},
  1247  			},
  1248  		},
  1249  		{
  1250  			name: "basic_relation_with_intersection_2",
  1251  			model: `model
  1252  	schema 1.1
  1253  type user
  1254  
  1255  type document
  1256    relations
  1257  	define allowed: [user]
  1258  	define editor: [user]
  1259  	define viewer: editor and allowed`,
  1260  			target: typesystem.DirectRelationReference("document", "viewer"),
  1261  			source: typesystem.DirectRelationReference("user", ""),
  1262  			expected: []*RelationshipEdge{
  1263  				{
  1264  					Type:            DirectEdge,
  1265  					TargetReference: typesystem.DirectRelationReference("document", "editor"),
  1266  					TargetReferenceInvolvesIntersectionOrExclusion: true,
  1267  				},
  1268  				{
  1269  					Type:            DirectEdge,
  1270  					TargetReference: typesystem.DirectRelationReference("document", "allowed"),
  1271  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1272  				},
  1273  			},
  1274  		},
  1275  		{
  1276  			name: "basic_relation_with_intersection_3",
  1277  			authModel: &openfgav1.AuthorizationModel{
  1278  				SchemaVersion: typesystem.SchemaVersion1_1,
  1279  				TypeDefinitions: []*openfgav1.TypeDefinition{
  1280  					{
  1281  						Type: "user",
  1282  					},
  1283  					{
  1284  						Type: "document",
  1285  						Relations: map[string]*openfgav1.Userset{
  1286  							"allowed": typesystem.This(),
  1287  							"editor":  typesystem.This(),
  1288  							"viewer": typesystem.Intersection(
  1289  								typesystem.ComputedUserset("allowed"),
  1290  								typesystem.This(),
  1291  							),
  1292  						},
  1293  						Metadata: &openfgav1.Metadata{
  1294  							Relations: map[string]*openfgav1.RelationMetadata{
  1295  								"allowed": {
  1296  									DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1297  										{Type: "user"},
  1298  									},
  1299  								},
  1300  								"editor": {
  1301  									DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1302  										{Type: "user"},
  1303  									},
  1304  								},
  1305  								"viewer": {
  1306  									DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1307  										{Type: "user"},
  1308  									},
  1309  								},
  1310  							},
  1311  						},
  1312  					},
  1313  				},
  1314  			},
  1315  			target: typesystem.DirectRelationReference("document", "viewer"),
  1316  			source: typesystem.DirectRelationReference("user", ""),
  1317  			expected: []*RelationshipEdge{
  1318  				{
  1319  					Type:            DirectEdge,
  1320  					TargetReference: typesystem.DirectRelationReference("document", "allowed"),
  1321  					TargetReferenceInvolvesIntersectionOrExclusion: true,
  1322  				},
  1323  				{
  1324  					Type:            DirectEdge,
  1325  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
  1326  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1327  				},
  1328  			},
  1329  		},
  1330  		{
  1331  			name: "basic_relation_with_exclusion_1",
  1332  			model: `model
  1333  	schema 1.1
  1334  type user
  1335  
  1336  type document
  1337    relations
  1338  	define restricted: [user]
  1339  	define viewer: [user] but not restricted`,
  1340  			target: typesystem.DirectRelationReference("document", "viewer"),
  1341  			source: typesystem.DirectRelationReference("user", ""),
  1342  			expected: []*RelationshipEdge{
  1343  				{
  1344  					Type:            DirectEdge,
  1345  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
  1346  					TargetReferenceInvolvesIntersectionOrExclusion: true,
  1347  				},
  1348  				{
  1349  					Type:            DirectEdge,
  1350  					TargetReference: typesystem.DirectRelationReference("document", "restricted"),
  1351  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1352  				},
  1353  			},
  1354  		},
  1355  		{
  1356  			name: "basic_relation_with_exclusion_2",
  1357  			model: `model
  1358  	schema 1.1
  1359  type user
  1360  
  1361  type document
  1362    relations
  1363  	define restricted: [user]
  1364  	define editor: [user]
  1365  	define viewer: editor but not restricted`,
  1366  			target: typesystem.DirectRelationReference("document", "viewer"),
  1367  			source: typesystem.DirectRelationReference("user", ""),
  1368  			expected: []*RelationshipEdge{
  1369  				{
  1370  					Type:            DirectEdge,
  1371  					TargetReference: typesystem.DirectRelationReference("document", "editor"),
  1372  					TargetReferenceInvolvesIntersectionOrExclusion: true,
  1373  				},
  1374  				{
  1375  					Type:            DirectEdge,
  1376  					TargetReference: typesystem.DirectRelationReference("document", "restricted"),
  1377  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1378  				},
  1379  			},
  1380  		},
  1381  		{
  1382  			name: "basic_relation_with_exclusion_3",
  1383  			authModel: &openfgav1.AuthorizationModel{
  1384  				SchemaVersion: typesystem.SchemaVersion1_1,
  1385  				TypeDefinitions: []*openfgav1.TypeDefinition{
  1386  					{
  1387  						Type: "user",
  1388  					},
  1389  					{
  1390  						Type: "document",
  1391  						Relations: map[string]*openfgav1.Userset{
  1392  							"allowed": typesystem.This(),
  1393  							"viewer": typesystem.Difference(
  1394  								typesystem.ComputedUserset("allowed"),
  1395  								typesystem.This()),
  1396  						},
  1397  						Metadata: &openfgav1.Metadata{
  1398  							Relations: map[string]*openfgav1.RelationMetadata{
  1399  								"allowed": {
  1400  									DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1401  										{Type: "user"},
  1402  									},
  1403  								},
  1404  								"viewer": {
  1405  									DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1406  										{Type: "user"},
  1407  									},
  1408  								},
  1409  							},
  1410  						},
  1411  					},
  1412  				},
  1413  			},
  1414  			target: typesystem.DirectRelationReference("document", "viewer"),
  1415  			source: typesystem.DirectRelationReference("user", ""),
  1416  			expected: []*RelationshipEdge{
  1417  				{
  1418  					Type:            DirectEdge,
  1419  					TargetReference: typesystem.DirectRelationReference("document", "allowed"),
  1420  					TargetReferenceInvolvesIntersectionOrExclusion: true,
  1421  				},
  1422  				{
  1423  					Type:            DirectEdge,
  1424  					TargetReference: typesystem.DirectRelationReference("document", "viewer"),
  1425  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1426  				},
  1427  			},
  1428  		},
  1429  		{
  1430  			name: "ttu_through_direct_rewrite_1",
  1431  			model: `model
  1432  	schema 1.1
  1433  type folder
  1434  	relations
  1435  	define viewer: [folder]
  1436  
  1437  type document
  1438  	relations
  1439  	define parent: [folder]
  1440  	define viewer: viewer from parent`,
  1441  			target: typesystem.DirectRelationReference("document", "viewer"),
  1442  			source: typesystem.DirectRelationReference("folder", "viewer"),
  1443  			expected: []*RelationshipEdge{
  1444  				{
  1445  					Type:             TupleToUsersetEdge,
  1446  					TargetReference:  typesystem.DirectRelationReference("document", "viewer"),
  1447  					TuplesetRelation: "parent",
  1448  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1449  				},
  1450  			},
  1451  		},
  1452  		{
  1453  			name: "ttu_through_direct_rewrite_2",
  1454  			model: `model
  1455  	schema 1.1
  1456  type folder
  1457  	relations
  1458  	define viewer: [folder]
  1459  
  1460  type document
  1461  	relations
  1462  	define parent: [folder]
  1463  	define viewer: viewer from parent`,
  1464  			target: typesystem.DirectRelationReference("document", "viewer"),
  1465  			source: typesystem.DirectRelationReference("folder", ""),
  1466  			expected: []*RelationshipEdge{
  1467  				{
  1468  					Type:            DirectEdge,
  1469  					TargetReference: typesystem.DirectRelationReference("folder", "viewer"),
  1470  					TargetReferenceInvolvesIntersectionOrExclusion: false,
  1471  				},
  1472  			},
  1473  		},
  1474  	}
  1475  
  1476  	for _, test := range tests {
  1477  		t.Run(test.name, func(t *testing.T) {
  1478  			var typesys *typesystem.TypeSystem
  1479  			if test.model == "" {
  1480  				typesys = typesystem.New(test.authModel)
  1481  			} else {
  1482  				model := testutils.MustTransformDSLToProtoWithID(test.model)
  1483  				typesys = typesystem.New(model)
  1484  			}
  1485  
  1486  			g := New(typesys)
  1487  
  1488  			edges, err := g.GetRelationshipEdges(test.target, test.source)
  1489  			require.NoError(t, err)
  1490  
  1491  			cmpOpts := []cmp.Option{
  1492  				cmpopts.IgnoreUnexported(openfgav1.RelationReference{}),
  1493  				RelationshipEdgeTransformer,
  1494  			}
  1495  			if diff := cmp.Diff(test.expected, edges, cmpOpts...); diff != "" {
  1496  				t.Errorf("mismatch (-want +got):\n%s", diff)
  1497  			}
  1498  		})
  1499  	}
  1500  }
  1501  
  1502  func TestResolutionDepthContext(t *testing.T) {
  1503  	ctx := ContextWithResolutionDepth(context.Background(), 2)
  1504  
  1505  	depth, ok := ResolutionDepthFromContext(ctx)
  1506  	require.True(t, ok)
  1507  	require.Equal(t, uint32(2), depth)
  1508  
  1509  	depth, ok = ResolutionDepthFromContext(context.Background())
  1510  	require.False(t, ok)
  1511  	require.Equal(t, uint32(0), depth)
  1512  }