github.com/openfga/openfga@v1.5.4-rc1/pkg/server/test/read.go (about)

     1  package test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/oklog/ulid/v2"
     9  	openfgav1 "github.com/openfga/api/proto/openfga/v1"
    10  	parser "github.com/openfga/language/pkg/go/transformer"
    11  	"github.com/stretchr/testify/require"
    12  	"google.golang.org/protobuf/protoadapt"
    13  	"google.golang.org/protobuf/testing/protocmp"
    14  	"google.golang.org/protobuf/types/known/wrapperspb"
    15  
    16  	"github.com/openfga/openfga/pkg/server/commands"
    17  	serverErrors "github.com/openfga/openfga/pkg/server/errors"
    18  	"github.com/openfga/openfga/pkg/storage"
    19  	"github.com/openfga/openfga/pkg/testutils"
    20  	"github.com/openfga/openfga/pkg/tuple"
    21  	"github.com/openfga/openfga/pkg/typesystem"
    22  )
    23  
    24  // Read Command delegates to [storage.ReadPage].
    25  // TODO Tests here shouldn't assert on correctness of results because that should be tested in pkg/storage/test.
    26  // We should pass a mock datastore and assert that mock.ReadPage was called
    27  
    28  func ReadQuerySuccessTest(t *testing.T, datastore storage.OpenFGADatastore) {
    29  	// TODO: review which of these tests should be moved to validation/types in grpc rather than execution. e.g.: invalid relation in authorizationmodel is fine, but tuple without authorizationmodel is should be required before. see issue: https://github.com/openfga/sandcastle/issues/13
    30  	tests := []struct {
    31  		_name    string
    32  		model    *openfgav1.AuthorizationModel
    33  		tuples   []*openfgav1.TupleKey
    34  		request  *openfgav1.ReadRequest
    35  		response *openfgav1.ReadResponse
    36  	}{
    37  		//		{
    38  		//			_name: "ExecuteReturnsExactMatchingTupleKey",
    39  		//			// state
    40  		//			model: &openfgav1.AuthorizationModel{
    41  		//				Id:            ulid.Make().String(),
    42  		//				SchemaVersion: typesystem.SchemaVersion1_0,
    43  		//				TypeDefinitions: parser.MustTransformDSLToProto(`model
    44  		//  schema 1.0
    45  		// type user
    46  		//
    47  		// type team
    48  		//
    49  		// type repo
    50  		//  relations
    51  		//	define owner: [team]
    52  		//	define admin: [user]`).GetTypeDefinitions(),
    53  		//			},
    54  		//			tuples: []*openfgav1.TupleKey{
    55  		//				{
    56  		//					Object:   "repo:openfga/openfga",
    57  		//					Relation: "admin",
    58  		//					User:     "user:github|jose",
    59  		//				},
    60  		//				{
    61  		//					Object:   "repo:openfga/openfga",
    62  		//					Relation: "owner",
    63  		//					User:     "team:iam",
    64  		//				},
    65  		//			},
    66  		//			// input
    67  		//			request: &openfgav1.ReadRequest{
    68  		//				TupleKey: &openfgav1.ReadRequestTupleKey{
    69  		//					Object:   "repo:openfga/openfga",
    70  		//					Relation: "admin",
    71  		//					User:     "user:github|jose",
    72  		//				},
    73  		//			},
    74  		//			// output
    75  		//			response: &openfgav1.ReadResponse{
    76  		//				Tuples: []*openfgav1.Tuple{
    77  		//					{
    78  		//						Key: &openfgav1.TupleKey{
    79  		//							Object:   "repo:openfga/openfga",
    80  		//							Relation: "admin",
    81  		//							User:     "user:github|jose",
    82  		//						},
    83  		//					},
    84  		//				},
    85  		//			},
    86  		//		},
    87  		{
    88  			_name: "ExecuteReturnsTuplesWithProvidedUserAndObjectIdInAuthorizationModelRegardlessOfRelationIfNoRelation",
    89  			// state
    90  			model: &openfgav1.AuthorizationModel{
    91  				Id:            ulid.Make().String(),
    92  				SchemaVersion: typesystem.SchemaVersion1_1,
    93  				TypeDefinitions: parser.MustTransformDSLToProto(`model
    94    schema 1.1
    95  type user
    96  
    97  type repo
    98    relations
    99  	define admin: [user]
   100  	define owner: [user]`).GetTypeDefinitions(),
   101  			},
   102  			tuples: []*openfgav1.TupleKey{
   103  				tuple.NewTupleKey("repo:openfga/openfga", "admin", "user:github|jose"),
   104  				tuple.NewTupleKey("repo:openfga/openfga", "owner", "user:github|jose"),
   105  			},
   106  			// input
   107  			request: &openfgav1.ReadRequest{
   108  				TupleKey: &openfgav1.ReadRequestTupleKey{
   109  					Object: "repo:openfga/openfga",
   110  					User:   "user:github|jose",
   111  				},
   112  			},
   113  			// output
   114  			response: &openfgav1.ReadResponse{
   115  				Tuples: []*openfgav1.Tuple{
   116  					{Key: tuple.NewTupleKey("repo:openfga/openfga", "admin", "user:github|jose")},
   117  					{Key: tuple.NewTupleKey("repo:openfga/openfga", "owner", "user:github|jose")},
   118  				},
   119  			},
   120  		},
   121  		//{
   122  		//	_name: "ExecuteReturnsTuplesWithProvidedUserInAuthorizationModelRegardlessOfRelationAndObjectIdIfNoRelationAndNoObjectId",
   123  		//	// state
   124  		//	model: &openfgav1.AuthorizationModel{
   125  		//		Id:            ulid.Make().String(),
   126  		//		SchemaVersion: typesystem.SchemaVersion1_0,
   127  		//		TypeDefinitions: []*openfgav1.TypeDefinition{
   128  		//			{
   129  		//				Type: "repo",
   130  		//				Relations: map[string]*openfgav1.Userset{
   131  		//					"admin":  {},
   132  		//					"writer": {},
   133  		//				},
   134  		//			},
   135  		//		},
   136  		//	},
   137  		//	tuples: []*openfgav1.TupleKey{
   138  		//		{
   139  		//			Object:   "repo:openfga/openfga",
   140  		//			Relation: "admin",
   141  		//			User:     "github|jose",
   142  		//		},
   143  		//		{
   144  		//			Object:   "repo:openfga/openfga-server",
   145  		//			Relation: "writer",
   146  		//			User:     "github|jose",
   147  		//		},
   148  		//		{
   149  		//			Object:   "org:openfga",
   150  		//			Relation: "member",
   151  		//			User:     "github|jose",
   152  		//		},
   153  		//	},
   154  		//	// input
   155  		//	request: &openfgav1.ReadRequest{
   156  		//		TupleKey: &openfgav1.ReadRequestTupleKey{
   157  		//			Object: "repo:",
   158  		//			User:   "github|jose",
   159  		//		},
   160  		//	},
   161  		//	// output
   162  		//	response: &openfgav1.ReadResponse{
   163  		//		Tuples: []*openfgav1.Tuple{
   164  		//			{Key: &openfgav1.TupleKey{
   165  		//				Object:   "repo:openfga/openfga",
   166  		//				Relation: "admin",
   167  		//				User:     "github|jose",
   168  		//			}},
   169  		//			{Key: &openfgav1.TupleKey{
   170  		//				Object:   "repo:openfga/openfga-server",
   171  		//				Relation: "writer",
   172  		//				User:     "github|jose",
   173  		//			}},
   174  		//		},
   175  		//	},
   176  		// },
   177  		//{
   178  		//	_name: "ExecuteReturnsTuplesWithProvidedUserAndRelationInAuthorizationModelRegardlessOfObjectIdIfNoObjectId",
   179  		//	// state
   180  		//	model: &openfgav1.AuthorizationModel{
   181  		//		Id:            ulid.Make().String(),
   182  		//		SchemaVersion: typesystem.SchemaVersion1_0,
   183  		//		TypeDefinitions: []*openfgav1.TypeDefinition{
   184  		//			{
   185  		//				Type: "repo",
   186  		//				Relations: map[string]*openfgav1.Userset{
   187  		//					"admin":  {},
   188  		//					"writer": {},
   189  		//				},
   190  		//			},
   191  		//		},
   192  		//	},
   193  		//	tuples: []*openfgav1.TupleKey{
   194  		//		{
   195  		//			Object:   "repo:openfga/openfga",
   196  		//			Relation: "admin",
   197  		//			User:     "github|jose",
   198  		//		},
   199  		//		{
   200  		//			Object:   "repo:openfga/openfga-server",
   201  		//			Relation: "writer",
   202  		//			User:     "github|jose",
   203  		//		},
   204  		//		{
   205  		//			Object:   "repo:openfga/openfga-users",
   206  		//			Relation: "writer",
   207  		//			User:     "github|jose",
   208  		//		},
   209  		//		{
   210  		//			Object:   "org:openfga",
   211  		//			Relation: "member",
   212  		//			User:     "github|jose",
   213  		//		},
   214  		//	},
   215  		//	// input
   216  		//	request: &openfgav1.ReadRequest{
   217  		//		TupleKey: &openfgav1.ReadRequestTupleKey{
   218  		//			Object:   "repo:",
   219  		//			Relation: "writer",
   220  		//			User:     "github|jose",
   221  		//		},
   222  		//	},
   223  		//	// output
   224  		//	response: &openfgav1.ReadResponse{
   225  		//		Tuples: []*openfgav1.Tuple{
   226  		//			{Key: &openfgav1.TupleKey{
   227  		//				Object:   "repo:openfga/openfga-server",
   228  		//				Relation: "writer",
   229  		//				User:     "github|jose",
   230  		//			}},
   231  		//			{Key: &openfgav1.TupleKey{
   232  		//				Object:   "repo:openfga/openfga-users",
   233  		//				Relation: "writer",
   234  		//				User:     "github|jose",
   235  		//			}},
   236  		//		},
   237  		//	},
   238  		// },
   239  		{
   240  			_name: "ExecuteReturnsTuplesWithProvidedObjectIdAndRelationInAuthorizationModelRegardlessOfUser",
   241  			// state
   242  			model: &openfgav1.AuthorizationModel{
   243  				Id:            ulid.Make().String(),
   244  				SchemaVersion: typesystem.SchemaVersion1_0,
   245  				TypeDefinitions: []*openfgav1.TypeDefinition{
   246  					{
   247  						Type: "repo",
   248  						Relations: map[string]*openfgav1.Userset{
   249  							"admin":  {},
   250  							"writer": {},
   251  						},
   252  					},
   253  				},
   254  			},
   255  			tuples: []*openfgav1.TupleKey{
   256  				{
   257  					Object:   "repo:openfga/openfga",
   258  					Relation: "admin",
   259  					User:     "github|jose",
   260  				},
   261  				{
   262  					Object:   "repo:openfga/openfga",
   263  					Relation: "admin",
   264  					User:     "github|yenkel",
   265  				},
   266  				{
   267  					Object:   "repo:openfga/openfga-users",
   268  					Relation: "writer",
   269  					User:     "github|jose",
   270  				},
   271  				{
   272  					Object:   "org:openfga",
   273  					Relation: "member",
   274  					User:     "github|jose",
   275  				},
   276  			},
   277  			// input
   278  			request: &openfgav1.ReadRequest{
   279  				TupleKey: &openfgav1.ReadRequestTupleKey{
   280  					Object:   "repo:openfga/openfga",
   281  					Relation: "admin",
   282  				},
   283  			},
   284  			// output
   285  			response: &openfgav1.ReadResponse{
   286  				Tuples: []*openfgav1.Tuple{
   287  					{Key: &openfgav1.TupleKey{
   288  						Object:   "repo:openfga/openfga",
   289  						Relation: "admin",
   290  						User:     "github|jose",
   291  					}},
   292  					{Key: &openfgav1.TupleKey{
   293  						Object:   "repo:openfga/openfga",
   294  						Relation: "admin",
   295  						User:     "github|yenkel",
   296  					}},
   297  				},
   298  			},
   299  		},
   300  		//{
   301  		//	_name: "ExecuteReturnsTuplesWithProvidedObjectIdInAuthorizationModelRegardlessOfUserAndRelation",
   302  		//	// state
   303  		//	model: &openfgav1.AuthorizationModel{
   304  		//		Id:            ulid.Make().String(),
   305  		//		SchemaVersion: typesystem.SchemaVersion1_0,
   306  		//		TypeDefinitions: []*openfgav1.TypeDefinition{
   307  		//			{
   308  		//				Type: "repo",
   309  		//				Relations: map[string]*openfgav1.Userset{
   310  		//					"admin":  {},
   311  		//					"writer": {},
   312  		//				},
   313  		//			},
   314  		//		},
   315  		//	},
   316  		//	tuples: []*openfgav1.TupleKey{
   317  		//		{
   318  		//			Object:   "repo:openfga/openfga",
   319  		//			Relation: "admin",
   320  		//			User:     "github|jose",
   321  		//		},
   322  		//		{
   323  		//			Object:   "repo:openfga/openfga",
   324  		//			Relation: "writer",
   325  		//			User:     "github|yenkel",
   326  		//		},
   327  		//		{
   328  		//			Object:   "repo:openfga/openfga-users",
   329  		//			Relation: "writer",
   330  		//			User:     "github|jose",
   331  		//		},
   332  		//		{
   333  		//			Object:   "org:openfga",
   334  		//			Relation: "member",
   335  		//			User:     "github|jose",
   336  		//		},
   337  		//	},
   338  		//	// input
   339  		//	request: &openfgav1.ReadRequest{
   340  		//		TupleKey: &openfgav1.ReadRequestTupleKey{
   341  		//			Object: "repo:openfga/openfga",
   342  		//		},
   343  		//	},
   344  		//	// output
   345  		//	response: &openfgav1.ReadResponse{
   346  		//		Tuples: []*openfgav1.Tuple{
   347  		//			{Key: &openfgav1.TupleKey{
   348  		//				Object:   "repo:openfga/openfga",
   349  		//				Relation: "admin",
   350  		//				User:     "github|jose",
   351  		//			}},
   352  		//			{Key: &openfgav1.TupleKey{
   353  		//				Object:   "repo:openfga/openfga",
   354  		//				Relation: "writer",
   355  		//				User:     "github|yenkel",
   356  		//			}},
   357  		//		},
   358  		//	},
   359  		// },
   360  	}
   361  
   362  	ctx := context.Background()
   363  
   364  	for _, test := range tests {
   365  		t.Run(test._name, func(t *testing.T) {
   366  			store := ulid.Make().String()
   367  			err := datastore.WriteAuthorizationModel(ctx, store, test.model)
   368  			require.NoError(t, err)
   369  
   370  			if test.tuples != nil {
   371  				err = datastore.Write(ctx, store, []*openfgav1.TupleKeyWithoutCondition{}, test.tuples)
   372  				require.NoError(t, err)
   373  			}
   374  
   375  			test.request.StoreId = store
   376  			resp, err := commands.NewReadQuery(datastore).Execute(ctx, test.request)
   377  			require.NoError(t, err)
   378  
   379  			if test.response.GetTuples() != nil {
   380  				require.Equal(t, len(test.response.GetTuples()), len(resp.GetTuples()))
   381  
   382  				for i, responseTuple := range test.response.GetTuples() {
   383  					responseTupleKey := responseTuple.GetKey()
   384  					actualTupleKey := resp.GetTuples()[i].GetKey()
   385  					require.Equal(t, responseTupleKey.GetObject(), actualTupleKey.GetObject())
   386  					require.Equal(t, responseTupleKey.GetRelation(), actualTupleKey.GetRelation())
   387  					require.Equal(t, responseTupleKey.GetUser(), actualTupleKey.GetUser())
   388  				}
   389  			}
   390  		})
   391  	}
   392  }
   393  
   394  func ReadQueryErrorTest(t *testing.T, datastore storage.OpenFGADatastore) {
   395  	// TODO: review which of these tests should be moved to validation/types in grpc rather than execution. e.g.: invalid relation in authorizationmodel is fine, but tuple without authorizationmodel is should be required before. see issue: https://github.com/openfga/sandcastle/issues/13
   396  	tests := []struct {
   397  		_name   string
   398  		model   *openfgav1.AuthorizationModel
   399  		request *openfgav1.ReadRequest
   400  	}{
   401  		{
   402  			_name: "ExecuteErrorsIfOneTupleKeyHasObjectWithoutType",
   403  			model: &openfgav1.AuthorizationModel{
   404  				Id:            ulid.Make().String(),
   405  				SchemaVersion: typesystem.SchemaVersion1_0,
   406  				TypeDefinitions: []*openfgav1.TypeDefinition{
   407  					{
   408  						Type: "repo",
   409  						Relations: map[string]*openfgav1.Userset{
   410  							"admin": {},
   411  						},
   412  					},
   413  				},
   414  			},
   415  			request: &openfgav1.ReadRequest{
   416  				TupleKey: &openfgav1.ReadRequestTupleKey{
   417  					Object: "openfga/iam",
   418  				},
   419  			},
   420  		},
   421  		{
   422  			_name: "ExecuteErrorsIfOneTupleKeyObjectIs':'",
   423  			model: &openfgav1.AuthorizationModel{
   424  				Id:            ulid.Make().String(),
   425  				SchemaVersion: typesystem.SchemaVersion1_0,
   426  				TypeDefinitions: []*openfgav1.TypeDefinition{
   427  					{
   428  						Type: "repo",
   429  						Relations: map[string]*openfgav1.Userset{
   430  							"admin": {},
   431  						},
   432  					},
   433  				},
   434  			},
   435  			request: &openfgav1.ReadRequest{
   436  				TupleKey: &openfgav1.ReadRequestTupleKey{
   437  					Object: ":",
   438  				},
   439  			},
   440  		},
   441  		{
   442  			_name: "ErrorIfRequestHasNoObjectAndThusNoType",
   443  			model: &openfgav1.AuthorizationModel{
   444  				Id:            ulid.Make().String(),
   445  				SchemaVersion: typesystem.SchemaVersion1_0,
   446  				TypeDefinitions: []*openfgav1.TypeDefinition{
   447  					{
   448  						Type: "repo",
   449  						Relations: map[string]*openfgav1.Userset{
   450  							"admin": {},
   451  						},
   452  					},
   453  				},
   454  			},
   455  			request: &openfgav1.ReadRequest{
   456  				TupleKey: &openfgav1.ReadRequestTupleKey{
   457  					Relation: "admin",
   458  					User:     "github|jonallie",
   459  				},
   460  			},
   461  		},
   462  		{
   463  			_name: "ExecuteErrorsIfOneTupleKeyHasNoObjectIdAndNoUserSetButHasAType",
   464  			model: &openfgav1.AuthorizationModel{
   465  				Id:            ulid.Make().String(),
   466  				SchemaVersion: typesystem.SchemaVersion1_0,
   467  				TypeDefinitions: []*openfgav1.TypeDefinition{
   468  					{
   469  						Type: "repo",
   470  						Relations: map[string]*openfgav1.Userset{
   471  							"admin": {},
   472  						},
   473  					},
   474  				},
   475  			},
   476  			request: &openfgav1.ReadRequest{
   477  				TupleKey: &openfgav1.ReadRequestTupleKey{
   478  					Object:   "repo:",
   479  					Relation: "writer",
   480  				},
   481  			},
   482  		},
   483  		{
   484  			_name: "ExecuteErrorsIfOneTupleKeyInTupleSetOnlyHasRelation",
   485  			model: &openfgav1.AuthorizationModel{
   486  				Id:            ulid.Make().String(),
   487  				SchemaVersion: typesystem.SchemaVersion1_0,
   488  				TypeDefinitions: []*openfgav1.TypeDefinition{
   489  					{
   490  						Type: "repo",
   491  						Relations: map[string]*openfgav1.Userset{
   492  							"admin": {},
   493  						},
   494  					},
   495  				},
   496  			},
   497  			request: &openfgav1.ReadRequest{
   498  				TupleKey: &openfgav1.ReadRequestTupleKey{
   499  					Relation: "writer",
   500  				},
   501  			},
   502  		},
   503  		{
   504  			_name: "ExecuteErrorsIfContinuationTokenIsBad",
   505  			model: &openfgav1.AuthorizationModel{
   506  				Id:            ulid.Make().String(),
   507  				SchemaVersion: typesystem.SchemaVersion1_0,
   508  				TypeDefinitions: []*openfgav1.TypeDefinition{
   509  					{
   510  						Type: "repo",
   511  						Relations: map[string]*openfgav1.Userset{
   512  							"admin":  {},
   513  							"writer": {},
   514  						},
   515  					},
   516  				},
   517  			},
   518  			request: &openfgav1.ReadRequest{
   519  				TupleKey: &openfgav1.ReadRequestTupleKey{
   520  					Object: "repo:openfga/openfga",
   521  				},
   522  				ContinuationToken: "foo",
   523  			},
   524  		},
   525  	}
   526  
   527  	ctx := context.Background()
   528  
   529  	for _, test := range tests {
   530  		t.Run(test._name, func(t *testing.T) {
   531  			store := ulid.Make().String()
   532  			err := datastore.WriteAuthorizationModel(ctx, store, test.model)
   533  			require.NoError(t, err)
   534  
   535  			test.request.StoreId = store
   536  			_, err = commands.NewReadQuery(datastore).Execute(ctx, test.request)
   537  			require.Error(t, err)
   538  		})
   539  	}
   540  }
   541  
   542  func ReadAllTuplesTest(t *testing.T, datastore storage.OpenFGADatastore) {
   543  	ctx := context.Background()
   544  	store := ulid.Make().String()
   545  
   546  	writes := []*openfgav1.TupleKey{
   547  		{
   548  			Object:   "repo:openfga/foo",
   549  			Relation: "admin",
   550  			User:     "github|jon.allie",
   551  		},
   552  		{
   553  			Object:   "repo:openfga/bar",
   554  			Relation: "admin",
   555  			User:     "github|jon.allie",
   556  		},
   557  		{
   558  			Object:   "repo:openfga/baz",
   559  			Relation: "admin",
   560  			User:     "github|jon.allie",
   561  		},
   562  	}
   563  	err := datastore.Write(ctx, store, nil, writes)
   564  	require.NoError(t, err)
   565  
   566  	cmd := commands.NewReadQuery(datastore)
   567  
   568  	firstRequest := &openfgav1.ReadRequest{
   569  		StoreId:           store,
   570  		PageSize:          wrapperspb.Int32(1),
   571  		ContinuationToken: "",
   572  	}
   573  	firstResponse, err := cmd.Execute(ctx, firstRequest)
   574  	require.NoError(t, err)
   575  
   576  	require.Len(t, firstResponse.GetTuples(), 1)
   577  	require.NotEmpty(t, firstResponse.GetContinuationToken())
   578  
   579  	var receivedTuples []*openfgav1.TupleKey
   580  	for _, tuple := range firstResponse.GetTuples() {
   581  		receivedTuples = append(receivedTuples, tuple.GetKey())
   582  	}
   583  
   584  	secondRequest := &openfgav1.ReadRequest{StoreId: store, ContinuationToken: firstResponse.GetContinuationToken()}
   585  	secondResponse, err := cmd.Execute(ctx, secondRequest)
   586  	require.NoError(t, err)
   587  
   588  	require.Len(t, secondResponse.GetTuples(), 2)
   589  	require.Empty(t, secondResponse.GetContinuationToken())
   590  
   591  	for _, tuple := range secondResponse.GetTuples() {
   592  		receivedTuples = append(receivedTuples, tuple.GetKey())
   593  	}
   594  
   595  	cmpOpts := []cmp.Option{
   596  		protocmp.IgnoreFields(protoadapt.MessageV2Of(&openfgav1.Tuple{}), "timestamp"),
   597  		protocmp.IgnoreFields(protoadapt.MessageV2Of(&openfgav1.TupleChange{}), "timestamp"),
   598  		protocmp.Transform(),
   599  		testutils.TupleKeyCmpTransformer,
   600  	}
   601  
   602  	if diff := cmp.Diff(writes, receivedTuples, cmpOpts...); diff != "" {
   603  		t.Errorf("Tuple mismatch (-want +got):\n%s", diff)
   604  	}
   605  }
   606  
   607  func ReadAllTuplesInvalidContinuationTokenTest(t *testing.T, datastore storage.OpenFGADatastore) {
   608  	ctx := context.Background()
   609  	store := ulid.Make().String()
   610  
   611  	model := &openfgav1.AuthorizationModel{
   612  		Id:            ulid.Make().String(),
   613  		SchemaVersion: typesystem.SchemaVersion1_0,
   614  		TypeDefinitions: []*openfgav1.TypeDefinition{
   615  			{
   616  				Type: "repo",
   617  			},
   618  		},
   619  	}
   620  
   621  	err := datastore.WriteAuthorizationModel(ctx, store, model)
   622  	require.NoError(t, err)
   623  
   624  	_, err = commands.NewReadQuery(datastore).Execute(ctx, &openfgav1.ReadRequest{
   625  		StoreId:           store,
   626  		ContinuationToken: "foo",
   627  	})
   628  	require.ErrorIs(t, err, serverErrors.InvalidContinuationToken)
   629  }