github.com/openfga/openfga@v1.5.4-rc1/pkg/server/commands/listusers/list_users_rpc_test.go (about)

     1  package listusers
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/oklog/ulid/v2"
    10  	openfgav1 "github.com/openfga/api/proto/openfga/v1"
    11  	parser "github.com/openfga/language/pkg/go/transformer"
    12  	"github.com/stretchr/testify/require"
    13  	"go.uber.org/goleak"
    14  	"go.uber.org/mock/gomock"
    15  
    16  	"github.com/openfga/openfga/pkg/storage"
    17  
    18  	"github.com/openfga/openfga/internal/graph"
    19  	"github.com/openfga/openfga/internal/mocks"
    20  
    21  	"github.com/openfga/openfga/pkg/storage/memory"
    22  	"github.com/openfga/openfga/pkg/storage/storagewrappers"
    23  	"github.com/openfga/openfga/pkg/testutils"
    24  	"github.com/openfga/openfga/pkg/tuple"
    25  	"github.com/openfga/openfga/pkg/typesystem"
    26  )
    27  
    28  type ListUsersTests []struct {
    29  	name             string
    30  	req              *openfgav1.ListUsersRequest
    31  	model            string
    32  	tuples           []*openfgav1.TupleKey
    33  	expectedUsers    []string
    34  	expectedErrorMsg string
    35  }
    36  
    37  const maximumRecursiveDepth = 25
    38  
    39  func TestListUsersDirectRelationship(t *testing.T) {
    40  	t.Cleanup(func() {
    41  		goleak.VerifyNone(t)
    42  	})
    43  	model := `model
    44  	schema 1.1
    45  	type user
    46  	type document
    47  		relations
    48  			define viewer: [user]`
    49  
    50  	tests := ListUsersTests{
    51  		{
    52  			name: "direct_relationship",
    53  			req: &openfgav1.ListUsersRequest{
    54  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
    55  				Relation: "viewer",
    56  				UserFilters: []*openfgav1.UserTypeFilter{
    57  					{
    58  						Type: "user",
    59  					},
    60  				},
    61  			},
    62  			model: model,
    63  			tuples: []*openfgav1.TupleKey{
    64  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
    65  				tuple.NewTupleKey("document:1", "viewer", "user:maria"),
    66  				tuple.NewTupleKey("document:2", "viewer", "user:jon"),
    67  			},
    68  			expectedUsers: []string{"user:will", "user:maria"},
    69  		},
    70  		{
    71  			name: "direct_relationship_with_userset_subjects_and_userset_filter",
    72  			req: &openfgav1.ListUsersRequest{
    73  				Object:   &openfgav1.Object{Type: "group", Id: "eng"},
    74  				Relation: "member",
    75  				UserFilters: []*openfgav1.UserTypeFilter{
    76  					{
    77  						Type:     "group",
    78  						Relation: "member",
    79  					},
    80  				},
    81  			},
    82  			model: `model
    83  			schema 1.1
    84  			type user
    85  			type group
    86  				relations
    87  					define member: [user, group#member]`,
    88  			tuples: []*openfgav1.TupleKey{
    89  				tuple.NewTupleKey("group:eng", "member", "group:fga#member"),
    90  				tuple.NewTupleKey("group:fga", "member", "group:fga-backend#member"),
    91  			},
    92  			expectedUsers: []string{"group:fga#member", "group:fga-backend#member", "group:eng#member"},
    93  		},
    94  		{
    95  			name: "direct_relationship_no_tuples",
    96  			req: &openfgav1.ListUsersRequest{
    97  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
    98  				Relation: "viewer",
    99  				UserFilters: []*openfgav1.UserTypeFilter{
   100  					{
   101  						Type: "user",
   102  					},
   103  				},
   104  			},
   105  			model:         model,
   106  			tuples:        []*openfgav1.TupleKey{},
   107  			expectedUsers: []string{},
   108  		},
   109  		{
   110  			name: "direct_relationship_unapplicable_tuples",
   111  			req: &openfgav1.ListUsersRequest{
   112  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   113  				Relation: "viewer",
   114  				UserFilters: []*openfgav1.UserTypeFilter{
   115  					{
   116  						Type: "user",
   117  					},
   118  				},
   119  			},
   120  			model: model,
   121  			tuples: []*openfgav1.TupleKey{
   122  				tuple.NewTupleKey("document:2", "viewer", "user:will"),
   123  				tuple.NewTupleKey("document:3", "viewer", "user:will"),
   124  				tuple.NewTupleKey("document:4", "viewer", "user:will"),
   125  			},
   126  			expectedUsers: []string{},
   127  		},
   128  		{
   129  			name: "direct_relationship_contextual_tuples",
   130  			req: &openfgav1.ListUsersRequest{
   131  				Object: &openfgav1.Object{Type: "document", Id: "1"},
   132  				ContextualTuples: []*openfgav1.TupleKey{
   133  					tuple.NewTupleKey("document:1", "viewer", "user:will"),
   134  					tuple.NewTupleKey("document:1", "viewer", "user:maria"),
   135  					tuple.NewTupleKey("document:2", "viewer", "user:jon"),
   136  				},
   137  				Relation: "viewer",
   138  				UserFilters: []*openfgav1.UserTypeFilter{
   139  					{
   140  						Type: "user",
   141  					},
   142  				},
   143  			},
   144  			model:         model,
   145  			tuples:        []*openfgav1.TupleKey{},
   146  			expectedUsers: []string{"user:will", "user:maria"},
   147  		},
   148  	}
   149  	tests.runListUsersTestCases(t)
   150  }
   151  
   152  func TestListUsersComputedRelationship(t *testing.T) {
   153  	t.Cleanup(func() {
   154  		goleak.VerifyNone(t)
   155  	})
   156  	tests := ListUsersTests{
   157  		{
   158  			name: "computed_relationship",
   159  			req: &openfgav1.ListUsersRequest{
   160  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   161  				Relation: "viewer",
   162  				UserFilters: []*openfgav1.UserTypeFilter{
   163  					{
   164  						Type: "user",
   165  					},
   166  				},
   167  			},
   168  			model: `model
   169  			schema 1.1
   170  			type user
   171  			type document
   172  				relations
   173  					define owner: [user]
   174  					define viewer: owner`,
   175  			tuples: []*openfgav1.TupleKey{
   176  				tuple.NewTupleKey("document:1", "owner", "user:will"),
   177  				tuple.NewTupleKey("document:1", "owner", "user:maria"),
   178  				tuple.NewTupleKey("document:2", "viewer", "user:jon"),
   179  			},
   180  			expectedUsers: []string{"user:will", "user:maria"},
   181  		},
   182  		{
   183  			name: "computed_relationship_with_possible_direct_relationship",
   184  			req: &openfgav1.ListUsersRequest{
   185  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   186  				Relation: "viewer",
   187  				UserFilters: []*openfgav1.UserTypeFilter{
   188  					{
   189  						Type: "user",
   190  					},
   191  				},
   192  			},
   193  			model: `model
   194  			schema 1.1
   195  			type user
   196  			type document
   197  				relations
   198  					define owner: [user]
   199  					define editor: [user] or owner
   200  					define viewer: owner or editor`,
   201  			tuples: []*openfgav1.TupleKey{
   202  				tuple.NewTupleKey("document:1", "owner", "user:will"),
   203  				tuple.NewTupleKey("document:1", "editor", "user:maria"),
   204  				tuple.NewTupleKey("document:2", "viewer", "user:jon"),
   205  			},
   206  			expectedUsers: []string{"user:will", "user:maria"},
   207  		},
   208  		{
   209  			name: "computed_relationship_with_contextual_tuples",
   210  			req: &openfgav1.ListUsersRequest{
   211  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   212  				Relation: "viewer",
   213  				UserFilters: []*openfgav1.UserTypeFilter{
   214  					{
   215  						Type: "user",
   216  					},
   217  				},
   218  				ContextualTuples: []*openfgav1.TupleKey{
   219  					tuple.NewTupleKey("document:1", "owner", "user:will"),
   220  					tuple.NewTupleKey("document:1", "owner", "user:maria"),
   221  					tuple.NewTupleKey("document:2", "viewer", "user:jon"),
   222  				},
   223  			},
   224  			model: `model
   225  			schema 1.1
   226  			type user
   227  			type document
   228  				relations
   229  					define owner: [user]
   230  					define viewer: owner`,
   231  			tuples:        []*openfgav1.TupleKey{},
   232  			expectedUsers: []string{"user:will", "user:maria"},
   233  		},
   234  	}
   235  	tests.runListUsersTestCases(t)
   236  }
   237  
   238  func TestListUsersUsersets(t *testing.T) {
   239  	t.Cleanup(func() {
   240  		goleak.VerifyNone(t)
   241  	})
   242  	model := `model
   243  	schema 1.1
   244  	type user
   245  	type group
   246  		relations
   247  			define member: [user]
   248  	type document
   249  		relations
   250  			define viewer: [group#member]`
   251  
   252  	tests := ListUsersTests{
   253  		{
   254  			name: "userset_user_granularity",
   255  			req: &openfgav1.ListUsersRequest{
   256  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   257  				Relation: "viewer",
   258  				UserFilters: []*openfgav1.UserTypeFilter{
   259  					{
   260  						Type: "user",
   261  					},
   262  				},
   263  			},
   264  			model: model,
   265  			tuples: []*openfgav1.TupleKey{
   266  				tuple.NewTupleKey("group:eng", "member", "user:will"),
   267  				tuple.NewTupleKey("group:eng", "member", "user:maria"),
   268  				tuple.NewTupleKey("group:marketing", "viewer", "user:jon"),
   269  				tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   270  			},
   271  			expectedUsers: []string{"user:will", "user:maria"},
   272  		},
   273  		{
   274  			name: "userset_group_granularity",
   275  			req: &openfgav1.ListUsersRequest{
   276  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   277  				Relation: "viewer",
   278  				UserFilters: []*openfgav1.UserTypeFilter{
   279  					{
   280  						Type:     "group",
   281  						Relation: "member",
   282  					},
   283  				},
   284  			},
   285  			model: model,
   286  			tuples: []*openfgav1.TupleKey{
   287  				tuple.NewTupleKey("group:eng", "member", "user:will"),
   288  				tuple.NewTupleKey("group:eng", "member", "user:maria"),
   289  				tuple.NewTupleKey("group:marketing", "viewer", "user:jon"),
   290  				tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   291  			},
   292  			expectedUsers: []string{"group:eng#member"},
   293  		},
   294  		{
   295  			name: "userset_group_granularity_with_incorrect_user_filter",
   296  			req: &openfgav1.ListUsersRequest{
   297  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   298  				Relation: "viewer",
   299  				UserFilters: []*openfgav1.UserTypeFilter{
   300  					{
   301  						Type:     "group",
   302  						Relation: "", // Would return results if "member"
   303  					},
   304  				},
   305  			},
   306  			model: model,
   307  			tuples: []*openfgav1.TupleKey{
   308  				tuple.NewTupleKey("group:eng", "member", "user:will"),
   309  				tuple.NewTupleKey("group:eng", "member", "user:maria"),
   310  				tuple.NewTupleKey("group:marketing", "member", "user:jon"),
   311  				tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   312  			},
   313  			expectedUsers: []string{},
   314  		},
   315  		{
   316  			name: "userset_group_granularity_with_direct_user_relationships",
   317  			req: &openfgav1.ListUsersRequest{
   318  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   319  				Relation: "viewer",
   320  				UserFilters: []*openfgav1.UserTypeFilter{
   321  					{
   322  						Type:     "group",
   323  						Relation: "member",
   324  					},
   325  				},
   326  			},
   327  			model: `model
   328  			schema 1.1
   329  			type user
   330  			type group
   331  				relations
   332  					define member: [user]
   333  			type document
   334  				relations
   335  					define viewer: [ user, group#member ]`,
   336  			tuples: []*openfgav1.TupleKey{
   337  				tuple.NewTupleKey("group:eng", "member", "user:will"),
   338  				tuple.NewTupleKey("group:eng", "member", "user:maria"),
   339  				tuple.NewTupleKey("group:marketing", "member", "user:jon"),
   340  				tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   341  
   342  				tuple.NewTupleKey("document:1", "viewer", "user:poovam"),
   343  			},
   344  			expectedUsers: []string{"group:eng#member"},
   345  		},
   346  		{
   347  			name: "userset_multiple_usersets",
   348  			req: &openfgav1.ListUsersRequest{
   349  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   350  				Relation: "viewer",
   351  				UserFilters: []*openfgav1.UserTypeFilter{
   352  					{
   353  						Type: "user",
   354  					},
   355  				},
   356  			},
   357  			model: `model
   358              schema 1.1
   359  			type user
   360  			type group
   361  			  relations
   362  			    define member: [user, group#member]
   363  			type document
   364  			  relations
   365  			    define viewer: [group#member]`,
   366  			tuples: []*openfgav1.TupleKey{
   367  				tuple.NewTupleKey("group:eng", "member", "user:hawker"),
   368  				tuple.NewTupleKey("group:fga", "member", "user:jon"),
   369  				tuple.NewTupleKey("group:eng", "member", "group:fga#member"),
   370  				tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   371  				tuple.NewTupleKey("document:1", "viewer", "group:other#member"),
   372  				tuple.NewTupleKey("group:other", "member", "user:will"),
   373  			},
   374  			expectedUsers: []string{"user:jon", "user:hawker", "user:will"},
   375  		},
   376  		{
   377  			name: "userset_multiple_usersets_group_granularity",
   378  			req: &openfgav1.ListUsersRequest{
   379  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   380  				Relation: "viewer",
   381  				UserFilters: []*openfgav1.UserTypeFilter{
   382  					{
   383  						Type:     "group",
   384  						Relation: "member",
   385  					},
   386  				},
   387  			},
   388  			model: `model
   389              schema 1.1
   390  			type user
   391  			type group
   392  			  relations
   393  			    define member: [user, group#member]
   394  			type document
   395  			  relations
   396  			    define viewer: [group#member]`,
   397  			tuples: []*openfgav1.TupleKey{
   398  				tuple.NewTupleKey("group:eng", "member", "user:hawker"),
   399  				tuple.NewTupleKey("group:eng", "member", "group:fga#member"),
   400  				tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   401  				tuple.NewTupleKey("document:1", "viewer", "group:other#member"),
   402  			},
   403  			expectedUsers: []string{"group:fga#member", "group:eng#member", "group:other#member"},
   404  		},
   405  		{
   406  			name: "userset_user_granularity_with_contextual_tuples",
   407  			req: &openfgav1.ListUsersRequest{
   408  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   409  				Relation: "viewer",
   410  				UserFilters: []*openfgav1.UserTypeFilter{
   411  					{
   412  						Type: "user",
   413  					},
   414  				},
   415  				ContextualTuples: []*openfgav1.TupleKey{
   416  					tuple.NewTupleKey("group:marketing", "member", "user:jon"),
   417  					tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   418  				},
   419  			},
   420  			model: model,
   421  			tuples: []*openfgav1.TupleKey{
   422  				tuple.NewTupleKey("group:eng", "member", "user:will"),
   423  				tuple.NewTupleKey("group:eng", "member", "user:maria"),
   424  			},
   425  			expectedUsers: []string{"user:will", "user:maria"},
   426  		},
   427  		{
   428  			name: "userset_user_assigned_multiple_groups",
   429  			req: &openfgav1.ListUsersRequest{
   430  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   431  				Relation: "viewer",
   432  				UserFilters: []*openfgav1.UserTypeFilter{
   433  					{
   434  						Type: "user",
   435  					},
   436  				},
   437  				ContextualTuples: []*openfgav1.TupleKey{},
   438  			},
   439  			model: model,
   440  			tuples: []*openfgav1.TupleKey{
   441  				tuple.NewTupleKey("group:eng", "member", "user:maria"),
   442  				tuple.NewTupleKey("group:eng", "member", "user:will"),
   443  				tuple.NewTupleKey("group:fga", "member", "user:will"),
   444  				tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   445  				tuple.NewTupleKey("document:1", "viewer", "group:fga#member"),
   446  			},
   447  			expectedUsers: []string{"user:will", "user:maria"},
   448  		},
   449  		{
   450  			name: "tuple_defines_itself",
   451  			req: &openfgav1.ListUsersRequest{
   452  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   453  				Relation: "viewer",
   454  				UserFilters: []*openfgav1.UserTypeFilter{
   455  					{
   456  						Type: "document",
   457  					},
   458  				},
   459  			},
   460  			model: `model
   461              schema 1.1
   462  			type user
   463  			type document
   464  			  relations
   465  			    define viewer: [user]
   466  			`,
   467  			tuples:        []*openfgav1.TupleKey{},
   468  			expectedUsers: []string{},
   469  		},
   470  		{
   471  			name: "userset_defines_itself",
   472  			req: &openfgav1.ListUsersRequest{
   473  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   474  				Relation: "viewer",
   475  				UserFilters: []*openfgav1.UserTypeFilter{
   476  					{
   477  						Type:     "document",
   478  						Relation: "viewer",
   479  					},
   480  				},
   481  			},
   482  			model: `model
   483              schema 1.1
   484  			type user
   485  			type document
   486  			  relations
   487  			    define viewer: [user]
   488  			`,
   489  			tuples:        []*openfgav1.TupleKey{},
   490  			expectedUsers: []string{"document:1#viewer"},
   491  		},
   492  		{
   493  			name: "evaluate_userset_in_computed_relation_of_ttu",
   494  			req: &openfgav1.ListUsersRequest{
   495  				Object:   &openfgav1.Object{Type: "repo", Id: "fga"},
   496  				Relation: "reader",
   497  				UserFilters: []*openfgav1.UserTypeFilter{
   498  					{
   499  						Type:     "org",
   500  						Relation: "member",
   501  					},
   502  				},
   503  			},
   504  			model: `model
   505  			schema 1.1
   506  		  type user
   507  		  
   508  		  type org
   509  			relations
   510  			  define member: [user]
   511  			  define admin: [org#member]
   512  		  
   513  		  type repo
   514  			relations
   515  			  define owner: [org]
   516  			  define reader: admin from owner`,
   517  			tuples: []*openfgav1.TupleKey{
   518  				tuple.NewTupleKey("repo:fga", "owner", "org:x"),
   519  				tuple.NewTupleKey("org:x", "admin", "org:x#member"),
   520  				tuple.NewTupleKey("org:x", "member", "user:will"),
   521  			},
   522  			expectedUsers: []string{"org:x#member"},
   523  		},
   524  		{
   525  			name: "userset_with_intersection_in_computed_relation_of_ttu",
   526  			req: &openfgav1.ListUsersRequest{
   527  				Object:   &openfgav1.Object{Type: "repo", Id: "fga"},
   528  				Relation: "reader",
   529  				UserFilters: []*openfgav1.UserTypeFilter{
   530  					{
   531  						Type:     "org",
   532  						Relation: "member",
   533  					},
   534  				},
   535  			},
   536  			model: `model
   537              schema 1.1
   538            type user
   539            
   540            type org
   541              relations
   542                define member: [user]
   543                define admin: [org#member]
   544  		  type repo
   545  			relations
   546  			  define owner: [org]
   547  			  define allowed: [user]
   548  			  define reader: admin from owner and allowed`,
   549  			tuples: []*openfgav1.TupleKey{
   550  				tuple.NewTupleKey("repo:x", "owner", "org:fga"),
   551  				tuple.NewTupleKey("org:fga", "admin", "org:fga#member"),
   552  				tuple.NewTupleKey("repo:x", "allowed", "user:will"),
   553  			},
   554  			expectedUsers: []string{},
   555  		},
   556  	}
   557  	tests.runListUsersTestCases(t)
   558  }
   559  
   560  func TestListUsersTTU(t *testing.T) {
   561  	t.Cleanup(func() {
   562  		goleak.VerifyNone(t)
   563  	})
   564  	model := `model
   565  	schema 1.1
   566    type user
   567  
   568    type folder
   569  	relations
   570  	  define viewer: [user]
   571  
   572    type document
   573  	relations
   574  	  define parent: [folder]
   575  	  define viewer: viewer from parent`
   576  
   577  	tests := ListUsersTests{
   578  		{
   579  			name: "ttu_user_granularity",
   580  			req: &openfgav1.ListUsersRequest{
   581  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   582  				Relation: "viewer",
   583  				UserFilters: []*openfgav1.UserTypeFilter{
   584  					{
   585  						Type: "user",
   586  					},
   587  				},
   588  			},
   589  			model: model,
   590  			tuples: []*openfgav1.TupleKey{
   591  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
   592  				tuple.NewTupleKey("folder:x", "viewer", "user:maria"),
   593  				tuple.NewTupleKey("folder:no-doc", "viewer", "user:maria"),
   594  				tuple.NewTupleKey("document:1", "parent", "folder:no-user"),
   595  			},
   596  			expectedUsers: []string{"user:maria"},
   597  		},
   598  		{
   599  			name: "ttu_folder_granularity",
   600  			req: &openfgav1.ListUsersRequest{
   601  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   602  				Relation: "viewer",
   603  				UserFilters: []*openfgav1.UserTypeFilter{
   604  					{
   605  						Type: "folder",
   606  					},
   607  				},
   608  			},
   609  			model: model,
   610  			tuples: []*openfgav1.TupleKey{
   611  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
   612  				tuple.NewTupleKey("folder:x", "viewer", "user:maria"),
   613  			},
   614  			expectedUsers: []string{},
   615  		},
   616  		{
   617  			name: "ttu_with_computed_relation_user_granularity",
   618  			req: &openfgav1.ListUsersRequest{
   619  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   620  				Relation: "viewer",
   621  				UserFilters: []*openfgav1.UserTypeFilter{
   622  					{
   623  						Type: "user",
   624  					},
   625  				},
   626  			},
   627  			model: `model
   628  			schema 1.1
   629  		  type user
   630  		
   631  		  type folder
   632  			relations
   633  				define owner: [user]
   634  				define editor: [user] or owner
   635  				define viewer: [user] or owner or editor
   636  				define unrelated_not_computed: [user]
   637  		
   638  		  type document
   639  			relations
   640  			  define parent: [folder]
   641  			  define viewer: viewer from parent`,
   642  
   643  			tuples: []*openfgav1.TupleKey{
   644  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
   645  				tuple.NewTupleKey("folder:x", "viewer", "user:maria"),
   646  				tuple.NewTupleKey("folder:x", "editor", "user:will"),
   647  				tuple.NewTupleKey("folder:x", "owner", "user:jon"),
   648  				tuple.NewTupleKey("folder:x", "unrelated_not_computed", "user:poovam"),
   649  
   650  				tuple.NewTupleKey("folder:no-doc", "viewer", "user:maria"),
   651  				tuple.NewTupleKey("document:1", "parent", "folder:no-user"),
   652  			},
   653  			expectedUsers: []string{"user:maria", "user:will", "user:jon"},
   654  		},
   655  		{
   656  			name: "ttu_multiple_levels",
   657  			req: &openfgav1.ListUsersRequest{
   658  				Object:   &openfgav1.Object{Type: "folder", Id: "c"},
   659  				Relation: "viewer",
   660  				UserFilters: []*openfgav1.UserTypeFilter{
   661  					{
   662  						Type: "user",
   663  					},
   664  				},
   665  			},
   666  			model: `model
   667  			schema 1.1
   668  			type user 
   669  			type folder 
   670  				relations
   671  					define parent: [folder]
   672  					define viewer: [user] or viewer from parent`,
   673  			tuples: []*openfgav1.TupleKey{
   674  				tuple.NewTupleKey("folder:a", "viewer", "user:will"),
   675  				tuple.NewTupleKey("folder:b", "parent", "folder:a"),
   676  				tuple.NewTupleKey("folder:c", "parent", "folder:b"),
   677  
   678  				tuple.NewTupleKey("folder:c", "parent", "folder:other"),
   679  				tuple.NewTupleKey("folder:other", "viewer", "user:jon"),
   680  			},
   681  			expectedUsers: []string{"user:will", "user:jon"},
   682  		},
   683  	}
   684  
   685  	tests.runListUsersTestCases(t)
   686  }
   687  
   688  func TestListUsersCycles(t *testing.T) {
   689  	t.Cleanup(func() {
   690  		goleak.VerifyNone(t)
   691  	})
   692  	tests := ListUsersTests{
   693  		{
   694  			name: "cycle_materialized_by_tuples",
   695  			req: &openfgav1.ListUsersRequest{
   696  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   697  				Relation: "viewer",
   698  				UserFilters: []*openfgav1.UserTypeFilter{
   699  					{
   700  						Type: "user",
   701  					},
   702  				},
   703  			},
   704  			model: `model
   705  			schema 1.1
   706  			type user
   707  			type document
   708  				relations
   709  					define viewer: [user, document#viewer]`,
   710  			tuples: []*openfgav1.TupleKey{
   711  				tuple.NewTupleKey("document:1", "viewer", "document:1#viewer"),
   712  			},
   713  			expectedUsers: []string{},
   714  		},
   715  		{
   716  			name: "cycle_and_union",
   717  			req: &openfgav1.ListUsersRequest{
   718  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   719  				Relation: "can_view",
   720  				UserFilters: []*openfgav1.UserTypeFilter{
   721  					{
   722  						Type: "user",
   723  					},
   724  				},
   725  			},
   726  			model: `model
   727  			schema 1.1
   728  			type user
   729  			type document
   730  				relations
   731  					define viewer1: [user, document#viewer1]
   732  					define viewer2: [user, document#viewer2]
   733  					define can_view: viewer1 or viewer2`,
   734  			tuples: []*openfgav1.TupleKey{
   735  				tuple.NewTupleKey("document:1", "viewer1", "document:1#viewer1"),
   736  				tuple.NewTupleKey("document:1", "viewer2", "document:1#viewer2"),
   737  			},
   738  			expectedUsers: []string{},
   739  		},
   740  		{
   741  			name: "cycle_and_intersection",
   742  			req: &openfgav1.ListUsersRequest{
   743  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   744  				Relation: "can_view",
   745  				UserFilters: []*openfgav1.UserTypeFilter{
   746  					{
   747  						Type: "user",
   748  					},
   749  				},
   750  			},
   751  			model: `model
   752  			schema 1.1
   753  			type user
   754  			type document
   755  				relations
   756  					define viewer1: [user, document#viewer1]
   757  					define viewer2: [user, document#viewer2]
   758  					define can_view: viewer1 and viewer2`,
   759  			tuples: []*openfgav1.TupleKey{
   760  				tuple.NewTupleKey("document:1", "viewer1", "document:1#viewer1"),
   761  				tuple.NewTupleKey("document:1", "viewer2", "document:1#viewer2"),
   762  			},
   763  			expectedUsers: []string{},
   764  		},
   765  		{
   766  			name: "cycle_when_model_has_two_parallel_edges",
   767  			req: &openfgav1.ListUsersRequest{
   768  				Object:   &openfgav1.Object{Type: "transition", Id: "1"},
   769  				Relation: "can_view_3",
   770  				UserFilters: []*openfgav1.UserTypeFilter{
   771  					{
   772  						Type: "user",
   773  					},
   774  				},
   775  			},
   776  			model: `
   777  			model
   778  				schema 1.1
   779  
   780  			type user
   781  
   782  			type state
   783  				relations
   784  					define can_view: [user] or can_view_3 from associated_transition
   785  					define associated_transition: [transition]
   786  
   787  			type transition
   788  				relations
   789  					define start: [state]
   790  					define end: [state]
   791  					define can_view: can_view from start or can_view from end
   792  					define can_view_2: can_view
   793  					define can_view_3: can_view_2`,
   794  			tuples:        []*openfgav1.TupleKey{},
   795  			expectedUsers: []string{},
   796  		},
   797  	}
   798  	tests.runListUsersTestCases(t)
   799  }
   800  
   801  func TestListUsersConditions(t *testing.T) {
   802  	t.Cleanup(func() {
   803  		goleak.VerifyNone(t)
   804  	})
   805  	model := `
   806  		model
   807  			schema 1.1
   808  		type user
   809  
   810  		type document
   811  			relations
   812  				define viewer: [user with isTrue]
   813  
   814  		condition isTrue(param: bool) {
   815  			param
   816  		}`
   817  
   818  	tests := ListUsersTests{
   819  		{
   820  			name: "conditions_with_true_evaluation",
   821  			req: &openfgav1.ListUsersRequest{
   822  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   823  				Relation: "viewer",
   824  				UserFilters: []*openfgav1.UserTypeFilter{
   825  					{
   826  						Type: "user",
   827  					},
   828  				},
   829  				Context: testutils.MustNewStruct(t, map[string]interface{}{"param": true}),
   830  			},
   831  			model: model,
   832  			tuples: []*openfgav1.TupleKey{
   833  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
   834  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:jon", "isTrue", nil),
   835  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isTrue", nil),
   836  			},
   837  			expectedUsers: []string{"user:jon", "user:maria"},
   838  		},
   839  		{
   840  			name: "conditions_with_false_evaluation",
   841  			req: &openfgav1.ListUsersRequest{
   842  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   843  				Relation: "viewer",
   844  				UserFilters: []*openfgav1.UserTypeFilter{
   845  					{
   846  						Type: "user",
   847  					},
   848  				},
   849  				Context: testutils.MustNewStruct(t, map[string]interface{}{"param": false}),
   850  			},
   851  			model: model,
   852  			tuples: []*openfgav1.TupleKey{
   853  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
   854  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:jon", "isTrue", nil),
   855  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isTrue", nil),
   856  			},
   857  			expectedUsers: []string{},
   858  		},
   859  		{
   860  			name: "conditions_with_usersets",
   861  			req: &openfgav1.ListUsersRequest{
   862  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   863  				Relation: "viewer",
   864  				UserFilters: []*openfgav1.UserTypeFilter{
   865  					{
   866  						Type:     "group",
   867  						Relation: "member",
   868  					},
   869  				},
   870  				Context: testutils.MustNewStruct(t, map[string]interface{}{"param": true}),
   871  			},
   872  			model: `
   873  			model
   874  				schema 1.1
   875  
   876  			type user
   877  
   878  			type group
   879  				relations
   880  					define member: [user]
   881  
   882  			type document
   883  				relations
   884  					define viewer: [group#member with isTrue, user]
   885  
   886  			condition isTrue(param: bool) {
   887  				param
   888  			}`,
   889  			tuples: []*openfgav1.TupleKey{
   890  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "group:eng#member", "isTrue", nil),
   891  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "group:fga#member", "isTrue", nil),
   892  				tuple.NewTupleKey("group:eng", "member", "user:jon"),
   893  				tuple.NewTupleKey("group:eng", "member", "user:maria"),
   894  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
   895  			},
   896  			expectedUsers: []string{"group:eng#member", "group:fga#member"},
   897  		},
   898  		{
   899  			name: "conditions_with_computed_relationships",
   900  			req: &openfgav1.ListUsersRequest{
   901  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   902  				Relation: "viewer",
   903  				UserFilters: []*openfgav1.UserTypeFilter{
   904  					{
   905  						Type: "user",
   906  					},
   907  				},
   908  				Context: testutils.MustNewStruct(t, map[string]interface{}{"param": true}),
   909  			},
   910  			model: `
   911  			model
   912  				schema 1.1
   913  
   914  			type user
   915  
   916  			type group
   917  				relations
   918  					define member: [user]
   919  
   920  			type document
   921  				relations
   922  					define owner: [user]
   923  					define editor: [user] or owner
   924  					define viewer: [user with isTrue] or editor or owner
   925  
   926  			condition isTrue(param: bool) {
   927  				param
   928  			}`,
   929  			tuples: []*openfgav1.TupleKey{
   930  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isTrue", nil),
   931  				tuple.NewTupleKey("document:1", "owner", "user:will"),
   932  				tuple.NewTupleKey("document:1", "editor", "user:poovam"),
   933  			},
   934  			expectedUsers: []string{"user:will", "user:poovam", "user:maria"},
   935  		},
   936  		{
   937  			name: "conditions_with_ttu",
   938  			req: &openfgav1.ListUsersRequest{
   939  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   940  				Relation: "viewer",
   941  				UserFilters: []*openfgav1.UserTypeFilter{
   942  					{
   943  						Type: "user",
   944  					},
   945  				},
   946  				Context: testutils.MustNewStruct(t, map[string]interface{}{"param": true}),
   947  			},
   948  			model: `
   949  			model
   950  				schema 1.1
   951  			type user
   952  
   953  			type folder
   954  				relations
   955  					define viewer: [user]
   956  
   957  			type document
   958  				relations
   959  				define parent: [folder with isTrue]
   960  				define viewer: viewer from parent
   961  
   962  			condition isTrue(param: bool) {
   963  				param
   964  			}`,
   965  			tuples: []*openfgav1.TupleKey{
   966  				tuple.NewTupleKeyWithCondition("document:1", "parent", "folder:x", "isTrue", nil),
   967  				tuple.NewTupleKey("folder:x", "viewer", "user:jon"),
   968  				tuple.NewTupleKeyWithCondition("document:1", "parent", "folder:y", "isTrue", nil),
   969  				tuple.NewTupleKey("folder:y", "viewer", "user:maria"),
   970  			},
   971  			expectedUsers: []string{"user:jon", "user:maria"},
   972  		},
   973  		{
   974  			name: "multiple_conditions_no_param_provided",
   975  			req: &openfgav1.ListUsersRequest{
   976  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
   977  				Relation: "viewer",
   978  				UserFilters: []*openfgav1.UserTypeFilter{
   979  					{
   980  						Type: "user",
   981  					},
   982  				},
   983  				Context: testutils.MustNewStruct(t, map[string]interface{}{}),
   984  			},
   985  			model: `
   986  			model
   987  				schema 1.1
   988  
   989  			type user
   990  
   991  			type document
   992  				relations
   993  					define viewer: [user]
   994  
   995  			condition isEqualToFive(param1: int) {
   996  				param1 == 5
   997  			}
   998  
   999  			condition isEqualToTen(param2: int) {
  1000  				param2 == 10
  1001  			}`,
  1002  			tuples: []*openfgav1.TupleKey{
  1003  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:will", "isEqualToFive", nil),
  1004  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isEqualToTen", nil),
  1005  			},
  1006  			expectedUsers: []string{},
  1007  		},
  1008  		{
  1009  			name: "multiple_conditions_some_params_provided",
  1010  			req: &openfgav1.ListUsersRequest{
  1011  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1012  				Relation: "viewer",
  1013  				UserFilters: []*openfgav1.UserTypeFilter{
  1014  					{
  1015  						Type: "user",
  1016  					},
  1017  				},
  1018  				Context: testutils.MustNewStruct(t, map[string]interface{}{"param1": 5}),
  1019  			},
  1020  			model: `
  1021  			model
  1022  				schema 1.1
  1023  			type user
  1024  
  1025  			type document
  1026  				relations
  1027  					define viewer: [user with isEqualToFive, user with isEqualToTen]
  1028  
  1029  			condition isEqualToFive(param1: int) {
  1030  				param1 == 5
  1031  			}
  1032  			
  1033  			condition isEqualToTen(param2: int) {
  1034  				param2 == 10
  1035  			}`,
  1036  			tuples: []*openfgav1.TupleKey{
  1037  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:will", "isEqualToFive", nil),
  1038  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isEqualToTen", nil),
  1039  			},
  1040  			expectedErrorMsg: "failed to evaluate relationship condition: 'isEqualToTen' - context is missing parameters '[param2]'",
  1041  		},
  1042  		{
  1043  			name: "multiple_conditions_all_params_provided",
  1044  			req: &openfgav1.ListUsersRequest{
  1045  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1046  				Relation: "viewer",
  1047  				UserFilters: []*openfgav1.UserTypeFilter{
  1048  					{
  1049  						Type: "user",
  1050  					},
  1051  				},
  1052  				Context: testutils.MustNewStruct(t, map[string]interface{}{"param1": 5, "param2": 10}),
  1053  			},
  1054  			model: `
  1055  			model
  1056  				schema 1.1
  1057  			type user
  1058  
  1059  			type document
  1060  				relations
  1061  					define viewer: [user with isEqualToFive, user with isEqualToTen]
  1062  
  1063  			condition isEqualToFive(param1: int) {
  1064  				param1 == 5
  1065  			}
  1066  			
  1067  			condition isEqualToTen(param2: int) {
  1068  				param2 == 10
  1069  			}`,
  1070  			tuples: []*openfgav1.TupleKey{
  1071  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:will", "isEqualToFive", nil),
  1072  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "isEqualToTen", nil),
  1073  			},
  1074  			expectedUsers: []string{"user:will", "user:maria"},
  1075  		},
  1076  		{
  1077  			name: "error_in_direct_eval",
  1078  			req: &openfgav1.ListUsersRequest{
  1079  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1080  				Relation: "viewer",
  1081  				UserFilters: []*openfgav1.UserTypeFilter{
  1082  					{
  1083  						Type: "user",
  1084  					},
  1085  				},
  1086  				Context: testutils.MustNewStruct(t, map[string]interface{}{"x": "1.79769313486231570814527423731704356798070e+309"}),
  1087  			},
  1088  			model: `
  1089  			model
  1090  				schema 1.1
  1091  			type user
  1092  
  1093  			type document
  1094  				relations
  1095  					define viewer: [user with condFloat]
  1096  
  1097  			condition condFloat(x: double) {
  1098  				x > 0.0
  1099  			}`,
  1100  			tuples: []*openfgav1.TupleKey{
  1101  				tuple.NewTupleKeyWithCondition("document:1", "viewer", "user:maria", "condFloat", nil),
  1102  			},
  1103  			expectedErrorMsg: "failed to evaluate relationship condition: parameter type error on condition 'condFloat' - failed to convert context parameter 'x': number cannot be represented as a float64: 1.797693135e+309",
  1104  		},
  1105  		{
  1106  			name: "error_in_ttu_eval",
  1107  			req: &openfgav1.ListUsersRequest{
  1108  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1109  				Relation: "viewer",
  1110  				UserFilters: []*openfgav1.UserTypeFilter{
  1111  					{
  1112  						Type: "user",
  1113  					},
  1114  				},
  1115  				Context: testutils.MustNewStruct(t, map[string]interface{}{"x": "1.79769313486231570814527423731704356798070e+309"}),
  1116  			},
  1117  			model: `
  1118  			model
  1119  				schema 1.1
  1120  			type user
  1121  			type folder
  1122  				relations
  1123  					define viewer: [user with condFloat]
  1124  			type document
  1125  				relations
  1126  					define parent: [folder with condFloat]
  1127  					define viewer: viewer from parent
  1128  
  1129  			condition condFloat(x: double) {
  1130  				x > 0.0
  1131  			}`,
  1132  			tuples: []*openfgav1.TupleKey{
  1133  				tuple.NewTupleKeyWithCondition("document:1", "parent", "folder:x", "condFloat", nil),
  1134  			},
  1135  			expectedErrorMsg: "failed to evaluate relationship condition: parameter type error on condition 'condFloat' - failed to convert context parameter 'x': number cannot be represented as a float64: 1.797693135e+309",
  1136  		},
  1137  	}
  1138  	tests.runListUsersTestCases(t)
  1139  }
  1140  
  1141  func TestListUsersIntersection(t *testing.T) {
  1142  	t.Cleanup(func() {
  1143  		goleak.VerifyNone(t)
  1144  	})
  1145  	tests := ListUsersTests{
  1146  		{
  1147  			name: "intersection",
  1148  			req: &openfgav1.ListUsersRequest{
  1149  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1150  				Relation: "viewer",
  1151  				UserFilters: []*openfgav1.UserTypeFilter{
  1152  					{
  1153  						Type: "user",
  1154  					},
  1155  				},
  1156  			},
  1157  			model: `model
  1158  			schema 1.1
  1159  			type user
  1160  			type document
  1161  				relations
  1162  					define required: [user]
  1163  					define required_other: [user]
  1164  					define viewer: required and required_other`,
  1165  
  1166  			tuples: []*openfgav1.TupleKey{
  1167  				tuple.NewTupleKey("document:1", "required", "user:will"),
  1168  				tuple.NewTupleKey("document:1", "required_other", "user:will"),
  1169  
  1170  				tuple.NewTupleKey("document:1", "required", "user:jon"),
  1171  				tuple.NewTupleKey("document:1", "required_other", "user:maria"),
  1172  			},
  1173  			expectedUsers: []string{"user:will"},
  1174  		},
  1175  		{
  1176  			name: "intersection_multiple",
  1177  			req: &openfgav1.ListUsersRequest{
  1178  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1179  				Relation: "viewer",
  1180  				UserFilters: []*openfgav1.UserTypeFilter{
  1181  					{
  1182  						Type: "user",
  1183  					},
  1184  				},
  1185  			},
  1186  			model: `model
  1187  			schema 1.1
  1188  			type user
  1189  			type document
  1190  				relations
  1191  					define required_1: [user]
  1192  					define required_2: [user]
  1193  					define required_3: [user]
  1194  					define viewer: [user] and required_1 and required_2 and required_3`,
  1195  
  1196  			tuples: []*openfgav1.TupleKey{
  1197  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  1198  				tuple.NewTupleKey("document:1", "required_1", "user:will"),
  1199  				tuple.NewTupleKey("document:1", "required_2", "user:will"),
  1200  				tuple.NewTupleKey("document:1", "required_3", "user:will"),
  1201  
  1202  				tuple.NewTupleKey("document:1", "viewer", "user:jon"),
  1203  				tuple.NewTupleKey("document:1", "required_1", "user:jon"),
  1204  				tuple.NewTupleKey("document:1", "required_2", "user:jon"),
  1205  
  1206  				tuple.NewTupleKey("document:1", "viewer", "user:maria"),
  1207  				tuple.NewTupleKey("document:1", "required_1", "user:maria"),
  1208  
  1209  				tuple.NewTupleKey("document:1", "viewer", "user:poovam"),
  1210  			},
  1211  			expectedUsers: []string{"user:will"},
  1212  		},
  1213  		{
  1214  			name: "intersection_at_multiple_levels",
  1215  			req: &openfgav1.ListUsersRequest{
  1216  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1217  				Relation: "viewer",
  1218  				UserFilters: []*openfgav1.UserTypeFilter{
  1219  					{
  1220  						Type: "user",
  1221  					},
  1222  				},
  1223  			},
  1224  			model: `model
  1225  			schema 1.1
  1226  		type user
  1227  		type document
  1228  			relations
  1229  				define required: [user]
  1230  				define owner: [user] and required
  1231  				define editor: [user] and owner
  1232  				define viewer: [user] and editor`,
  1233  			tuples: []*openfgav1.TupleKey{
  1234  				tuple.NewTupleKey("document:1", "required", "user:will"),
  1235  				tuple.NewTupleKey("document:1", "owner", "user:will"),
  1236  				tuple.NewTupleKey("document:1", "editor", "user:will"),
  1237  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  1238  
  1239  				tuple.NewTupleKey("document:1", "viewer", "user:jon"),
  1240  				tuple.NewTupleKey("document:1", "owner", "user:jon"),
  1241  				tuple.NewTupleKey("document:1", "editor", "user:jon"),
  1242  
  1243  				tuple.NewTupleKey("document:1", "viewer", "user:maria"),
  1244  				tuple.NewTupleKey("document:1", "owner", "user:maria"),
  1245  				tuple.NewTupleKey("document:1", "required", "user:maria"),
  1246  			},
  1247  			expectedUsers: []string{"user:will"},
  1248  		},
  1249  		{
  1250  			name: "intersection_and_ttu",
  1251  			req: &openfgav1.ListUsersRequest{
  1252  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1253  				Relation: "viewer",
  1254  				UserFilters: []*openfgav1.UserTypeFilter{
  1255  					{
  1256  						Type: "user",
  1257  					},
  1258  				},
  1259  			},
  1260  			model: `model
  1261  			schema 1.1
  1262  		  type user
  1263  		  type folder
  1264  			relations
  1265  			  define viewer: [user]
  1266  		  type document
  1267  			relations
  1268  			  define required: [user]
  1269  			  define parent: [folder]
  1270  			  define viewer: (viewer from parent) and required`,
  1271  
  1272  			tuples: []*openfgav1.TupleKey{
  1273  				tuple.NewTupleKey("document:1", "required", "user:will"),
  1274  				tuple.NewTupleKey("folder:x", "viewer", "user:will"),
  1275  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
  1276  
  1277  				tuple.NewTupleKey("document:1", "required", "user:maria"),
  1278  				tuple.NewTupleKey("folder:x", "viewer", "user:jon"),
  1279  			},
  1280  			expectedUsers: []string{"user:will"},
  1281  		},
  1282  	}
  1283  	tests.runListUsersTestCases(t)
  1284  }
  1285  
  1286  func TestListUsersUnion(t *testing.T) {
  1287  	t.Cleanup(func() {
  1288  		goleak.VerifyNone(t)
  1289  	})
  1290  	tests := ListUsersTests{
  1291  		{
  1292  			name: "union",
  1293  			req: &openfgav1.ListUsersRequest{
  1294  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1295  				Relation: "viewer",
  1296  				UserFilters: []*openfgav1.UserTypeFilter{
  1297  					{
  1298  						Type: "user",
  1299  					},
  1300  				},
  1301  			},
  1302  			model: `model
  1303  			schema 1.1
  1304  			type user
  1305  			type document
  1306  				relations
  1307  					define optional_1: [user]
  1308  					define optional_2: [user]
  1309  					define viewer: optional_1 or optional_2`,
  1310  
  1311  			tuples: []*openfgav1.TupleKey{
  1312  				tuple.NewTupleKey("document:1", "optional_1", "user:will"),
  1313  				tuple.NewTupleKey("document:1", "optional_2", "user:will"),
  1314  
  1315  				tuple.NewTupleKey("document:1", "optional_1", "user:jon"),
  1316  				tuple.NewTupleKey("document:1", "optional_2", "user:maria"),
  1317  			},
  1318  			expectedUsers: []string{"user:will", "user:jon", "user:maria"},
  1319  		},
  1320  		{
  1321  			name: "union_and_ttu",
  1322  			req: &openfgav1.ListUsersRequest{
  1323  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1324  				Relation: "viewer",
  1325  				UserFilters: []*openfgav1.UserTypeFilter{
  1326  					{
  1327  						Type: "user",
  1328  					},
  1329  				},
  1330  			},
  1331  			model: `model
  1332  			schema 1.1
  1333  		  type user
  1334  		  type folder
  1335  			relations
  1336  			  define viewer: [user]
  1337  		  type document
  1338  			relations
  1339  			  define optional: [user]
  1340  			  define parent: [folder]
  1341  			  define viewer: (viewer from parent) or optional`,
  1342  
  1343  			tuples: []*openfgav1.TupleKey{
  1344  				tuple.NewTupleKey("document:1", "optional", "user:will"),
  1345  				tuple.NewTupleKey("folder:x", "viewer", "user:will"),
  1346  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
  1347  
  1348  				tuple.NewTupleKey("document:1", "optional", "user:maria"),
  1349  				tuple.NewTupleKey("folder:x", "viewer", "user:jon"),
  1350  			},
  1351  			expectedUsers: []string{"user:will", "user:maria", "user:jon"},
  1352  		},
  1353  		{
  1354  			name: "union_all_possible_rewrites",
  1355  			req: &openfgav1.ListUsersRequest{
  1356  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1357  				Relation: "viewer",
  1358  				UserFilters: []*openfgav1.UserTypeFilter{
  1359  					{
  1360  						Type: "user",
  1361  					},
  1362  				},
  1363  			},
  1364  			model: `model
  1365  			schema 1.1
  1366  		  	type user
  1367  			type folder
  1368  				relations
  1369  					define viewer: [user]
  1370  			type document
  1371  				relations
  1372  					define parent: [folder]
  1373  					define editor: [user]
  1374  					define viewer: [user] or editor or viewer from parent`,
  1375  			tuples: []*openfgav1.TupleKey{
  1376  				tuple.NewTupleKey("folder:x", "viewer", "user:will"),
  1377  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
  1378  				tuple.NewTupleKey("document:1", "editor", "user:maria"),
  1379  				tuple.NewTupleKey("document:1", "viewer", "user:jon"),
  1380  				tuple.NewTupleKey("folder:other", "viewer", "user:poovam"),
  1381  			},
  1382  			expectedUsers: []string{"user:jon", "user:maria", "user:will"},
  1383  		},
  1384  	}
  1385  	tests.runListUsersTestCases(t)
  1386  }
  1387  
  1388  func TestListUsersExclusion(t *testing.T) {
  1389  	t.Cleanup(func() {
  1390  		goleak.VerifyNone(t)
  1391  	})
  1392  	tests := ListUsersTests{
  1393  		{
  1394  			name: "exclusion",
  1395  			req: &openfgav1.ListUsersRequest{
  1396  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1397  				Relation: "viewer",
  1398  				UserFilters: []*openfgav1.UserTypeFilter{
  1399  					{
  1400  						Type: "user",
  1401  					},
  1402  				},
  1403  			},
  1404  			model: `model
  1405  			schema 1.1
  1406  			type user
  1407  			type document
  1408  				relations
  1409  					define blocked: [user]
  1410  					define viewer: [user] but not blocked`,
  1411  
  1412  			tuples: []*openfgav1.TupleKey{
  1413  				tuple.NewTupleKey("document:1", "viewer", "user:blocked_user"),
  1414  				tuple.NewTupleKey("document:1", "blocked", "user:blocked_user"),
  1415  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  1416  				tuple.NewTupleKey("document:1", "blocked", "user:another_blocked_user"),
  1417  			},
  1418  			expectedUsers: []string{"user:will"},
  1419  		},
  1420  		{
  1421  			name: "exclusion_multiple",
  1422  			req: &openfgav1.ListUsersRequest{
  1423  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1424  				Relation: "viewer",
  1425  				UserFilters: []*openfgav1.UserTypeFilter{
  1426  					{
  1427  						Type: "user",
  1428  					},
  1429  				},
  1430  			},
  1431  			model: `model
  1432  			schema 1.1
  1433  		  type user
  1434  		  type document
  1435  			relations
  1436  			  define blocked_1: [user]
  1437  			  define blocked_2: [user]
  1438  			  define viewer: ([user] but not blocked_1) but not blocked_2`,
  1439  
  1440  			tuples: []*openfgav1.TupleKey{
  1441  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  1442  
  1443  				tuple.NewTupleKey("document:1", "viewer", "user:maria"),
  1444  				tuple.NewTupleKey("document:1", "blocked_1", "user:maria"),
  1445  
  1446  				tuple.NewTupleKey("document:1", "viewer", "user:jon"),
  1447  				tuple.NewTupleKey("document:1", "blocked_2", "user:jon"),
  1448  
  1449  				tuple.NewTupleKey("document:1", "viewer", "user:poovam"),
  1450  				tuple.NewTupleKey("document:1", "blocked_1", "user:poovam"),
  1451  				tuple.NewTupleKey("document:1", "blocked_2", "user:poovam"),
  1452  			},
  1453  			expectedUsers: []string{"user:will"},
  1454  		},
  1455  		{
  1456  			name: "exclusion_chained_computed",
  1457  			req: &openfgav1.ListUsersRequest{
  1458  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1459  				Relation: "viewer",
  1460  				UserFilters: []*openfgav1.UserTypeFilter{
  1461  					{
  1462  						Type: "user",
  1463  					},
  1464  				},
  1465  			},
  1466  			model: `model
  1467  			schema 1.1
  1468  		  
  1469  		  type org
  1470  			relations
  1471  			  define blocked: [user]
  1472  		  
  1473  		  type user    
  1474  		  
  1475  		  type document
  1476  			relations
  1477  			  define parent: [org]
  1478  			  define owner: [user]
  1479  			  define blocked: blocked from parent
  1480  			  define editor: owner but not blocked
  1481  			  define viewer: editor`,
  1482  
  1483  			tuples: []*openfgav1.TupleKey{
  1484  				tuple.NewTupleKey("document:1", "parent", "org:x"),
  1485  				tuple.NewTupleKey("document:1", "owner", "user:will"),
  1486  				tuple.NewTupleKey("document:1", "owner", "user:poovam"),
  1487  
  1488  				tuple.NewTupleKey("org:x", "blocked", "user:poovam"),
  1489  			},
  1490  			expectedUsers: []string{"user:will"},
  1491  		},
  1492  		{
  1493  			name: "exclusion_and_ttu",
  1494  			req: &openfgav1.ListUsersRequest{
  1495  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1496  				Relation: "viewer",
  1497  				UserFilters: []*openfgav1.UserTypeFilter{
  1498  					{
  1499  						Type: "user",
  1500  					},
  1501  				},
  1502  			},
  1503  			model: `model
  1504  			schema 1.1
  1505  		  
  1506  		  type user
  1507  		  
  1508  		  type org
  1509  			relations
  1510  			  define blocked: [user]
  1511  		  
  1512  		  type folder
  1513  			relations
  1514  			  define blocked: blocked from org
  1515  			  define org: [org]
  1516  			  define viewer: [user]
  1517  		  
  1518  		  type document
  1519  			relations
  1520  			  define parent: [folder]
  1521  			  define viewer: viewer from parent but not blocked from parent
  1522  		  `,
  1523  
  1524  			tuples: []*openfgav1.TupleKey{
  1525  				tuple.NewTupleKey("folder:x", "org", "org:x"),
  1526  
  1527  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
  1528  				tuple.NewTupleKey("folder:x", "viewer", "user:will"),
  1529  
  1530  				tuple.NewTupleKey("folder:x", "viewer", "user:maria"),
  1531  				tuple.NewTupleKey("org:x", "blocked", "user:maria"),
  1532  			},
  1533  			expectedUsers: []string{"user:will"},
  1534  		},
  1535  		{
  1536  			name: "exclusion_and_self_referential_tuples_1",
  1537  			req: &openfgav1.ListUsersRequest{
  1538  				Object:   &openfgav1.Object{Type: "group", Id: "1"},
  1539  				Relation: "member",
  1540  				UserFilters: []*openfgav1.UserTypeFilter{
  1541  					{
  1542  						Type: "user",
  1543  					},
  1544  				},
  1545  			},
  1546  			model: `model
  1547  			schema 1.1
  1548  		  
  1549  		  type user
  1550  		  
  1551  		  type group
  1552  			relations
  1553  			  define member: [user, group#member] but not blocked
  1554  			  define blocked: [user, group#member]`,
  1555  
  1556  			tuples: []*openfgav1.TupleKey{
  1557  				tuple.NewTupleKey("group:1", "blocked", "group:1#member"),
  1558  				tuple.NewTupleKey("group:1", "member", "user:will"),
  1559  			},
  1560  			expectedUsers: []string{},
  1561  		},
  1562  		{
  1563  			name: "exclusion_with_chained_negation",
  1564  			req: &openfgav1.ListUsersRequest{
  1565  				Object:   &openfgav1.Object{Type: "document", Id: "2"},
  1566  				Relation: "viewer",
  1567  				UserFilters: []*openfgav1.UserTypeFilter{
  1568  					{
  1569  						Type: "user",
  1570  					},
  1571  				},
  1572  			},
  1573  			model: `model
  1574  			  schema 1.1
  1575  
  1576  			type user
  1577  
  1578  			type document
  1579  			  relations
  1580  			    define unblocked: [user]
  1581  				define blocked: [user, document#viewer] but not unblocked
  1582  				define viewer: [user, document#blocked] but not blocked
  1583  			`,
  1584  			tuples: []*openfgav1.TupleKey{
  1585  				tuple.NewTupleKey("document:1", "viewer", "document:2#blocked"),
  1586  				tuple.NewTupleKey("document:2", "blocked", "document:1#viewer"),
  1587  				tuple.NewTupleKey("document:2", "viewer", "user:jon"),
  1588  				tuple.NewTupleKey("document:2", "unblocked", "user:jon"),
  1589  			},
  1590  			expectedUsers: []string{"user:jon"},
  1591  		},
  1592  		{
  1593  			name: "non_stratifiable_exclusion_containing_cycle_1",
  1594  			req: &openfgav1.ListUsersRequest{
  1595  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1596  				Relation: "viewer",
  1597  				UserFilters: []*openfgav1.UserTypeFilter{
  1598  					{
  1599  						Type:     "document",
  1600  						Relation: "blocked",
  1601  					},
  1602  				},
  1603  			},
  1604  			model: `model
  1605  				schema 1.1
  1606  		
  1607  			type user
  1608  		
  1609  			type document
  1610  				relations
  1611  				define blocked: [user, document#viewer]
  1612  				define viewer: [user, document#blocked] but not blocked
  1613  			`,
  1614  			tuples: []*openfgav1.TupleKey{
  1615  				tuple.NewTupleKey("document:1", "viewer", "document:2#blocked"),
  1616  				tuple.NewTupleKey("document:2", "blocked", "document:1#viewer"),
  1617  			},
  1618  			expectedUsers: []string{"document:2#blocked"},
  1619  		},
  1620  	}
  1621  	tests.runListUsersTestCases(t)
  1622  }
  1623  
  1624  func TestListUsersExclusionWildcards(t *testing.T) {
  1625  	t.Cleanup(func() {
  1626  		goleak.VerifyNone(t)
  1627  	})
  1628  
  1629  	model := `model
  1630  	schema 1.1
  1631    
  1632    type user
  1633    
  1634    type document
  1635  	relations
  1636  	  define blocked: [user:*,user]
  1637  	  define viewer: [user:*,user] but not blocked`
  1638  
  1639  	tests := ListUsersTests{
  1640  		{
  1641  			name: "exclusion_and_wildcards_1",
  1642  			req: &openfgav1.ListUsersRequest{
  1643  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1644  				Relation: "viewer",
  1645  				UserFilters: []*openfgav1.UserTypeFilter{
  1646  					{
  1647  						Type: "user",
  1648  					},
  1649  				},
  1650  			},
  1651  			model: model,
  1652  			tuples: []*openfgav1.TupleKey{
  1653  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  1654  				tuple.NewTupleKey("document:1", "blocked", "user:*"),
  1655  			},
  1656  			expectedUsers: []string{},
  1657  		},
  1658  		{
  1659  			name: "exclusion_and_wildcards_2",
  1660  			req: &openfgav1.ListUsersRequest{
  1661  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1662  				Relation: "viewer",
  1663  				UserFilters: []*openfgav1.UserTypeFilter{
  1664  					{
  1665  						Type: "user",
  1666  					},
  1667  				},
  1668  			},
  1669  			model: model,
  1670  			tuples: []*openfgav1.TupleKey{
  1671  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  1672  				tuple.NewTupleKey("document:1", "blocked", "user:will"),
  1673  				tuple.NewTupleKey("document:1", "blocked", "user:*"),
  1674  			},
  1675  			expectedUsers: []string{},
  1676  		},
  1677  		{
  1678  			name: "exclusion_and_wildcards_3",
  1679  			req: &openfgav1.ListUsersRequest{
  1680  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1681  				Relation: "viewer",
  1682  				UserFilters: []*openfgav1.UserTypeFilter{
  1683  					{
  1684  						Type: "user",
  1685  					},
  1686  				},
  1687  			},
  1688  			model: model,
  1689  			tuples: []*openfgav1.TupleKey{
  1690  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  1691  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  1692  				tuple.NewTupleKey("document:1", "blocked", "user:will"),
  1693  				tuple.NewTupleKey("document:1", "blocked", "user:*"),
  1694  			},
  1695  			expectedUsers: []string{},
  1696  		},
  1697  		{
  1698  			name: "exclusion_and_wildcards_4",
  1699  			req: &openfgav1.ListUsersRequest{
  1700  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1701  				Relation: "viewer",
  1702  				UserFilters: []*openfgav1.UserTypeFilter{
  1703  					{
  1704  						Type: "user",
  1705  					},
  1706  				},
  1707  			},
  1708  			model: model,
  1709  			tuples: []*openfgav1.TupleKey{
  1710  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  1711  				tuple.NewTupleKey("document:1", "blocked", "user:maria"),
  1712  			},
  1713  			expectedUsers: []string{"user:*"},
  1714  		},
  1715  		{
  1716  			name: "exclusion_and_wildcards_5",
  1717  			req: &openfgav1.ListUsersRequest{
  1718  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1719  				Relation: "viewer",
  1720  				UserFilters: []*openfgav1.UserTypeFilter{
  1721  					{
  1722  						Type: "user",
  1723  					},
  1724  				},
  1725  			},
  1726  			model: model,
  1727  			tuples: []*openfgav1.TupleKey{
  1728  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  1729  				tuple.NewTupleKey("document:1", "viewer", "user:maria"),
  1730  				tuple.NewTupleKey("document:1", "blocked", "user:*"),
  1731  			},
  1732  			expectedUsers: []string{},
  1733  		},
  1734  		{
  1735  			name: "exclusion_and_wildcards_6",
  1736  			req: &openfgav1.ListUsersRequest{
  1737  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1738  				Relation: "viewer",
  1739  				UserFilters: []*openfgav1.UserTypeFilter{
  1740  					{
  1741  						Type: "user",
  1742  					},
  1743  				},
  1744  			},
  1745  			model: model,
  1746  			tuples: []*openfgav1.TupleKey{
  1747  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  1748  				tuple.NewTupleKey("document:1", "viewer", "user:maria"),
  1749  				tuple.NewTupleKey("document:1", "viewer", "user:jon"),
  1750  				tuple.NewTupleKey("document:1", "blocked", "user:jon"),
  1751  				tuple.NewTupleKey("document:1", "blocked", "user:will"),
  1752  			},
  1753  			expectedUsers: []string{"user:*", "user:maria"},
  1754  		},
  1755  	}
  1756  	tests.runListUsersTestCases(t)
  1757  }
  1758  
  1759  func TestListUsersWildcards(t *testing.T) {
  1760  	t.Cleanup(func() {
  1761  		goleak.VerifyNone(t)
  1762  	})
  1763  	tests := ListUsersTests{
  1764  		{
  1765  			name: "direct_relationship_wildcard",
  1766  			req: &openfgav1.ListUsersRequest{
  1767  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1768  				Relation: "viewer",
  1769  				UserFilters: []*openfgav1.UserTypeFilter{
  1770  					{
  1771  						Type: "user",
  1772  					},
  1773  				},
  1774  			},
  1775  			model: `model
  1776  			schema 1.1
  1777  			type user
  1778  			type document
  1779  				relations
  1780  					define viewer: [user:*]`,
  1781  			tuples: []*openfgav1.TupleKey{
  1782  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  1783  				tuple.NewTupleKey("document:2", "viewer", "user:*"),
  1784  			},
  1785  			expectedUsers: []string{"user:*"},
  1786  		},
  1787  		{
  1788  			name: "direct_relationship_wildcard_with_direct_relationships_also",
  1789  			req: &openfgav1.ListUsersRequest{
  1790  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1791  				Relation: "viewer",
  1792  				UserFilters: []*openfgav1.UserTypeFilter{
  1793  					{
  1794  						Type: "user",
  1795  					},
  1796  				},
  1797  			},
  1798  			model: `model
  1799  			schema 1.1
  1800  			type user
  1801  			type document
  1802  				relations
  1803  					define viewer: [user:*,user]`,
  1804  			tuples: []*openfgav1.TupleKey{
  1805  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  1806  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  1807  				tuple.NewTupleKey("document:2", "viewer", "user:maria"),
  1808  			},
  1809  			expectedUsers: []string{"user:*", "user:will"},
  1810  		},
  1811  		{
  1812  			name: "multiple_possible_wildcards_user_granularity",
  1813  			req: &openfgav1.ListUsersRequest{
  1814  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1815  				Relation: "viewer",
  1816  				UserFilters: []*openfgav1.UserTypeFilter{
  1817  					{
  1818  						Type: "user",
  1819  					},
  1820  				},
  1821  			},
  1822  			model: `model
  1823  			schema 1.1
  1824  			type user
  1825  			type group
  1826  			type document
  1827  				relations
  1828  					define viewer: [ group:*, user:*]`,
  1829  
  1830  			tuples: []*openfgav1.TupleKey{
  1831  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  1832  				tuple.NewTupleKey("document:1", "viewer", "group:*"),
  1833  			},
  1834  			expectedUsers: []string{"user:*"},
  1835  		},
  1836  		{
  1837  			name: "wildcard_with_indirection",
  1838  			req: &openfgav1.ListUsersRequest{
  1839  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1840  				Relation: "viewer",
  1841  				UserFilters: []*openfgav1.UserTypeFilter{
  1842  					{
  1843  						Type: "user",
  1844  					},
  1845  				},
  1846  			},
  1847  			model: `model
  1848  			schema 1.1
  1849  			type user
  1850  			type group
  1851  				relations
  1852  					define member: [user:*]
  1853  			type document
  1854  				relations
  1855  					define viewer: [group#member]`,
  1856  			tuples: []*openfgav1.TupleKey{
  1857  				tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
  1858  				tuple.NewTupleKey("group:eng", "member", "user:*"),
  1859  			},
  1860  			expectedUsers: []string{"user:*"},
  1861  		},
  1862  		{
  1863  			name: "wildcard_computed_ttu",
  1864  			req: &openfgav1.ListUsersRequest{
  1865  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1866  				Relation: "viewer",
  1867  				UserFilters: []*openfgav1.UserTypeFilter{
  1868  					{
  1869  						Type: "user",
  1870  					},
  1871  				},
  1872  			},
  1873  			model: `model
  1874              schema 1.1
  1875            type user
  1876            type group
  1877              relations
  1878                define member: [user:*]
  1879            type folder
  1880              relations
  1881                define can_view: viewer or can_view from parent
  1882                define parent: [folder]
  1883                define viewer: [group#member]
  1884            type document
  1885              relations
  1886                define parent: [folder]
  1887                define viewer: can_view from parent`,
  1888  			tuples: []*openfgav1.TupleKey{
  1889  				tuple.NewTupleKey("group:eng", "member", "user:*"),
  1890  				tuple.NewTupleKey("folder:eng", "viewer", "group:eng#member"),
  1891  				tuple.NewTupleKey("folder:x", "parent", "folder:eng"),
  1892  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
  1893  				tuple.NewTupleKey("document:1", "parent", "folder:eng"),
  1894  			},
  1895  			expectedUsers: []string{"user:*"},
  1896  		},
  1897  	}
  1898  	tests.runListUsersTestCases(t)
  1899  }
  1900  
  1901  func TestListUsersEdgePruning(t *testing.T) {
  1902  	t.Cleanup(func() {
  1903  		goleak.VerifyNone(t)
  1904  	})
  1905  	tests := ListUsersTests{
  1906  		{
  1907  			name: "valid_edges",
  1908  			req: &openfgav1.ListUsersRequest{
  1909  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1910  				Relation: "viewer",
  1911  				UserFilters: []*openfgav1.UserTypeFilter{
  1912  					{
  1913  						Type: "user",
  1914  					},
  1915  				},
  1916  			},
  1917  			model: `model
  1918  			schema 1.1
  1919  		  type user
  1920  
  1921  		  type document
  1922  			relations			  
  1923  			  define viewer: [user]`,
  1924  			tuples: []*openfgav1.TupleKey{
  1925  				tuple.NewTupleKey("document:1", "viewer", "user:maria"),
  1926  			},
  1927  			expectedUsers: []string{"user:maria"},
  1928  		},
  1929  		{
  1930  			name: "valid_edge_several_computed_relations_away",
  1931  			req: &openfgav1.ListUsersRequest{
  1932  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1933  				Relation: "viewer",
  1934  				UserFilters: []*openfgav1.UserTypeFilter{
  1935  					{
  1936  						Type: "user",
  1937  					},
  1938  				},
  1939  			},
  1940  			model: `model
  1941  			schema 1.1
  1942  		  type user
  1943  		  type document
  1944  			relations
  1945  				define parent: [user]
  1946  				define owner: parent
  1947  				define editor: owner
  1948  				define viewer: editor`,
  1949  			tuples: []*openfgav1.TupleKey{
  1950  				tuple.NewTupleKey("document:1", "parent", "user:maria"),
  1951  			},
  1952  			expectedUsers: []string{"user:maria"},
  1953  		},
  1954  
  1955  		{
  1956  			name: "user_filter_has_invalid_edge",
  1957  			req: &openfgav1.ListUsersRequest{
  1958  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1959  				Relation: "viewer",
  1960  				UserFilters: []*openfgav1.UserTypeFilter{
  1961  					{
  1962  						Type: "folder",
  1963  					},
  1964  				},
  1965  			},
  1966  			model: `model
  1967  			schema 1.1
  1968  		type user
  1969  		type folder
  1970  			relations
  1971  				define viewer: [user]
  1972  		type document
  1973  			relations
  1974  				define parent: [folder]
  1975  				define viewer: viewer from parent`,
  1976  			tuples: []*openfgav1.TupleKey{
  1977  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
  1978  				tuple.NewTupleKey("folder:x", "viewer", "user:1"),
  1979  			},
  1980  			expectedUsers: []string{},
  1981  		},
  1982  		{
  1983  			name: "user_filter_has_valid_edge",
  1984  			req: &openfgav1.ListUsersRequest{
  1985  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  1986  				Relation: "viewer",
  1987  				UserFilters: []*openfgav1.UserTypeFilter{
  1988  					{
  1989  						Type: "user",
  1990  					},
  1991  				},
  1992  			},
  1993  			model: `model
  1994  			schema 1.1
  1995  		type user
  1996  		type folder
  1997  			relations
  1998  				define viewer: [user]
  1999  		type document
  2000  			relations
  2001  				define parent: [folder]
  2002  				define viewer: viewer from parent`,
  2003  			tuples: []*openfgav1.TupleKey{
  2004  				tuple.NewTupleKey("document:1", "parent", "folder:x"),
  2005  				tuple.NewTupleKey("folder:x", "viewer", "user:maria"),
  2006  			},
  2007  			expectedUsers: []string{"user:maria"},
  2008  		},
  2009  		{
  2010  			name: "user_filter_has_invalid_edge_because_relation",
  2011  			req: &openfgav1.ListUsersRequest{
  2012  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2013  				Relation: "INVALID_RELATION",
  2014  				UserFilters: []*openfgav1.UserTypeFilter{
  2015  					{
  2016  						Type: "user",
  2017  					},
  2018  				},
  2019  			},
  2020  			model: `model
  2021  			schema 1.1
  2022  		type user
  2023  		type folder
  2024  			relations
  2025  				define viewer: [user]
  2026  		type document
  2027  			relations
  2028  				define parent: [folder]
  2029  				define viewer: viewer from parent`,
  2030  			expectedErrorMsg: "'document#INVALID_RELATION' relation is undefined",
  2031  		},
  2032  	}
  2033  
  2034  	tests.runListUsersTestCases(t)
  2035  }
  2036  
  2037  func TestListUsersWildcardsAndIntersection(t *testing.T) {
  2038  	t.Cleanup(func() {
  2039  		goleak.VerifyNone(t)
  2040  	})
  2041  	tests := ListUsersTests{
  2042  		{
  2043  			name: "wildcard_and_intersection",
  2044  			req: &openfgav1.ListUsersRequest{
  2045  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2046  				Relation: "can_view",
  2047  				UserFilters: []*openfgav1.UserTypeFilter{
  2048  					{
  2049  						Type: "user",
  2050  					},
  2051  				},
  2052  			},
  2053  			model: `model
  2054              schema 1.1
  2055            type user
  2056            type document
  2057              relations
  2058                define allowed: [user]
  2059                define viewer: [user:*,user] and allowed
  2060                define can_view: viewer`,
  2061  
  2062  			tuples: []*openfgav1.TupleKey{
  2063  				tuple.NewTupleKey("document:1", "allowed", "user:jon"),
  2064  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  2065  				tuple.NewTupleKey("document:1", "viewer", "user:jon"),
  2066  			},
  2067  			expectedUsers: []string{"user:jon"},
  2068  		},
  2069  		{
  2070  			name: "with_multiple_wildcards_1",
  2071  			req: &openfgav1.ListUsersRequest{
  2072  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2073  				Relation: "is_public",
  2074  				UserFilters: []*openfgav1.UserTypeFilter{
  2075  					{
  2076  						Type: "user",
  2077  					},
  2078  				},
  2079  			},
  2080  			model: `model
  2081              schema 1.1
  2082            type user
  2083            type document
  2084              relations
  2085  			  define public_1: [user:*,user]
  2086  			  define public_2: [user:*,user]
  2087  			  define is_public: public_1 and public_2`,
  2088  
  2089  			tuples: []*openfgav1.TupleKey{
  2090  				tuple.NewTupleKey("document:1", "public_1", "user:maria"),
  2091  				tuple.NewTupleKey("document:1", "public_2", "user:maria"),
  2092  
  2093  				tuple.NewTupleKey("document:1", "public_1", "user:*"),
  2094  				tuple.NewTupleKey("document:1", "public_2", "user:*"),
  2095  
  2096  				tuple.NewTupleKey("document:1", "public_1", "user:jon"),
  2097  			},
  2098  			expectedUsers: []string{"user:maria", "user:*", "user:jon"},
  2099  		},
  2100  		{
  2101  			name: "with_multiple_wildcards_2",
  2102  			req: &openfgav1.ListUsersRequest{
  2103  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2104  				Relation: "can_view",
  2105  				UserFilters: []*openfgav1.UserTypeFilter{
  2106  					{
  2107  						Type: "user",
  2108  					},
  2109  				},
  2110  			},
  2111  			model: `model
  2112  		    schema 1.1
  2113  		  type user
  2114  		  type document
  2115  		    relations
  2116  		      define viewer: [user:*,user]
  2117  		      define this_is_not_assigned_to_any_user: [user]
  2118  		      define can_view: viewer and this_is_not_assigned_to_any_user`,
  2119  
  2120  			tuples: []*openfgav1.TupleKey{
  2121  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  2122  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  2123  			},
  2124  			expectedUsers: []string{},
  2125  		},
  2126  		{
  2127  			name: "with_multiple_wildcards_3",
  2128  			req: &openfgav1.ListUsersRequest{
  2129  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2130  				Relation: "can_view",
  2131  				UserFilters: []*openfgav1.UserTypeFilter{
  2132  					{
  2133  						Type: "user",
  2134  					},
  2135  				},
  2136  			},
  2137  			model: `model
  2138  		    schema 1.1
  2139  		  type user
  2140  		  type document
  2141  		    relations
  2142  		      define viewer: [user:*,user]
  2143  		      define required: [user:*,user]
  2144  		      define can_view: viewer and required`,
  2145  
  2146  			tuples: []*openfgav1.TupleKey{
  2147  				tuple.NewTupleKey("document:1", "viewer", "user:*"),
  2148  				tuple.NewTupleKey("document:1", "required", "user:*"),
  2149  				tuple.NewTupleKey("document:1", "viewer", "user:will"),
  2150  				tuple.NewTupleKey("document:1", "required", "user:will"),
  2151  			},
  2152  			expectedUsers: []string{"user:*", "user:will"},
  2153  		},
  2154  		{
  2155  			name: "with_multiple_wildcards_4",
  2156  			req: &openfgav1.ListUsersRequest{
  2157  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2158  				Relation: "can_view",
  2159  				UserFilters: []*openfgav1.UserTypeFilter{
  2160  					{
  2161  						Type: "user",
  2162  					},
  2163  				},
  2164  			},
  2165  			model: `model
  2166  		    schema 1.1
  2167  		  type user
  2168  		  type document
  2169  		    relations
  2170  		      define required_1: [user]
  2171  		      define required_2: [user]
  2172  		      define can_view: required_1 and required_2`,
  2173  
  2174  			tuples: []*openfgav1.TupleKey{
  2175  				tuple.NewTupleKey("document:1", "required_1", "user:*"), // Invalid tuple, wildcard not allowed
  2176  				tuple.NewTupleKey("document:1", "required_2", "user:*"), // Invalid tuple, wildcard not allowed
  2177  				tuple.NewTupleKey("document:1", "required_1", "user:maria"),
  2178  				tuple.NewTupleKey("document:1", "required_2", "user:maria"),
  2179  			},
  2180  			expectedUsers: []string{"user:maria"},
  2181  		},
  2182  
  2183  		{
  2184  			name: "with_multiple_wildcards_5",
  2185  			req: &openfgav1.ListUsersRequest{
  2186  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2187  				Relation: "can_view",
  2188  				UserFilters: []*openfgav1.UserTypeFilter{
  2189  					{
  2190  						Type: "user",
  2191  					},
  2192  				},
  2193  			},
  2194  			model: `model
  2195  		    schema 1.1
  2196  		  type user
  2197  		  type document
  2198  		    relations
  2199  		      define required_1: [user]
  2200  		      define required_2: [user:*]
  2201  		      define can_view: required_1 and required_2`,
  2202  
  2203  			tuples: []*openfgav1.TupleKey{
  2204  				tuple.NewTupleKey("document:1", "required_1", "user:maria"),
  2205  				tuple.NewTupleKey("document:1", "required_2", "user:*"),
  2206  			},
  2207  			expectedUsers: []string{"user:maria"},
  2208  		},
  2209  		{
  2210  			name: "with_multiple_wildcards_6",
  2211  			req: &openfgav1.ListUsersRequest{
  2212  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2213  				Relation: "can_view",
  2214  				UserFilters: []*openfgav1.UserTypeFilter{
  2215  					{
  2216  						Type: "user",
  2217  					},
  2218  				},
  2219  			},
  2220  			model: `model
  2221  		    schema 1.1
  2222  		  type user
  2223  		  type document
  2224  		    relations
  2225  		      define required_1: [user]
  2226  		      define required_2: [user]
  2227  		      define can_view: required_1 and required_2`,
  2228  
  2229  			tuples: []*openfgav1.TupleKey{
  2230  				tuple.NewTupleKey("document:1", "required_1", "user:maria"),
  2231  				tuple.NewTupleKey("document:1", "required_1", "user:jon"),
  2232  
  2233  				tuple.NewTupleKey("document:1", "required_1", "user:will"),
  2234  				tuple.NewTupleKey("document:1", "required_2", "user:will"),
  2235  
  2236  				tuple.NewTupleKey("document:1", "required_1", "user:*"), // Invalid tuple, wildcard not allowed
  2237  				tuple.NewTupleKey("document:1", "required_2", "user:*"), // Invalid tuple, wildcard not allowed
  2238  			},
  2239  			expectedUsers: []string{"user:will"},
  2240  		},
  2241  		{
  2242  			name: "with_multiple_wildcards_7",
  2243  			req: &openfgav1.ListUsersRequest{
  2244  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2245  				Relation: "can_view",
  2246  				UserFilters: []*openfgav1.UserTypeFilter{
  2247  					{
  2248  						Type: "user",
  2249  					},
  2250  				},
  2251  			},
  2252  			model: `model
  2253  		    schema 1.1
  2254  		  type user
  2255  		  type document
  2256  		    relations
  2257  		      define required_1: [user,user:*]
  2258  		      define required_2: [user,user:*]
  2259  		      define can_view: required_1 and required_2`,
  2260  
  2261  			tuples: []*openfgav1.TupleKey{
  2262  				tuple.NewTupleKey("document:1", "required_1", "user:maria"),
  2263  				tuple.NewTupleKey("document:1", "required_1", "user:jon"),
  2264  				tuple.NewTupleKey("document:1", "required_1", "user:*"),
  2265  				tuple.NewTupleKey("document:1", "required_2", "user:will"),
  2266  			},
  2267  			expectedUsers: []string{"user:will"},
  2268  		},
  2269  		{
  2270  			name: "wildcard_intermediate_expansion",
  2271  			req: &openfgav1.ListUsersRequest{
  2272  				Object:   &openfgav1.Object{Type: "document", Id: "1"},
  2273  				Relation: "can_view",
  2274  				UserFilters: []*openfgav1.UserTypeFilter{
  2275  					{
  2276  						Type: "user",
  2277  					},
  2278  				},
  2279  			},
  2280  			model: `model
  2281  			schema 1.1
  2282  		  
  2283  		  type user
  2284  		  
  2285  		  type group
  2286  			relations
  2287  			  define member: [user:*, user]
  2288  		  
  2289  		  type document
  2290  			relations
  2291  			  define group: [group]
  2292  			  define viewer: [group#member] and member from group
  2293  			  define can_view: viewer`,
  2294  
  2295  			tuples: []*openfgav1.TupleKey{
  2296  				tuple.NewTupleKey("document:1", "viewer", "group:y#member"),
  2297  				tuple.NewTupleKey("document:1", "group", "group:x"),
  2298  				tuple.NewTupleKey("group:x", "member", "user:*"),
  2299  				tuple.NewTupleKey("group:y", "member", "user:jon"),
  2300  			},
  2301  			expectedUsers: []string{"user:jon"},
  2302  		},
  2303  	}
  2304  	tests.runListUsersTestCases(t)
  2305  }
  2306  
  2307  func TestListUsersCycleDetection(t *testing.T) {
  2308  	t.Cleanup(func() {
  2309  		goleak.VerifyNone(t)
  2310  	})
  2311  	storeID := ulid.Make().String()
  2312  	modelID := ulid.Make().String()
  2313  
  2314  	mockController := gomock.NewController(t)
  2315  	defer mockController.Finish()
  2316  
  2317  	mockDatastore := mocks.NewMockOpenFGADatastore(mockController)
  2318  
  2319  	// Times(0) ensures that we exit quickly
  2320  	mockDatastore.EXPECT().Read(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
  2321  
  2322  	l := NewListUsersQuery(mockDatastore, WithResolveNodeLimit(maximumRecursiveDepth))
  2323  	channelDone := make(chan struct{})
  2324  	channelWithResults := make(chan *openfgav1.User)
  2325  	channelWithError := make(chan error, 1)
  2326  	model := testutils.MustTransformDSLToProtoWithID(`
  2327  	model
  2328  		schema 1.1
  2329  	type user
  2330  	type document
  2331  		relations
  2332  			define viewer: [user]
  2333  	`)
  2334  	typesys := typesystem.New(model)
  2335  	ctx := typesystem.ContextWithTypesystem(context.Background(), typesys)
  2336  
  2337  	t.Run("enters_loop_detection", func(t *testing.T) {
  2338  		visitedUserset := &openfgav1.UsersetUser{
  2339  			Type:     "document",
  2340  			Id:       "1",
  2341  			Relation: "viewer",
  2342  		}
  2343  		visitedUsersetKey := fmt.Sprintf("%s:%s#%s", visitedUserset.GetType(), visitedUserset.GetId(), visitedUserset.GetRelation())
  2344  		visitedUsersets := make(map[string]struct{})
  2345  		visitedUsersets[visitedUsersetKey] = struct{}{}
  2346  
  2347  		go func() {
  2348  			resp := l.expand(ctx, &internalListUsersRequest{
  2349  				ListUsersRequest: &openfgav1.ListUsersRequest{
  2350  					StoreId:              storeID,
  2351  					AuthorizationModelId: modelID,
  2352  					Object: &openfgav1.Object{
  2353  						Type: visitedUserset.GetType(),
  2354  						Id:   visitedUserset.GetId(),
  2355  					},
  2356  					Relation: visitedUserset.GetRelation(),
  2357  					UserFilters: []*openfgav1.UserTypeFilter{{
  2358  						Type: "user",
  2359  					}},
  2360  				},
  2361  				visitedUsersetsMap: visitedUsersets,
  2362  			}, channelWithResults)
  2363  			if resp.err != nil {
  2364  				channelWithError <- resp.err
  2365  				return
  2366  			}
  2367  			channelDone <- struct{}{}
  2368  		}()
  2369  
  2370  		select {
  2371  		case <-channelWithError:
  2372  			require.FailNow(t, "expected 0 errors")
  2373  		case <-channelWithResults:
  2374  			require.FailNow(t, "expected 0 results")
  2375  		case <-channelDone:
  2376  			break
  2377  		}
  2378  	})
  2379  }
  2380  
  2381  func TestListUsersDepthExceeded(t *testing.T) {
  2382  	t.Cleanup(func() {
  2383  		goleak.VerifyNone(t)
  2384  	})
  2385  	model := `model
  2386  	schema 1.1
  2387  		type user
  2388  
  2389  	type folder
  2390  		relations
  2391  			define parent: [folder]
  2392  			define viewer: [user] or viewer from parent`
  2393  
  2394  	tuples := []*openfgav1.TupleKey{
  2395  		tuple.NewTupleKey("folder:26", "viewer", "user:maria"),
  2396  		tuple.NewTupleKey("folder:25", "parent", "folder:26"),
  2397  		tuple.NewTupleKey("folder:24", "parent", "folder:25"),
  2398  		tuple.NewTupleKey("folder:23", "parent", "folder:24"),
  2399  		tuple.NewTupleKey("folder:22", "parent", "folder:23"),
  2400  		tuple.NewTupleKey("folder:21", "parent", "folder:22"),
  2401  		tuple.NewTupleKey("folder:20", "parent", "folder:21"),
  2402  		tuple.NewTupleKey("folder:19", "parent", "folder:20"),
  2403  		tuple.NewTupleKey("folder:18", "parent", "folder:19"),
  2404  		tuple.NewTupleKey("folder:17", "parent", "folder:18"),
  2405  		tuple.NewTupleKey("folder:16", "parent", "folder:17"),
  2406  		tuple.NewTupleKey("folder:15", "parent", "folder:16"),
  2407  		tuple.NewTupleKey("folder:14", "parent", "folder:15"),
  2408  		tuple.NewTupleKey("folder:13", "parent", "folder:14"),
  2409  		tuple.NewTupleKey("folder:12", "parent", "folder:13"),
  2410  		tuple.NewTupleKey("folder:11", "parent", "folder:12"),
  2411  		tuple.NewTupleKey("folder:10", "parent", "folder:11"),
  2412  		tuple.NewTupleKey("folder:9", "parent", "folder:10"),
  2413  		tuple.NewTupleKey("folder:8", "parent", "folder:9"),
  2414  		tuple.NewTupleKey("folder:7", "parent", "folder:8"),
  2415  		tuple.NewTupleKey("folder:6", "parent", "folder:7"),
  2416  		tuple.NewTupleKey("folder:5", "parent", "folder:6"),
  2417  		tuple.NewTupleKey("folder:4", "parent", "folder:5"),
  2418  		tuple.NewTupleKey("folder:3", "parent", "folder:4"),
  2419  		tuple.NewTupleKey("folder:2", "parent", "folder:3"), // folder:2 will not exceed depth limit of 25
  2420  		tuple.NewTupleKey("folder:1", "parent", "folder:2"), // folder:1 will exceed depth limit of 25
  2421  	}
  2422  
  2423  	tests := ListUsersTests{
  2424  		{
  2425  			name: "depth_should_exceed_limit",
  2426  			req: &openfgav1.ListUsersRequest{
  2427  				Object: &openfgav1.Object{
  2428  					Type: "folder",
  2429  					Id:   "1", // Exceeded because we expand up until folder:1, beyond 25 allowable levels
  2430  				},
  2431  				Relation: "viewer",
  2432  				UserFilters: []*openfgav1.UserTypeFilter{
  2433  					{
  2434  						Type: "user",
  2435  					},
  2436  				},
  2437  			},
  2438  			model:            model,
  2439  			tuples:           tuples,
  2440  			expectedErrorMsg: graph.ErrResolutionDepthExceeded.Error(),
  2441  		},
  2442  		{
  2443  			name: "depth_should_not_exceed_limit",
  2444  			req: &openfgav1.ListUsersRequest{
  2445  				Object: &openfgav1.Object{
  2446  					Type: "folder",
  2447  					Id:   "2", // Does not exceed limit because we expand up until folder:2, up to the allowable 25 levels
  2448  				},
  2449  				Relation: "viewer",
  2450  				UserFilters: []*openfgav1.UserTypeFilter{
  2451  					{
  2452  						Type: "user",
  2453  					},
  2454  				},
  2455  			},
  2456  			model:         model,
  2457  			tuples:        tuples,
  2458  			expectedUsers: []string{"user:maria"},
  2459  		},
  2460  	}
  2461  
  2462  	tests.runListUsersTestCases(t)
  2463  }
  2464  
  2465  func TestListUsersStorageErrors(t *testing.T) {
  2466  	t.Cleanup(func() {
  2467  		goleak.VerifyNone(t)
  2468  	})
  2469  	testCases := map[string]struct {
  2470  		req *openfgav1.ListUsersRequest
  2471  	}{
  2472  		`union`: {
  2473  			req: &openfgav1.ListUsersRequest{
  2474  				Object:      &openfgav1.Object{Type: "document", Id: "1"},
  2475  				Relation:    "union",
  2476  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2477  			},
  2478  		},
  2479  		`exclusion`: {
  2480  			req: &openfgav1.ListUsersRequest{
  2481  				Object:      &openfgav1.Object{Type: "document", Id: "1"},
  2482  				Relation:    "exclusion",
  2483  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2484  			},
  2485  		},
  2486  		`intersection`: {
  2487  			req: &openfgav1.ListUsersRequest{
  2488  				Object:      &openfgav1.Object{Type: "document", Id: "1"},
  2489  				Relation:    "intersection",
  2490  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2491  			},
  2492  		},
  2493  	}
  2494  	for name, test := range testCases {
  2495  		t.Run(name, func(t *testing.T) {
  2496  			mockController := gomock.NewController(t)
  2497  			t.Cleanup(func() {
  2498  				mockController.Finish()
  2499  			})
  2500  			mockDatastore := mocks.NewMockOpenFGADatastore(mockController)
  2501  			mockDatastore.EXPECT().
  2502  				Read(gomock.Any(), gomock.Any(), gomock.Any()).
  2503  				Return(nil, fmt.Errorf("storage err")).
  2504  				Times(2) // each relation consists of two handlers
  2505  
  2506  			model := testutils.MustTransformDSLToProtoWithID(`
  2507  			model
  2508  				schema 1.1
  2509  			type user
  2510  			
  2511  			type document
  2512  				relations
  2513  					define a: [user]
  2514  					define b: [user]
  2515  					define union: a or b
  2516  					define exclusion: a but not b
  2517  					define intersection: a and b`)
  2518  			typesys := typesystem.New(model)
  2519  
  2520  			l := NewListUsersQuery(mockDatastore)
  2521  
  2522  			ctx := typesystem.ContextWithTypesystem(context.Background(), typesys)
  2523  			resp, err := l.ListUsers(ctx, test.req)
  2524  			require.Nil(t, resp)
  2525  			require.ErrorContains(t, err, "storage err")
  2526  		})
  2527  	}
  2528  }
  2529  
  2530  func (testCases ListUsersTests) runListUsersTestCases(t *testing.T) {
  2531  	storeID := ulid.Make().String()
  2532  
  2533  	for _, test := range testCases {
  2534  		ds := memory.New()
  2535  		t.Cleanup(ds.Close)
  2536  		model := testutils.MustTransformDSLToProtoWithID(test.model)
  2537  
  2538  		t.Run(test.name, func(t *testing.T) {
  2539  			typesys, err := typesystem.NewAndValidate(context.Background(), model)
  2540  			require.NoError(t, err)
  2541  
  2542  			err = ds.WriteAuthorizationModel(context.Background(), storeID, model)
  2543  			require.NoError(t, err)
  2544  
  2545  			if len(test.tuples) > 0 {
  2546  				err = ds.Write(context.Background(), storeID, nil, test.tuples)
  2547  				require.NoError(t, err)
  2548  			}
  2549  
  2550  			l := NewListUsersQuery(ds, WithResolveNodeLimit(maximumRecursiveDepth))
  2551  
  2552  			ctx := typesystem.ContextWithTypesystem(context.Background(), typesys)
  2553  
  2554  			test.req.AuthorizationModelId = model.GetId()
  2555  			test.req.StoreId = storeID
  2556  
  2557  			resp, err := l.ListUsers(ctx, test.req)
  2558  
  2559  			actualErrorMsg := ""
  2560  			if err != nil {
  2561  				actualErrorMsg = err.Error()
  2562  			}
  2563  			require.Equal(t, test.expectedErrorMsg, actualErrorMsg)
  2564  
  2565  			actualUsers := resp.GetUsers()
  2566  
  2567  			actualCompare := make([]string, len(actualUsers))
  2568  			for i, u := range resp.GetUsers() {
  2569  				actualCompare[i] = tuple.UserProtoToString(u)
  2570  			}
  2571  
  2572  			require.ElementsMatch(t, actualCompare, test.expectedUsers)
  2573  		})
  2574  	}
  2575  }
  2576  
  2577  func TestListUsersReadFails_NoLeaks(t *testing.T) {
  2578  	t.Cleanup(func() {
  2579  		goleak.VerifyNone(t)
  2580  	})
  2581  
  2582  	store := ulid.Make().String()
  2583  	model := testutils.MustTransformDSLToProtoWithID(`
  2584  		model
  2585  			schema 1.1
  2586  		type user
  2587  		type group
  2588  			relations
  2589  				define member: [user]
  2590  		type document
  2591  			relations
  2592  				define viewer: [group#member]`)
  2593  
  2594  	mockController := gomock.NewController(t)
  2595  	defer mockController.Finish()
  2596  
  2597  	mockDatastore := mocks.NewMockOpenFGADatastore(mockController)
  2598  	gomock.InOrder(
  2599  		mockDatastore.EXPECT().Read(gomock.Any(), store, &openfgav1.TupleKey{
  2600  			Relation: "viewer",
  2601  			Object:   "document:1",
  2602  		}).DoAndReturn(func(_ context.Context, _ string, _ *openfgav1.TupleKey) (storage.TupleIterator, error) {
  2603  			return mocks.NewErrorTupleIterator([]*openfgav1.Tuple{
  2604  				{Key: tuple.NewTupleKey("document:1", "viewer", "group:fga#member")},
  2605  				{Key: tuple.NewTupleKey("document:1", "viewer", "group:eng#member")},
  2606  			}), nil
  2607  		}),
  2608  		mockDatastore.EXPECT().Read(gomock.Any(), store, gomock.Any()).
  2609  			DoAndReturn(func(_ context.Context, _ string, _ *openfgav1.TupleKey) (storage.TupleIterator, error) {
  2610  				return storage.NewStaticTupleIterator([]*openfgav1.Tuple{}), nil
  2611  			}),
  2612  	)
  2613  
  2614  	typesys, err := typesystem.NewAndValidate(context.Background(), model)
  2615  	require.NoError(t, err)
  2616  	ctx := typesystem.ContextWithTypesystem(context.Background(), typesys)
  2617  	resp, err := NewListUsersQuery(mockDatastore).ListUsers(ctx, &openfgav1.ListUsersRequest{
  2618  		StoreId:     store,
  2619  		Object:      &openfgav1.Object{Type: "document", Id: "1"},
  2620  		Relation:    "viewer",
  2621  		UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2622  	})
  2623  
  2624  	require.ErrorContains(t, err, "simulated errors")
  2625  	require.Nil(t, resp)
  2626  }
  2627  
  2628  func TestListUsersReadFails_NoLeaks_TTU(t *testing.T) {
  2629  	t.Cleanup(func() {
  2630  		goleak.VerifyNone(t)
  2631  	})
  2632  
  2633  	store := ulid.Make().String()
  2634  	model := testutils.MustTransformDSLToProtoWithID(`
  2635  		model
  2636  			schema 1.1
  2637  		type user
  2638  		type folder
  2639  			relations
  2640  				define viewer: [user]
  2641  		type document
  2642  			relations
  2643  				define parent: [folder]
  2644  				define viewer: viewer from parent`)
  2645  
  2646  	mockController := gomock.NewController(t)
  2647  	defer mockController.Finish()
  2648  
  2649  	mockDatastore := mocks.NewMockOpenFGADatastore(mockController)
  2650  	gomock.InOrder(
  2651  		mockDatastore.EXPECT().Read(gomock.Any(), store, &openfgav1.TupleKey{
  2652  			Object:   "document:1",
  2653  			Relation: "parent",
  2654  		}).DoAndReturn(func(_ context.Context, _ string, _ *openfgav1.TupleKey) (storage.TupleIterator, error) {
  2655  			return mocks.NewErrorTupleIterator([]*openfgav1.Tuple{
  2656  				{Key: tuple.NewTupleKey("document:1", "parent", "folder:1")},
  2657  				{Key: tuple.NewTupleKey("document:1", "parent", "folder:2")},
  2658  			}), nil
  2659  		}),
  2660  		mockDatastore.EXPECT().Read(gomock.Any(), store, &openfgav1.TupleKey{
  2661  			Object:   "folder:1",
  2662  			Relation: "viewer",
  2663  		}).DoAndReturn(func(_ context.Context, _ string, _ *openfgav1.TupleKey) (storage.TupleIterator, error) {
  2664  			return storage.NewStaticTupleIterator([]*openfgav1.Tuple{}), nil
  2665  		}),
  2666  	)
  2667  
  2668  	typesys, err := typesystem.NewAndValidate(context.Background(), model)
  2669  	require.NoError(t, err)
  2670  	ctx := typesystem.ContextWithTypesystem(context.Background(), typesys)
  2671  	resp, err := NewListUsersQuery(mockDatastore).ListUsers(ctx, &openfgav1.ListUsersRequest{
  2672  		StoreId:     store,
  2673  		Object:      &openfgav1.Object{Type: "document", Id: "1"},
  2674  		Relation:    "viewer",
  2675  		UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2676  	})
  2677  
  2678  	require.ErrorContains(t, err, "simulated errors")
  2679  	require.Nil(t, resp)
  2680  }
  2681  
  2682  func TestListUsersDatastoreQueryCount(t *testing.T) {
  2683  	t.Cleanup(func() {
  2684  		goleak.VerifyNone(t)
  2685  	})
  2686  	ds := memory.New()
  2687  	defer ds.Close()
  2688  
  2689  	storeID := ulid.Make().String()
  2690  
  2691  	err := ds.Write(context.Background(), storeID, nil, []*openfgav1.TupleKey{
  2692  		tuple.NewTupleKey("document:x", "a", "user:jon"),
  2693  		tuple.NewTupleKey("document:x", "a", "user:maria"),
  2694  		tuple.NewTupleKey("document:x", "b", "user:maria"),
  2695  		tuple.NewTupleKey("document:x", "parent", "org:fga"),
  2696  		tuple.NewTupleKey("org:fga", "member", "user:maria"),
  2697  		tuple.NewTupleKey("company:fga", "member", "user:maria"),
  2698  		tuple.NewTupleKey("document:x", "userset", "org:fga#member"),
  2699  		tuple.NewTupleKey("document:x", "multiple_userset", "org:fga#member"),
  2700  		tuple.NewTupleKey("document:x", "multiple_userset", "company:fga#member"),
  2701  		tuple.NewTupleKey("document:public", "wildcard", "user:*"),
  2702  	})
  2703  	require.NoError(t, err)
  2704  
  2705  	model := parser.MustTransformDSLToProto(`model
  2706  		schema 1.1
  2707  	type user
  2708  	
  2709  	type company
  2710  	  relations
  2711  		define member: [user]
  2712  	
  2713  	type org
  2714  	  relations
  2715  		define member: [user]
  2716  	
  2717  	type document
  2718  	  relations
  2719  		define wildcard: [user:*]
  2720  		define userset: [org#member]
  2721  		define multiple_userset: [org#member, company#member]
  2722  		define a: [user]
  2723  		define b: [user]
  2724  		define union: a or b
  2725  		define union_rewrite: union
  2726  		define intersection: a and b
  2727  		define difference: a but not b
  2728  		define ttu: member from parent
  2729  		define union_and_ttu: union and ttu
  2730  		define union_or_ttu: union or ttu or union_rewrite
  2731  		define intersection_of_ttus: union_or_ttu and union_and_ttu
  2732  		define parent: [org]
  2733  	`)
  2734  
  2735  	ctx := typesystem.ContextWithTypesystem(
  2736  		context.Background(),
  2737  		typesystem.New(model),
  2738  	)
  2739  
  2740  	tests := []struct {
  2741  		name             string
  2742  		relation         string
  2743  		object           *openfgav1.Object
  2744  		userFilters      []*openfgav1.UserTypeFilter
  2745  		contextualTuples []*openfgav1.TupleKey
  2746  		dbReads          uint32
  2747  	}{
  2748  		{
  2749  			name:        "no_direct_access",
  2750  			relation:    "a",
  2751  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2752  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2753  			dbReads:     1,
  2754  		},
  2755  		{
  2756  			name:        "direct_access",
  2757  			relation:    "a",
  2758  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2759  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2760  			dbReads:     1,
  2761  		},
  2762  		{
  2763  			name:             "direct_access_thanks_to_contextual_tuple",
  2764  			relation:         "a",
  2765  			contextualTuples: []*openfgav1.TupleKey{tuple.NewTupleKey("document:x", "a", "user:unknown")},
  2766  			object:           &openfgav1.Object{Type: "document", Id: "1"},
  2767  			userFilters:      []*openfgav1.UserTypeFilter{{Type: "user"}},
  2768  			dbReads:          1,
  2769  		},
  2770  		{
  2771  			name:        "union",
  2772  			relation:    "union",
  2773  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2774  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2775  			dbReads:     2,
  2776  		},
  2777  		{
  2778  			name:        "union_no_access",
  2779  			relation:    "union",
  2780  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2781  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2782  			dbReads:     2,
  2783  		},
  2784  		{
  2785  			name:        "intersection",
  2786  			relation:    "intersection",
  2787  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2788  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2789  			dbReads:     2,
  2790  		},
  2791  		{
  2792  			name:        "intersection_no_access",
  2793  			relation:    "intersection",
  2794  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2795  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2796  			dbReads:     2,
  2797  		},
  2798  		{
  2799  			name:        "difference",
  2800  			relation:    "difference",
  2801  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2802  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2803  			dbReads:     2,
  2804  		},
  2805  		{
  2806  			name:        "difference_no_access",
  2807  			relation:    "difference",
  2808  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2809  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2810  			dbReads:     2,
  2811  		},
  2812  		{
  2813  			name:        "ttu",
  2814  			relation:    "ttu",
  2815  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2816  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2817  			dbReads:     1,
  2818  		},
  2819  		{
  2820  			name:        "ttu_no_access",
  2821  			relation:    "ttu",
  2822  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2823  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2824  			dbReads:     1,
  2825  		},
  2826  		{
  2827  			name:        "userset_no_access_1",
  2828  			relation:    "userset",
  2829  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2830  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2831  			dbReads:     1,
  2832  		},
  2833  		{
  2834  			name:        "userset_no_access_2",
  2835  			relation:    "userset",
  2836  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2837  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2838  			dbReads:     1,
  2839  		},
  2840  		{
  2841  			name:        "userset_access",
  2842  			relation:    "userset",
  2843  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2844  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2845  			dbReads:     1,
  2846  		},
  2847  		{
  2848  			name:        "multiple_userset_no_access",
  2849  			relation:    "multiple_userset",
  2850  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2851  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2852  			dbReads:     1,
  2853  		},
  2854  		{
  2855  			name:        "multiple_userset_access",
  2856  			relation:    "multiple_userset",
  2857  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2858  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2859  			dbReads:     1,
  2860  		},
  2861  		{
  2862  			name:        "wildcard_no_access",
  2863  			relation:    "wildcard",
  2864  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2865  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2866  			dbReads:     1,
  2867  		},
  2868  		{
  2869  			name:        "wildcard_access",
  2870  			relation:    "wildcard",
  2871  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2872  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2873  			dbReads:     1,
  2874  		},
  2875  
  2876  		{
  2877  			name:        "union_and_ttu",
  2878  			relation:    "union_and_ttu",
  2879  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2880  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2881  			dbReads:     3,
  2882  		},
  2883  		{
  2884  			name:        "union_and_ttu_no_access",
  2885  			relation:    "union_and_ttu",
  2886  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2887  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2888  			dbReads:     3,
  2889  		},
  2890  		{
  2891  			name:        "union_or_ttu",
  2892  			relation:    "union_or_ttu",
  2893  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2894  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2895  			dbReads:     5,
  2896  		},
  2897  		{
  2898  			name:        "union_or_ttu_no_access",
  2899  			relation:    "union_or_ttu",
  2900  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2901  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2902  			dbReads:     5,
  2903  		},
  2904  		{
  2905  			name:        "intersection_of_ttus",
  2906  			relation:    "intersection_of_ttus",
  2907  			object:      &openfgav1.Object{Type: "document", Id: "1"},
  2908  			userFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2909  			dbReads:     8,
  2910  		},
  2911  	}
  2912  
  2913  	// run the test many times to exercise all the possible DBReads
  2914  
  2915  	for _, test := range tests {
  2916  		test := test
  2917  		t.Run(test.name, func(t *testing.T) {
  2918  			ctx := storage.ContextWithRelationshipTupleReader(
  2919  				ctx,
  2920  				storagewrappers.NewCombinedTupleReader(
  2921  					ds,
  2922  					test.contextualTuples,
  2923  				),
  2924  			)
  2925  
  2926  			l := NewListUsersQuery(ds)
  2927  			resp, err := l.ListUsers(ctx, &openfgav1.ListUsersRequest{
  2928  				Relation:         test.relation,
  2929  				Object:           test.object,
  2930  				UserFilters:      test.userFilters,
  2931  				ContextualTuples: test.contextualTuples,
  2932  			})
  2933  			require.NoError(t, err)
  2934  			require.Equal(t, test.dbReads, resp.GetMetadata().DatastoreQueryCount)
  2935  		})
  2936  	}
  2937  }
  2938  
  2939  func TestListUsersConfig_MaxResults(t *testing.T) {
  2940  	t.Cleanup(func() {
  2941  		goleak.VerifyNone(t)
  2942  	})
  2943  
  2944  	ds := memory.New()
  2945  	t.Cleanup(ds.Close)
  2946  
  2947  	testCases := map[string]struct {
  2948  		inputTuples           []*openfgav1.TupleKey
  2949  		inputModel            string
  2950  		inputRequest          *openfgav1.ListUsersRequest
  2951  		inputConfigMaxResults uint32
  2952  		allResults            []*openfgav1.User // all the results. the server may return less
  2953  		expectMinResults      uint32
  2954  	}{
  2955  		`max_results_infinite`: {
  2956  			inputModel: `
  2957  				model
  2958  					schema 1.1
  2959  				type user
  2960  				type repo
  2961  					relations
  2962  						define admin: [user]`,
  2963  			inputTuples: []*openfgav1.TupleKey{
  2964  				tuple.NewTupleKey("repo:target", "admin", "user:1"),
  2965  				tuple.NewTupleKey("repo:target", "admin", "user:2"),
  2966  			},
  2967  			inputRequest: &openfgav1.ListUsersRequest{
  2968  				ContextualTuples: []*openfgav1.TupleKey{
  2969  					tuple.NewTupleKey("repo:target", "admin", "user:3"),
  2970  				},
  2971  				Object:      &openfgav1.Object{Type: "repo", Id: "target"},
  2972  				Relation:    "admin",
  2973  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  2974  			},
  2975  			inputConfigMaxResults: 0,
  2976  			allResults: []*openfgav1.User{
  2977  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}},
  2978  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "2"}}},
  2979  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "3"}}},
  2980  			},
  2981  			expectMinResults: 3,
  2982  		},
  2983  		`max_results_less_than_actual_results`: {
  2984  			inputModel: `
  2985  				model
  2986  					schema 1.1
  2987  				type user
  2988  				type repo
  2989  					relations
  2990  						define admin: [user]`,
  2991  			inputTuples: []*openfgav1.TupleKey{
  2992  				tuple.NewTupleKey("repo:target", "admin", "user:1"),
  2993  				tuple.NewTupleKey("repo:target", "admin", "user:2"),
  2994  			},
  2995  			inputRequest: &openfgav1.ListUsersRequest{
  2996  				ContextualTuples: []*openfgav1.TupleKey{
  2997  					tuple.NewTupleKey("repo:target", "admin", "user:3"),
  2998  				},
  2999  				Object:      &openfgav1.Object{Type: "repo", Id: "target"},
  3000  				Relation:    "admin",
  3001  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  3002  			},
  3003  			inputConfigMaxResults: 2,
  3004  			allResults: []*openfgav1.User{
  3005  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}},
  3006  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "2"}}},
  3007  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "3"}}},
  3008  			},
  3009  			expectMinResults: 2,
  3010  		},
  3011  		`max_results_more_than_actual_results`: {
  3012  			inputModel: `
  3013  				model
  3014  					schema 1.1
  3015  				type user
  3016  				type repo
  3017  					relations
  3018  						define admin: [user]`,
  3019  			inputTuples: []*openfgav1.TupleKey{
  3020  				tuple.NewTupleKey("repo:target", "admin", "user:1"),
  3021  			},
  3022  			inputRequest: &openfgav1.ListUsersRequest{
  3023  				Object:      &openfgav1.Object{Type: "repo", Id: "target"},
  3024  				Relation:    "admin",
  3025  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  3026  			},
  3027  			inputConfigMaxResults: 2,
  3028  			allResults: []*openfgav1.User{
  3029  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}},
  3030  			},
  3031  			expectMinResults: 1,
  3032  		},
  3033  	}
  3034  	for name, test := range testCases {
  3035  		t.Run(name, func(t *testing.T) {
  3036  			ctx := context.Background()
  3037  
  3038  			// arrange: write model
  3039  			model := testutils.MustTransformDSLToProtoWithID(test.inputModel)
  3040  
  3041  			storeID := ulid.Make().String()
  3042  
  3043  			err := ds.WriteAuthorizationModel(ctx, storeID, model)
  3044  			require.NoError(t, err)
  3045  
  3046  			// arrange: write tuples
  3047  			err = ds.Write(context.Background(), storeID, nil, test.inputTuples)
  3048  			require.NoError(t, err)
  3049  
  3050  			typesys, err := typesystem.NewAndValidate(context.Background(), model)
  3051  			require.NoError(t, err)
  3052  			ctx = typesystem.ContextWithTypesystem(context.Background(), typesys)
  3053  
  3054  			// assertions
  3055  			test.inputRequest.StoreId = storeID
  3056  			res, err := NewListUsersQuery(ds,
  3057  				WithListUsersMaxResults(test.inputConfigMaxResults),
  3058  				WithListUsersDeadline(10*time.Second),
  3059  			).ListUsers(ctx, test.inputRequest)
  3060  
  3061  			require.NotNil(t, res)
  3062  			require.NoError(t, err)
  3063  			if test.inputConfigMaxResults != 0 { // don't get all results
  3064  				require.LessOrEqual(t, len(res.GetUsers()), int(test.inputConfigMaxResults))
  3065  			}
  3066  			require.GreaterOrEqual(t, len(res.GetUsers()), int(test.expectMinResults))
  3067  			require.Subset(t, test.allResults, res.GetUsers())
  3068  		})
  3069  	}
  3070  }
  3071  
  3072  func TestListUsersConfig_Deadline(t *testing.T) {
  3073  	t.Cleanup(func() {
  3074  		goleak.VerifyNone(t)
  3075  	})
  3076  
  3077  	ds := memory.New()
  3078  	t.Cleanup(ds.Close)
  3079  
  3080  	testCases := map[string]struct {
  3081  		inputTuples         []*openfgav1.TupleKey
  3082  		inputModel          string
  3083  		inputRequest        *openfgav1.ListUsersRequest
  3084  		inputConfigDeadline time.Duration     // request can only take this time
  3085  		inputReadDelay      time.Duration     // to be able to hit the deadline at a predictable time
  3086  		allResults          []*openfgav1.User // all the results. the server may return less
  3087  		expectMinResults    uint32
  3088  		expectError         string
  3089  	}{
  3090  		`deadline_very_small_returns_nothing`: {
  3091  			inputModel: `
  3092  				model
  3093  					schema 1.1
  3094  				type user
  3095  				type repo
  3096  					relations
  3097  						define admin: [user]`,
  3098  			inputTuples: []*openfgav1.TupleKey{
  3099  				tuple.NewTupleKey("repo:target", "admin", "user:1"),
  3100  			},
  3101  			inputRequest: &openfgav1.ListUsersRequest{
  3102  				Object:      &openfgav1.Object{Type: "repo", Id: "target"},
  3103  				Relation:    "admin",
  3104  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  3105  			},
  3106  			inputConfigDeadline: 1 * time.Millisecond,
  3107  			inputReadDelay:      50 * time.Millisecond,
  3108  			allResults: []*openfgav1.User{
  3109  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}},
  3110  			},
  3111  			expectError: "context deadline exceeded",
  3112  		},
  3113  		`deadline_very_high_returns_everything`: {
  3114  			inputModel: `
  3115  				model
  3116  					schema 1.1
  3117  				type user
  3118  				type repo
  3119  					relations
  3120  						define admin: [user]`,
  3121  			inputTuples: []*openfgav1.TupleKey{
  3122  				tuple.NewTupleKey("repo:target", "admin", "user:1"),
  3123  			},
  3124  			inputRequest: &openfgav1.ListUsersRequest{
  3125  				Object:      &openfgav1.Object{Type: "repo", Id: "target"},
  3126  				Relation:    "admin",
  3127  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  3128  			},
  3129  			inputConfigDeadline: 1 * time.Second,
  3130  			inputReadDelay:      0 * time.Second,
  3131  			allResults: []*openfgav1.User{
  3132  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}},
  3133  			},
  3134  			expectMinResults: 1,
  3135  		},
  3136  	}
  3137  	for name, test := range testCases {
  3138  		t.Run(name, func(t *testing.T) {
  3139  			ctx := context.Background()
  3140  
  3141  			// arrange: write model
  3142  			model := testutils.MustTransformDSLToProtoWithID(test.inputModel)
  3143  
  3144  			storeID := ulid.Make().String()
  3145  
  3146  			err := ds.WriteAuthorizationModel(ctx, storeID, model)
  3147  			require.NoError(t, err)
  3148  
  3149  			// arrange: write tuples
  3150  			err = ds.Write(context.Background(), storeID, nil, test.inputTuples)
  3151  			require.NoError(t, err)
  3152  
  3153  			typesys, err := typesystem.NewAndValidate(context.Background(), model)
  3154  			require.NoError(t, err)
  3155  			ctx = typesystem.ContextWithTypesystem(context.Background(), typesys)
  3156  
  3157  			// assertions
  3158  			t.Run("regular_endpoint", func(t *testing.T) {
  3159  				test.inputRequest.StoreId = storeID
  3160  				res, err := NewListUsersQuery(
  3161  					mocks.NewMockSlowDataStorage(ds, test.inputReadDelay),
  3162  					WithListUsersDeadline(test.inputConfigDeadline),
  3163  				).ListUsers(ctx, test.inputRequest)
  3164  
  3165  				if test.expectError != "" {
  3166  					require.ErrorContains(t, err, test.expectError)
  3167  				} else {
  3168  					require.NoError(t, err)
  3169  					require.GreaterOrEqual(t, len(res.GetUsers()), int(test.expectMinResults))
  3170  					require.Subset(t, test.allResults, res.GetUsers())
  3171  				}
  3172  			})
  3173  		})
  3174  	}
  3175  }
  3176  
  3177  func TestListUsersConfig_MaxConcurrency(t *testing.T) {
  3178  	t.Cleanup(func() {
  3179  		goleak.VerifyNone(t)
  3180  	})
  3181  
  3182  	ds := memory.New()
  3183  	t.Cleanup(ds.Close)
  3184  
  3185  	testCases := map[string]struct {
  3186  		inputTuples                   []*openfgav1.TupleKey
  3187  		inputModel                    string
  3188  		inputRequest                  *openfgav1.ListUsersRequest
  3189  		inputConfigMaxConcurrentReads uint32
  3190  		inputReadDelay                time.Duration     // to be able to hit the deadline at a predictable time
  3191  		allResults                    []*openfgav1.User // all the results. the server may return less
  3192  		expectMinResults              uint32
  3193  		expectMinExecutionTime        time.Duration
  3194  	}{
  3195  		`max_concurrent_reads_does_not_delay_response_if_only_contextual_tuples_are_in_place`: {
  3196  			inputModel: `
  3197  				model
  3198  					schema 1.1
  3199  				type user
  3200  				type repo
  3201  					relations
  3202  						define admin: [user]`,
  3203  			inputTuples: []*openfgav1.TupleKey{},
  3204  			inputRequest: &openfgav1.ListUsersRequest{
  3205  				Object:      &openfgav1.Object{Type: "repo", Id: "target"},
  3206  				Relation:    "admin",
  3207  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  3208  				ContextualTuples: []*openfgav1.TupleKey{
  3209  					tuple.NewTupleKey("repo:target", "admin", "user:1"),
  3210  				},
  3211  			},
  3212  			inputConfigMaxConcurrentReads: 1,
  3213  			allResults: []*openfgav1.User{
  3214  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}},
  3215  			},
  3216  			expectMinResults:       1,
  3217  			expectMinExecutionTime: 0 * time.Millisecond,
  3218  		},
  3219  		`max_concurrent_reads_delays_response`: {
  3220  			inputModel: `
  3221  				model
  3222  					schema 1.1
  3223  				type user
  3224  				type folder
  3225  					relations
  3226  						define admin: [user]
  3227  				type repo
  3228  					relations
  3229  						define parent: [folder]
  3230  						define admin: [user] or admin from parent`, // two parallel reads will have to be made
  3231  			inputTuples: []*openfgav1.TupleKey{
  3232  				tuple.NewTupleKey("repo:target", "admin", "user:1"),
  3233  			},
  3234  			inputRequest: &openfgav1.ListUsersRequest{
  3235  				Object:      &openfgav1.Object{Type: "repo", Id: "target"},
  3236  				Relation:    "admin",
  3237  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  3238  			},
  3239  			inputReadDelay:                1 * time.Second,
  3240  			inputConfigMaxConcurrentReads: 1,
  3241  			allResults: []*openfgav1.User{
  3242  				{User: &openfgav1.User_Object{Object: &openfgav1.Object{Type: "user", Id: "1"}}},
  3243  			},
  3244  			expectMinExecutionTime: 2 * time.Second,
  3245  		},
  3246  	}
  3247  	for name, test := range testCases {
  3248  		t.Run(name, func(t *testing.T) {
  3249  			ctx := context.Background()
  3250  
  3251  			// arrange: write model
  3252  			model := testutils.MustTransformDSLToProtoWithID(test.inputModel)
  3253  
  3254  			storeID := ulid.Make().String()
  3255  
  3256  			err := ds.WriteAuthorizationModel(ctx, storeID, model)
  3257  			require.NoError(t, err)
  3258  
  3259  			// arrange: write tuples
  3260  			err = ds.Write(context.Background(), storeID, nil, test.inputTuples)
  3261  			require.NoError(t, err)
  3262  
  3263  			typesys, err := typesystem.NewAndValidate(context.Background(), model)
  3264  			require.NoError(t, err)
  3265  			ctx = typesystem.ContextWithTypesystem(context.Background(), typesys)
  3266  
  3267  			// assertions
  3268  			t.Run("regular_endpoint", func(t *testing.T) {
  3269  				test.inputRequest.StoreId = storeID
  3270  				start := time.Now()
  3271  				res, err := NewListUsersQuery(
  3272  					mocks.NewMockSlowDataStorage(ds, test.inputReadDelay),
  3273  					WithListUsersMaxConcurrentReads(test.inputConfigMaxConcurrentReads),
  3274  				).ListUsers(ctx, test.inputRequest)
  3275  
  3276  				require.NoError(t, err)
  3277  				require.GreaterOrEqual(t, len(res.GetUsers()), int(test.expectMinResults))
  3278  				require.Subset(t, test.allResults, res.GetUsers())
  3279  				require.GreaterOrEqual(t, time.Since(start), test.expectMinExecutionTime)
  3280  			})
  3281  		})
  3282  	}
  3283  }