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

     1  package test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/oklog/ulid/v2"
    12  	openfgav1 "github.com/openfga/api/proto/openfga/v1"
    13  	"github.com/stretchr/testify/require"
    14  	"google.golang.org/protobuf/protoadapt"
    15  	"google.golang.org/protobuf/testing/protocmp"
    16  	"google.golang.org/protobuf/types/known/structpb"
    17  
    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  func ReadChangesTest(t *testing.T, datastore storage.OpenFGADatastore) {
    25  	ctx := context.Background()
    26  
    27  	t.Run("read_changes_with_continuation_token", func(t *testing.T) {
    28  		storeID := ulid.Make().String()
    29  
    30  		tk1 := &openfgav1.TupleKey{
    31  			Object:   tuple.BuildObject("folder", "folder1"),
    32  			Relation: "viewer",
    33  			User:     "bob",
    34  		}
    35  		tk2 := &openfgav1.TupleKey{
    36  			Object:   tuple.BuildObject("folder", "folder2"),
    37  			Relation: "viewer",
    38  			User:     "bill",
    39  		}
    40  
    41  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk1, tk2})
    42  		require.NoError(t, err)
    43  
    44  		changes, continuationToken, err := datastore.ReadChanges(ctx, storeID, "", storage.PaginationOptions{PageSize: 1}, 0)
    45  		require.NoError(t, err)
    46  		require.NotEmpty(t, continuationToken)
    47  
    48  		expectedChanges := []*openfgav1.TupleChange{
    49  			{
    50  				TupleKey:  tk1,
    51  				Operation: openfgav1.TupleOperation_TUPLE_OPERATION_WRITE,
    52  			},
    53  		}
    54  
    55  		if diff := cmp.Diff(expectedChanges, changes, cmpOpts...); diff != "" {
    56  			t.Fatalf("mismatch (-want +got):\n%s", diff)
    57  		}
    58  
    59  		changes, continuationToken, err = datastore.ReadChanges(ctx, storeID, "", storage.PaginationOptions{
    60  			PageSize: 2,
    61  			From:     string(continuationToken),
    62  		},
    63  			0,
    64  		)
    65  		require.NoError(t, err)
    66  		require.NotEmpty(t, continuationToken)
    67  
    68  		expectedChanges = []*openfgav1.TupleChange{
    69  			{
    70  				TupleKey:  tk2,
    71  				Operation: openfgav1.TupleOperation_TUPLE_OPERATION_WRITE,
    72  			},
    73  		}
    74  		if diff := cmp.Diff(expectedChanges, changes, cmpOpts...); diff != "" {
    75  			t.Errorf("mismatch (-want +got):\n%s", diff)
    76  		}
    77  	})
    78  
    79  	t.Run("read_changes_with_no_changes_should_return_not_found", func(t *testing.T) {
    80  		storeID := ulid.Make().String()
    81  
    82  		_, _, err := datastore.ReadChanges(ctx, storeID, "", storage.PaginationOptions{PageSize: storage.DefaultPageSize}, 0)
    83  		require.ErrorIs(t, err, storage.ErrNotFound)
    84  	})
    85  
    86  	t.Run("read_changes_with_horizon_offset_should_return_not_found_(no_changes)", func(t *testing.T) {
    87  		storeID := ulid.Make().String()
    88  
    89  		tk1 := &openfgav1.TupleKey{
    90  			Object:   tuple.BuildObject("folder", "folder1"),
    91  			Relation: "viewer",
    92  			User:     "bob",
    93  		}
    94  		tk2 := &openfgav1.TupleKey{
    95  			Object:   tuple.BuildObject("folder", "folder2"),
    96  			Relation: "viewer",
    97  			User:     "bill",
    98  		}
    99  
   100  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk1, tk2})
   101  		require.NoError(t, err)
   102  
   103  		_, _, err = datastore.ReadChanges(ctx, storeID, "", storage.PaginationOptions{PageSize: storage.DefaultPageSize}, 1*time.Minute)
   104  		require.ErrorIs(t, err, storage.ErrNotFound)
   105  	})
   106  
   107  	t.Run("read_changes_with_non-empty_object_type_should_only_read_that_object_type", func(t *testing.T) {
   108  		storeID := ulid.Make().String()
   109  
   110  		tk1 := &openfgav1.TupleKey{
   111  			Object:   tuple.BuildObject("folder", "1"),
   112  			Relation: "viewer",
   113  			User:     "bob",
   114  		}
   115  		tk2 := &openfgav1.TupleKey{
   116  			Object:   tuple.BuildObject("document", "1"),
   117  			Relation: "viewer",
   118  			User:     "bill",
   119  		}
   120  
   121  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk1, tk2})
   122  		require.NoError(t, err)
   123  
   124  		changes, continuationToken, err := datastore.ReadChanges(ctx, storeID, "folder", storage.PaginationOptions{PageSize: storage.DefaultPageSize}, 0)
   125  		require.NoError(t, err)
   126  		require.NotEmpty(t, continuationToken)
   127  
   128  		expectedChanges := []*openfgav1.TupleChange{
   129  			{
   130  				TupleKey:  tk1,
   131  				Operation: openfgav1.TupleOperation_TUPLE_OPERATION_WRITE,
   132  			},
   133  		}
   134  		if diff := cmp.Diff(expectedChanges, changes, cmpOpts...); diff != "" {
   135  			t.Errorf("mismatch (-want +got):\n%s", diff)
   136  		}
   137  	})
   138  
   139  	t.Run("read_changes_returns_deterministic_ordering_and_no_duplicates", func(t *testing.T) {
   140  		storeID := ulid.Make().String()
   141  
   142  		for i := 0; i < 100; i++ {
   143  			object := fmt.Sprintf("document:%d", i)
   144  			tuple := []*openfgav1.TupleKey{tuple.NewTupleKey(object, "viewer", "user:jon")}
   145  			err := datastore.Write(context.Background(), storeID, nil, tuple)
   146  			require.NoError(t, err)
   147  		}
   148  
   149  		seenObjects := map[string]struct{}{}
   150  
   151  		var changes []*openfgav1.TupleChange
   152  		var continuationToken []byte
   153  		var err error
   154  		for {
   155  			changes, continuationToken, err = datastore.ReadChanges(context.Background(), storeID, "", storage.PaginationOptions{
   156  				PageSize: 10,
   157  				From:     string(continuationToken),
   158  			}, 1*time.Millisecond)
   159  			if err != nil {
   160  				if errors.Is(err, storage.ErrNotFound) {
   161  					break
   162  				}
   163  			}
   164  			require.NoError(t, err)
   165  			require.NotNil(t, continuationToken)
   166  
   167  			for _, change := range changes {
   168  				if _, ok := seenObjects[change.GetTupleKey().GetObject()]; ok {
   169  					require.FailNowf(t, "duplicate changelog entry encountered", change.GetTupleKey().GetObject())
   170  				}
   171  
   172  				seenObjects[change.GetTupleKey().GetObject()] = struct{}{}
   173  			}
   174  
   175  			if string(continuationToken) == "" {
   176  				break
   177  			}
   178  		}
   179  	})
   180  
   181  	t.Run("read_changes_with_conditions", func(t *testing.T) {
   182  		storeID := ulid.Make().String()
   183  
   184  		tk1 := &openfgav1.TupleKey{
   185  			Object:   tuple.BuildObject("folder", "folder1"),
   186  			Relation: "viewer",
   187  			User:     "bob",
   188  			Condition: &openfgav1.RelationshipCondition{
   189  				Name: "condition",
   190  			},
   191  		}
   192  		tk2 := &openfgav1.TupleKey{
   193  			Object:   tuple.BuildObject("folder", "folder2"),
   194  			Relation: "viewer",
   195  			User:     "bill",
   196  			Condition: &openfgav1.RelationshipCondition{
   197  				Name:    "condition",
   198  				Context: testutils.MustNewStruct(t, map[string]interface{}{"param1": "ok"}),
   199  			},
   200  		}
   201  
   202  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk1, tk2})
   203  		require.NoError(t, err)
   204  
   205  		changes, continuationToken, err := datastore.ReadChanges(ctx, storeID, "", storage.PaginationOptions{PageSize: storage.DefaultPageSize}, 0)
   206  		require.NoError(t, err)
   207  		require.NotEmpty(t, continuationToken)
   208  
   209  		expectedChanges := []*openfgav1.TupleChange{
   210  			{
   211  				TupleKey: &openfgav1.TupleKey{
   212  					Object:   tuple.BuildObject("folder", "folder1"),
   213  					Relation: "viewer",
   214  					User:     "bob",
   215  					Condition: &openfgav1.RelationshipCondition{
   216  						Name:    "condition",
   217  						Context: &structpb.Struct{},
   218  					},
   219  				},
   220  				Operation: openfgav1.TupleOperation_TUPLE_OPERATION_WRITE,
   221  			},
   222  			{
   223  				TupleKey:  tk2,
   224  				Operation: openfgav1.TupleOperation_TUPLE_OPERATION_WRITE,
   225  			},
   226  		}
   227  
   228  		if diff := cmp.Diff(expectedChanges, changes, cmpOpts...); diff != "" {
   229  			t.Fatalf("mismatch (-want +got):\n%s", diff)
   230  		}
   231  	})
   232  
   233  	t.Run("tuple_with_condition_deleted", func(t *testing.T) {
   234  		storeID := ulid.Make().String()
   235  
   236  		tk1 := &openfgav1.TupleKey{
   237  			Object:   tuple.BuildObject("document", "1"),
   238  			Relation: "viewer",
   239  			User:     "user:jon",
   240  			Condition: &openfgav1.RelationshipCondition{
   241  				Name: "mycond",
   242  				Context: testutils.MustNewStruct(t, map[string]interface{}{
   243  					"x": 10,
   244  				}),
   245  			},
   246  		}
   247  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk1})
   248  		require.NoError(t, err)
   249  
   250  		tk2 := &openfgav1.TupleKeyWithoutCondition{
   251  			Object:   tuple.BuildObject("document", "1"),
   252  			Relation: "viewer",
   253  			User:     "user:jon",
   254  		}
   255  
   256  		err = datastore.Write(ctx, storeID, []*openfgav1.TupleKeyWithoutCondition{tk2}, nil)
   257  		require.NoError(t, err)
   258  
   259  		changes, continuationToken, err := datastore.ReadChanges(ctx, storeID, "", storage.PaginationOptions{PageSize: storage.DefaultPageSize}, 0)
   260  		require.NoError(t, err)
   261  		require.NotEmpty(t, continuationToken)
   262  
   263  		expectedChanges := []*openfgav1.TupleChange{
   264  			{
   265  				TupleKey:  tk1,
   266  				Operation: openfgav1.TupleOperation_TUPLE_OPERATION_WRITE,
   267  			},
   268  			{
   269  				// Tuples with a condition that are deleted don't include the condition info
   270  				// in the changelog entry.
   271  				TupleKey:  tuple.NewTupleKey("document:1", "viewer", "user:jon"),
   272  				Operation: openfgav1.TupleOperation_TUPLE_OPERATION_DELETE,
   273  			},
   274  		}
   275  
   276  		if diff := cmp.Diff(expectedChanges, changes, cmpOpts...); diff != "" {
   277  			t.Fatalf("mismatch (-want +got):\n%s", diff)
   278  		}
   279  	})
   280  }
   281  
   282  func TupleWritingAndReadingTest(t *testing.T, datastore storage.OpenFGADatastore) {
   283  	ctx := context.Background()
   284  
   285  	t.Run("deletes_would_succeed_and_write_would_fail,_fails_and_introduces_no_changes", func(t *testing.T) {
   286  		storeID := ulid.Make().String()
   287  		tks := []*openfgav1.TupleKey{
   288  			{
   289  				Object:   "doc:readme",
   290  				Relation: "owner",
   291  				User:     "org:openfga#member",
   292  			},
   293  			{
   294  				Object:   "doc:readme",
   295  				Relation: "owner",
   296  				User:     "domain:iam#member",
   297  			},
   298  			{
   299  				Object:   "doc:readme",
   300  				Relation: "viewer",
   301  				User:     "org:openfgapb#viewer",
   302  			},
   303  		}
   304  		expectedError := storage.InvalidWriteInputError(tks[2], openfgav1.TupleOperation_TUPLE_OPERATION_WRITE)
   305  
   306  		// Write tks.
   307  		err := datastore.Write(ctx, storeID, nil, tks)
   308  		require.NoError(t, err)
   309  
   310  		// Try to delete tks[0,1], and at the same time write tks[2]. It should fail with expectedError.
   311  		err = datastore.Write(
   312  			ctx,
   313  			storeID,
   314  			[]*openfgav1.TupleKeyWithoutCondition{
   315  				tuple.TupleKeyToTupleKeyWithoutCondition(tks[0]),
   316  				tuple.TupleKeyToTupleKeyWithoutCondition(tks[1]),
   317  			},
   318  			[]*openfgav1.TupleKey{tks[2]},
   319  		)
   320  		require.EqualError(t, err, expectedError.Error())
   321  
   322  		tuples, _, err := datastore.ReadPage(ctx, storeID, nil, storage.PaginationOptions{PageSize: 50})
   323  		require.NoError(t, err)
   324  		require.Equal(t, len(tks), len(tuples))
   325  	})
   326  
   327  	t.Run("delete_fails_if_the_tuple_does_not_exist", func(t *testing.T) {
   328  		storeID := ulid.Make().String()
   329  		tk := &openfgav1.TupleKey{Object: "doc:readme", Relation: "owner", User: "10"}
   330  
   331  		err := datastore.Write(
   332  			ctx,
   333  			storeID,
   334  			[]*openfgav1.TupleKeyWithoutCondition{
   335  				tuple.TupleKeyToTupleKeyWithoutCondition(tk),
   336  			},
   337  			nil,
   338  		)
   339  		require.ErrorContains(t, err, "cannot delete a tuple which does not exist")
   340  	})
   341  
   342  	t.Run("deleting_a_tuple_which_exists_succeeds", func(t *testing.T) {
   343  		storeID := ulid.Make().String()
   344  		tk := &openfgav1.TupleKey{Object: "doc:readme", Relation: "owner", User: "10"}
   345  
   346  		// Write.
   347  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk})
   348  		require.NoError(t, err)
   349  
   350  		// Then delete.
   351  		err = datastore.Write(
   352  			ctx,
   353  			storeID,
   354  			[]*openfgav1.TupleKeyWithoutCondition{
   355  				tuple.TupleKeyToTupleKeyWithoutCondition(tk),
   356  			},
   357  			nil,
   358  		)
   359  		require.NoError(t, err)
   360  
   361  		// Ensure it is not there.
   362  		_, err = datastore.ReadUserTuple(ctx, storeID, tk)
   363  		require.ErrorIs(t, err, storage.ErrNotFound)
   364  	})
   365  
   366  	t.Run("inserting_a_tuple_twice_fails", func(t *testing.T) {
   367  		storeID := ulid.Make().String()
   368  		tk := &openfgav1.TupleKey{Object: "doc:readme", Relation: "owner", User: "10"}
   369  		expectedError := storage.InvalidWriteInputError(tk, openfgav1.TupleOperation_TUPLE_OPERATION_WRITE)
   370  
   371  		// First write should succeed.
   372  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk})
   373  		require.NoError(t, err)
   374  
   375  		// Second write of the same tuple should fail.
   376  		err = datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk})
   377  		require.EqualError(t, err, expectedError.Error())
   378  	})
   379  
   380  	t.Run("inserting_a_tuple_twice_either_conditioned_or_not_fails", func(t *testing.T) {
   381  		storeID := ulid.Make().String()
   382  		tk := &openfgav1.TupleKey{Object: "doc:readme", Relation: "owner", User: "10"}
   383  		expectedError := storage.InvalidWriteInputError(tk, openfgav1.TupleOperation_TUPLE_OPERATION_WRITE)
   384  
   385  		// First write should succeed.
   386  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk})
   387  		require.NoError(t, err)
   388  
   389  		// Second write of the same tuple but conditioned should still fail.
   390  		err = datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{
   391  			{
   392  				Object:   tk.GetObject(),
   393  				Relation: tk.GetRelation(),
   394  				User:     tk.GetUser(),
   395  				Condition: &openfgav1.RelationshipCondition{
   396  					Name: "condition",
   397  				},
   398  			},
   399  		})
   400  		require.EqualError(t, err, expectedError.Error())
   401  	})
   402  
   403  	t.Run("inserting_conditioned_tuple_and_deleting_tuple_succeeds", func(t *testing.T) {
   404  		storeID := ulid.Make().String()
   405  		tk := &openfgav1.TupleKey{Object: "doc:readme", Relation: "owner", User: "10"}
   406  
   407  		writes := []*openfgav1.TupleKey{
   408  			{
   409  				Object:   tk.GetObject(),
   410  				Relation: tk.GetRelation(),
   411  				User:     tk.GetUser(),
   412  				Condition: &openfgav1.RelationshipCondition{
   413  					Name: "condition",
   414  				},
   415  			},
   416  		}
   417  
   418  		deletes := []*openfgav1.TupleKeyWithoutCondition{
   419  			{
   420  				Object:   tk.GetObject(),
   421  				Relation: tk.GetRelation(),
   422  				User:     tk.GetUser(),
   423  			},
   424  		}
   425  
   426  		err := datastore.Write(ctx, storeID, nil, writes)
   427  		require.NoError(t, err)
   428  
   429  		err = datastore.Write(ctx, storeID, deletes, nil)
   430  		require.NoError(t, err)
   431  	})
   432  
   433  	t.Run("reading_a_tuple_that_exists_succeeds", func(t *testing.T) {
   434  		storeID := ulid.Make().String()
   435  		tuple1 := tuple.NewTupleKey("doc:readme", "owner", "user:jon")
   436  		tuple2 := tuple.NewTupleKey("doc:readme", "viewer", "doc:other#viewer")
   437  		tuple3 := tuple.NewTupleKey("doc:readme", "viewer", "user:*")
   438  		tuple4 := &openfgav1.TupleKey{
   439  			Object:   "doc:readme",
   440  			Relation: "viewer",
   441  			User:     "user:anne",
   442  			Condition: &openfgav1.RelationshipCondition{
   443  				Name:    "condition",
   444  				Context: &structpb.Struct{},
   445  			},
   446  		}
   447  
   448  		err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tuple1, tuple2, tuple3, tuple4})
   449  		require.NoError(t, err)
   450  
   451  		gotTuple, err := datastore.ReadUserTuple(ctx, storeID, tuple1)
   452  		require.NoError(t, err)
   453  
   454  		if diff := cmp.Diff(tuple1, gotTuple.GetKey(), cmpOpts...); diff != "" {
   455  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   456  		}
   457  
   458  		gotTuple, err = datastore.ReadUserTuple(ctx, storeID, tuple2)
   459  		require.NoError(t, err)
   460  
   461  		if diff := cmp.Diff(tuple2, gotTuple.GetKey(), cmpOpts...); diff != "" {
   462  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   463  		}
   464  
   465  		gotTuple, err = datastore.ReadUserTuple(ctx, storeID, tuple3)
   466  		require.NoError(t, err)
   467  
   468  		if diff := cmp.Diff(tuple3, gotTuple.GetKey(), cmpOpts...); diff != "" {
   469  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   470  		}
   471  
   472  		gotTuple, err = datastore.ReadUserTuple(ctx, storeID, tuple4)
   473  		require.NoError(t, err)
   474  
   475  		if diff := cmp.Diff(tuple4, gotTuple.GetKey(), cmpOpts...); diff != "" {
   476  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   477  		}
   478  	})
   479  
   480  	t.Run("reading_a_tuple_that_does_not_exist_returns_not_found", func(t *testing.T) {
   481  		storeID := ulid.Make().String()
   482  		tk := &openfgav1.TupleKey{Object: "doc:readme", Relation: "owner", User: "10"}
   483  
   484  		_, err := datastore.ReadUserTuple(ctx, storeID, tk)
   485  		require.ErrorIs(t, err, storage.ErrNotFound)
   486  	})
   487  
   488  	t.Run("reading_userset_tuples_that_exists_succeeds", func(t *testing.T) {
   489  		storeID := ulid.Make().String()
   490  		tks := []*openfgav1.TupleKey{
   491  			{
   492  				Object:   "doc:readme",
   493  				Relation: "owner",
   494  				User:     "org:openfga#member",
   495  			},
   496  			{
   497  				Object:   "doc:readme",
   498  				Relation: "owner",
   499  				User:     "domain:iam#member",
   500  			},
   501  			{
   502  				Object:   "doc:readme",
   503  				Relation: "owner",
   504  				User:     "user:*",
   505  			},
   506  			{
   507  				Object:   "doc:readme",
   508  				Relation: "viewer",
   509  				User:     "org:openfgapb#viewer",
   510  			},
   511  		}
   512  
   513  		err := datastore.Write(ctx, storeID, nil, tks)
   514  		require.NoError(t, err)
   515  
   516  		gotTuples, err := datastore.ReadUsersetTuples(ctx, storeID, storage.ReadUsersetTuplesFilter{
   517  			Object:   "doc:readme",
   518  			Relation: "owner",
   519  		})
   520  		require.NoError(t, err)
   521  
   522  		iter := storage.NewTupleKeyIteratorFromTupleIterator(gotTuples)
   523  		defer iter.Stop()
   524  
   525  		var gotTupleKeys []*openfgav1.TupleKey
   526  		for {
   527  			tk, err := iter.Next(ctx)
   528  			if err != nil {
   529  				if errors.Is(err, storage.ErrIteratorDone) {
   530  					break
   531  				}
   532  
   533  				require.Fail(t, "unexpected error encountered")
   534  			}
   535  
   536  			gotTupleKeys = append(gotTupleKeys, tk)
   537  		}
   538  
   539  		// Then the iterator should run out.
   540  		_, err = gotTuples.Next(ctx)
   541  		require.ErrorIs(t, err, storage.ErrIteratorDone)
   542  
   543  		require.Len(t, gotTupleKeys, 3)
   544  
   545  		if diff := cmp.Diff(tks[:3], gotTupleKeys, cmpOpts...); diff != "" {
   546  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   547  		}
   548  	})
   549  
   550  	t.Run("reading_userset_tuples_that_don't_exist_should_an_empty_iterator", func(t *testing.T) {
   551  		storeID := ulid.Make().String()
   552  
   553  		gotTuples, err := datastore.ReadUsersetTuples(ctx, storeID, storage.ReadUsersetTuplesFilter{Object: "doc:readme", Relation: "owner"})
   554  		require.NoError(t, err)
   555  		defer gotTuples.Stop()
   556  
   557  		_, err = gotTuples.Next(ctx)
   558  		require.ErrorIs(t, err, storage.ErrIteratorDone)
   559  	})
   560  
   561  	t.Run("reading_userset_tuples_with_filter_made_of_direct_relation_reference", func(t *testing.T) {
   562  		storeID := ulid.Make().String()
   563  		tks := []*openfgav1.TupleKey{
   564  			tuple.NewTupleKey("document:1", "viewer", "user:*"),
   565  			tuple.NewTupleKey("document:1", "viewer", "users:*"),
   566  			tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   567  			tuple.NewTupleKey("document:1", "viewer", "grouping:eng#member"),
   568  		}
   569  
   570  		err := datastore.Write(ctx, storeID, nil, tks)
   571  		require.NoError(t, err)
   572  
   573  		gotTuples, err := datastore.ReadUsersetTuples(ctx, storeID, storage.ReadUsersetTuplesFilter{
   574  			Object:   "document:1",
   575  			Relation: "viewer",
   576  			AllowedUserTypeRestrictions: []*openfgav1.RelationReference{
   577  				typesystem.DirectRelationReference("group", "member"),
   578  			},
   579  		})
   580  		require.NoError(t, err)
   581  
   582  		iter := storage.NewTupleKeyIteratorFromTupleIterator(gotTuples)
   583  		defer iter.Stop()
   584  
   585  		gotTk, err := iter.Next(ctx)
   586  		require.NoError(t, err)
   587  
   588  		expected := tuple.NewTupleKey("document:1", "viewer", "group:eng#member")
   589  		if diff := cmp.Diff(expected, gotTk, cmpOpts...); diff != "" {
   590  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   591  		}
   592  
   593  		_, err = iter.Next(ctx)
   594  		require.ErrorIs(t, err, storage.ErrIteratorDone)
   595  	})
   596  
   597  	t.Run("reading_userset_tuples_with_filter_made_of_direct_relation_references", func(t *testing.T) {
   598  		storeID := ulid.Make().String()
   599  		tks := []*openfgav1.TupleKey{
   600  			tuple.NewTupleKey("document:1", "viewer", "user:*"),
   601  			tuple.NewTupleKey("document:1", "viewer", "users:*"),
   602  			tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   603  			tuple.NewTupleKey("document:1", "viewer", "grouping:eng#member"),
   604  		}
   605  
   606  		err := datastore.Write(ctx, storeID, nil, tks)
   607  		require.NoError(t, err)
   608  
   609  		gotTuples, err := datastore.ReadUsersetTuples(ctx, storeID, storage.ReadUsersetTuplesFilter{
   610  			Object:   "document:1",
   611  			Relation: "viewer",
   612  			AllowedUserTypeRestrictions: []*openfgav1.RelationReference{
   613  				typesystem.DirectRelationReference("group", "member"),
   614  				typesystem.DirectRelationReference("grouping", "member"),
   615  			},
   616  		})
   617  		require.NoError(t, err)
   618  
   619  		iter := storage.NewTupleKeyIteratorFromTupleIterator(gotTuples)
   620  		defer iter.Stop()
   621  
   622  		gotOne, err := iter.Next(ctx)
   623  		require.NoError(t, err)
   624  		gotTwo, err := iter.Next(ctx)
   625  		require.NoError(t, err)
   626  
   627  		expected := []*openfgav1.TupleKey{
   628  			tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   629  			tuple.NewTupleKey("document:1", "viewer", "grouping:eng#member"),
   630  		}
   631  		if diff := cmp.Diff(expected, []*openfgav1.TupleKey{gotOne, gotTwo}, cmpOpts...); diff != "" {
   632  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   633  		}
   634  
   635  		_, err = iter.Next(ctx)
   636  		require.ErrorIs(t, err, storage.ErrIteratorDone)
   637  	})
   638  
   639  	t.Run("reading_userset_tuples_with_filter_made_of_wildcard_relation_reference", func(t *testing.T) {
   640  		storeID := ulid.Make().String()
   641  		tks := []*openfgav1.TupleKey{
   642  			tuple.NewTupleKey("document:1", "viewer", "user:*"),
   643  			tuple.NewTupleKey("document:1", "viewer", "users:*"),
   644  			tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   645  			tuple.NewTupleKey("document:1", "viewer", "grouping:eng#member"),
   646  		}
   647  
   648  		err := datastore.Write(ctx, storeID, nil, tks)
   649  		require.NoError(t, err)
   650  
   651  		gotTuples, err := datastore.ReadUsersetTuples(ctx, storeID, storage.ReadUsersetTuplesFilter{
   652  			Object:   "document:1",
   653  			Relation: "viewer",
   654  			AllowedUserTypeRestrictions: []*openfgav1.RelationReference{
   655  				typesystem.WildcardRelationReference("user"),
   656  			},
   657  		})
   658  		require.NoError(t, err)
   659  
   660  		iter := storage.NewTupleKeyIteratorFromTupleIterator(gotTuples)
   661  		defer iter.Stop()
   662  
   663  		got, err := iter.Next(ctx)
   664  		require.NoError(t, err)
   665  
   666  		expected := tuple.NewTupleKey("document:1", "viewer", "user:*")
   667  		if diff := cmp.Diff(expected, got, cmpOpts...); diff != "" {
   668  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   669  		}
   670  
   671  		_, err = iter.Next(ctx)
   672  		require.ErrorIs(t, err, storage.ErrIteratorDone)
   673  	})
   674  
   675  	t.Run("reading_userset_tuples_with_filter_made_of_mix_references", func(t *testing.T) {
   676  		storeID := ulid.Make().String()
   677  		tks := []*openfgav1.TupleKey{
   678  			tuple.NewTupleKey("document:1", "viewer", "user:*"),
   679  			tuple.NewTupleKey("document:1", "viewer", "users:*"),
   680  			tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   681  			tuple.NewTupleKey("document:1", "viewer", "grouping:eng#member"),
   682  		}
   683  
   684  		err := datastore.Write(ctx, storeID, nil, tks)
   685  		require.NoError(t, err)
   686  
   687  		gotTuples, err := datastore.ReadUsersetTuples(ctx, storeID, storage.ReadUsersetTuplesFilter{
   688  			Object:   "document:1",
   689  			Relation: "viewer",
   690  			AllowedUserTypeRestrictions: []*openfgav1.RelationReference{
   691  				typesystem.DirectRelationReference("group", "member"),
   692  				typesystem.WildcardRelationReference("user"),
   693  			},
   694  		})
   695  		require.NoError(t, err)
   696  
   697  		iter := storage.NewTupleKeyIteratorFromTupleIterator(gotTuples)
   698  		defer iter.Stop()
   699  
   700  		gotOne, err := iter.Next(ctx)
   701  		require.NoError(t, err)
   702  		gotTwo, err := iter.Next(ctx)
   703  		require.NoError(t, err)
   704  
   705  		expected := []*openfgav1.TupleKey{
   706  			tuple.NewTupleKey("document:1", "viewer", "group:eng#member"),
   707  			tuple.NewTupleKey("document:1", "viewer", "user:*"),
   708  		}
   709  		if diff := cmp.Diff(expected, []*openfgav1.TupleKey{gotOne, gotTwo}, cmpOpts...); diff != "" {
   710  			require.FailNowf(t, "mismatch (-want +got):\n%s", diff)
   711  		}
   712  
   713  		_, err = iter.Next(ctx)
   714  		require.ErrorIs(t, err, storage.ErrIteratorDone)
   715  	})
   716  
   717  	t.Run("tuples_with_nil_condition", func(t *testing.T) {
   718  		// This test ensures we don't normalize nil conditions to an empty value.
   719  		storeID := ulid.Make().String()
   720  
   721  		tupleKey1 := tuple.NewTupleKey("document:1", "viewer", "user:jon")
   722  		tupleKey2 := tuple.NewTupleKey("group:1", "member", "group:2#member")
   723  
   724  		tks := []*openfgav1.TupleKey{
   725  			{
   726  				Object:    tupleKey1.GetObject(),
   727  				Relation:  tupleKey1.GetRelation(),
   728  				User:      tupleKey1.GetUser(),
   729  				Condition: nil,
   730  			},
   731  			{
   732  				Object:    tupleKey2.GetObject(),
   733  				Relation:  tupleKey2.GetRelation(),
   734  				User:      tupleKey2.GetUser(),
   735  				Condition: nil,
   736  			},
   737  		}
   738  
   739  		err := datastore.Write(ctx, storeID, nil, tks)
   740  		require.NoError(t, err)
   741  
   742  		iter, err := datastore.Read(ctx, storeID, tupleKey1)
   743  		require.NoError(t, err)
   744  		defer iter.Stop()
   745  
   746  		tp, err := iter.Next(ctx)
   747  		require.NoError(t, err)
   748  		require.Nil(t, tp.GetKey().GetCondition())
   749  
   750  		tuples, _, err := datastore.ReadPage(ctx, storeID, &openfgav1.TupleKey{}, storage.PaginationOptions{
   751  			PageSize: 2,
   752  		})
   753  		require.NoError(t, err)
   754  		require.Len(t, tuples, 2)
   755  		require.Nil(t, tuples[0].GetKey().GetCondition())
   756  		require.Nil(t, tuples[1].GetKey().GetCondition())
   757  
   758  		tp, err = datastore.ReadUserTuple(ctx, storeID, tupleKey1)
   759  		require.NoError(t, err)
   760  		require.Nil(t, tp.GetKey().GetCondition())
   761  
   762  		iter, err = datastore.ReadUsersetTuples(ctx, storeID, storage.ReadUsersetTuplesFilter{
   763  			Object:   tupleKey2.GetObject(),
   764  			Relation: tupleKey2.GetRelation(),
   765  		})
   766  		require.NoError(t, err)
   767  		defer iter.Stop()
   768  
   769  		tp, err = iter.Next(ctx)
   770  		require.NoError(t, err)
   771  		require.Nil(t, tp.GetKey().GetCondition())
   772  
   773  		iter, err = datastore.ReadStartingWithUser(ctx, storeID, storage.ReadStartingWithUserFilter{
   774  			ObjectType: tuple.GetType(tupleKey1.GetObject()),
   775  			Relation:   tupleKey1.GetRelation(),
   776  			UserFilter: []*openfgav1.ObjectRelation{
   777  				{Object: tupleKey1.GetUser()},
   778  			},
   779  		})
   780  		require.NoError(t, err)
   781  		defer iter.Stop()
   782  
   783  		tp, err = iter.Next(ctx)
   784  		require.NoError(t, err)
   785  		require.Nil(t, tp.GetKey().GetCondition())
   786  
   787  		changes, _, err := datastore.ReadChanges(ctx, storeID, "", storage.PaginationOptions{}, 0)
   788  		require.NoError(t, err)
   789  		require.Len(t, changes, 2)
   790  		require.Nil(t, changes[0].GetTupleKey().GetCondition())
   791  		require.Nil(t, changes[1].GetTupleKey().GetCondition())
   792  	})
   793  
   794  	t.Run("normalize_empty_context", func(t *testing.T) {
   795  		// This test ensures we normalize nil or empty context as empty context in all reads.
   796  		storeID := ulid.Make().String()
   797  
   798  		tupleKey1 := tuple.NewTupleKey("document:1", "viewer", "user:jon")
   799  		tupleKey2 := tuple.NewTupleKey("group:1", "member", "group:2#member")
   800  
   801  		tks := []*openfgav1.TupleKey{
   802  			{
   803  				Object:   tupleKey1.GetObject(),
   804  				Relation: tupleKey1.GetRelation(),
   805  				User:     tupleKey1.GetUser(),
   806  				Condition: &openfgav1.RelationshipCondition{
   807  					Name:    "somecondition",
   808  					Context: nil,
   809  				},
   810  			},
   811  			{
   812  				Object:   tupleKey2.GetObject(),
   813  				Relation: tupleKey2.GetRelation(),
   814  				User:     tupleKey2.GetUser(),
   815  				Condition: &openfgav1.RelationshipCondition{
   816  					Name:    "othercondition",
   817  					Context: nil,
   818  				},
   819  			},
   820  		}
   821  
   822  		err := datastore.Write(ctx, storeID, nil, tks)
   823  		require.NoError(t, err)
   824  
   825  		iter, err := datastore.Read(ctx, storeID, tupleKey1)
   826  		require.NoError(t, err)
   827  		defer iter.Stop()
   828  
   829  		tp, err := iter.Next(ctx)
   830  		require.NoError(t, err)
   831  		require.Equal(t, "somecondition", tp.GetKey().GetCondition().GetName())
   832  		require.NotNil(t, tp.GetKey().GetCondition().GetContext())
   833  		require.Empty(t, tp.GetKey().GetCondition().GetContext())
   834  
   835  		tuples, _, err := datastore.ReadPage(ctx, storeID, &openfgav1.TupleKey{}, storage.PaginationOptions{
   836  			PageSize: 2,
   837  		})
   838  		require.NoError(t, err)
   839  		require.Len(t, tuples, 2)
   840  		require.NotNil(t, tuples[0].GetKey().GetCondition().GetContext())
   841  		require.NotNil(t, tuples[1].GetKey().GetCondition().GetContext())
   842  
   843  		tp, err = datastore.ReadUserTuple(ctx, storeID, tupleKey1)
   844  		require.NoError(t, err)
   845  		require.NotNil(t, tp.GetKey().GetCondition().GetContext())
   846  
   847  		iter, err = datastore.ReadUsersetTuples(ctx, storeID, storage.ReadUsersetTuplesFilter{
   848  			Object:   tupleKey2.GetObject(),
   849  			Relation: tupleKey2.GetRelation(),
   850  		})
   851  		require.NoError(t, err)
   852  		defer iter.Stop()
   853  
   854  		tp, err = iter.Next(ctx)
   855  		require.NoError(t, err)
   856  		require.NotNil(t, tp.GetKey().GetCondition().GetContext())
   857  
   858  		iter, err = datastore.ReadStartingWithUser(ctx, storeID, storage.ReadStartingWithUserFilter{
   859  			ObjectType: tuple.GetType(tupleKey1.GetObject()),
   860  			Relation:   tupleKey1.GetRelation(),
   861  			UserFilter: []*openfgav1.ObjectRelation{
   862  				{Object: tupleKey1.GetUser()},
   863  			},
   864  		})
   865  		require.NoError(t, err)
   866  		defer iter.Stop()
   867  
   868  		tp, err = iter.Next(ctx)
   869  		require.NoError(t, err)
   870  		require.NotNil(t, tp.GetKey().GetCondition().GetContext())
   871  
   872  		changes, _, err := datastore.ReadChanges(ctx, storeID, "", storage.PaginationOptions{}, 0)
   873  		require.NoError(t, err)
   874  		require.Len(t, changes, 2)
   875  		require.NotNil(t, changes[0].GetTupleKey().GetCondition().GetContext())
   876  		require.NotNil(t, changes[1].GetTupleKey().GetCondition().GetContext())
   877  	})
   878  }
   879  
   880  func ReadPageTestCorrectnessOfContinuationTokens(t *testing.T, datastore storage.OpenFGADatastore) {
   881  	ctx := context.Background()
   882  	storeID := ulid.Make().String()
   883  	tk0 := &openfgav1.TupleKey{Object: "doc:readme", Relation: "owner", User: "10"}
   884  	tk1 := &openfgav1.TupleKey{Object: "doc:readme", Relation: "viewer", User: "11"}
   885  
   886  	err := datastore.Write(ctx, storeID, nil, []*openfgav1.TupleKey{tk0, tk1})
   887  	require.NoError(t, err)
   888  
   889  	t.Run("readPage_pagination_works_properly", func(t *testing.T) {
   890  		tuples0, contToken0, err := datastore.ReadPage(ctx, storeID, &openfgav1.TupleKey{Object: "doc:readme"}, storage.PaginationOptions{PageSize: 1})
   891  		require.NoError(t, err)
   892  		require.Len(t, tuples0, 1)
   893  		require.NotEmpty(t, contToken0)
   894  
   895  		if diff := cmp.Diff(tk0, tuples0[0].GetKey(), cmpOpts...); diff != "" {
   896  			t.Fatalf("mismatch (-want +got):\n%s", diff)
   897  		}
   898  
   899  		tuples1, contToken1, err := datastore.ReadPage(ctx, storeID, &openfgav1.TupleKey{Object: "doc:readme"}, storage.PaginationOptions{PageSize: 1, From: string(contToken0)})
   900  		require.NoError(t, err)
   901  		require.Len(t, tuples1, 1)
   902  		require.Empty(t, contToken1)
   903  
   904  		if diff := cmp.Diff(tk1, tuples1[0].GetKey(), cmpOpts...); diff != "" {
   905  			t.Fatalf("mismatch (-want +got):\n%s", diff)
   906  		}
   907  	})
   908  
   909  	t.Run("reading_a_page_completely_does_not_return_a_continuation_token", func(t *testing.T) {
   910  		tuples, contToken, err := datastore.ReadPage(ctx, storeID, &openfgav1.TupleKey{Object: "doc:readme"}, storage.PaginationOptions{PageSize: 2})
   911  		require.NoError(t, err)
   912  		require.Len(t, tuples, 2)
   913  		require.Empty(t, contToken)
   914  	})
   915  
   916  	t.Run("reading_a_page_partially_returns_a_continuation_token", func(t *testing.T) {
   917  		tuples, contToken, err := datastore.ReadPage(ctx, storeID, &openfgav1.TupleKey{Object: "doc:readme"}, storage.PaginationOptions{PageSize: 1})
   918  		require.NoError(t, err)
   919  		require.Len(t, tuples, 1)
   920  		require.NotEmpty(t, contToken)
   921  	})
   922  
   923  	t.Run("ReadPaginationWorks", func(t *testing.T) {
   924  		tuple0, contToken0, err := datastore.ReadPage(ctx, storeID, nil, storage.PaginationOptions{PageSize: 1})
   925  		require.NoError(t, err)
   926  		require.Len(t, tuple0, 1)
   927  		require.NotEmpty(t, contToken0)
   928  
   929  		if diff := cmp.Diff(tk0, tuple0[0].GetKey(), cmpOpts...); diff != "" {
   930  			t.Fatalf("mismatch (-want +got):\n%s", diff)
   931  		}
   932  
   933  		tuple1, contToken1, err := datastore.ReadPage(ctx, storeID, nil, storage.PaginationOptions{PageSize: 1, From: string(contToken0)})
   934  		require.NoError(t, err)
   935  		require.Len(t, tuple1, 1)
   936  		require.Empty(t, contToken1)
   937  
   938  		if diff := cmp.Diff(tk1, tuple1[0].GetKey(), cmpOpts...); diff != "" {
   939  			t.Fatalf("mismatch (-want +got):\n%s", diff)
   940  		}
   941  	})
   942  
   943  	t.Run("reading_by_storeID_completely_does_not_return_a_continuation_token", func(t *testing.T) {
   944  		tuples, contToken, err := datastore.ReadPage(ctx, storeID, nil, storage.PaginationOptions{PageSize: 2})
   945  		require.NoError(t, err)
   946  		require.Len(t, tuples, 2)
   947  		require.Empty(t, contToken)
   948  	})
   949  
   950  	t.Run("reading_by_storeID_partially_returns_a_continuation_token", func(t *testing.T) {
   951  		tuples, contToken, err := datastore.ReadPage(ctx, storeID, nil, storage.PaginationOptions{PageSize: 1})
   952  		require.NoError(t, err)
   953  		require.Len(t, tuples, 1)
   954  		require.NotEmpty(t, contToken)
   955  	})
   956  }
   957  
   958  func ReadStartingWithUserTest(t *testing.T, datastore storage.OpenFGADatastore) {
   959  	ctx := context.Background()
   960  
   961  	tuples := []*openfgav1.TupleKey{
   962  		tuple.NewTupleKey("document:doc1", "viewer", "user:jon"),
   963  		tuple.NewTupleKey("document:doc2", "viewer", "group:eng#member"),
   964  		tuple.NewTupleKey("document:doc3", "editor", "user:jon"),
   965  		tuple.NewTupleKey("folder:folder1", "viewer", "user:jon"),
   966  		{
   967  			Object:   "document:doc4",
   968  			Relation: "viewer",
   969  			User:     "user:jon",
   970  			Condition: &openfgav1.RelationshipCondition{
   971  				Name: "condition",
   972  			},
   973  		},
   974  	}
   975  
   976  	t.Run("returns_results_with_two_user_filters", func(t *testing.T) {
   977  		storeID := ulid.Make().String()
   978  
   979  		err := datastore.Write(ctx, storeID, nil, tuples)
   980  		require.NoError(t, err)
   981  
   982  		tupleIterator, err := datastore.ReadStartingWithUser(
   983  			ctx,
   984  			storeID,
   985  			storage.ReadStartingWithUserFilter{
   986  				ObjectType: "document",
   987  				Relation:   "viewer",
   988  				UserFilter: []*openfgav1.ObjectRelation{
   989  					{
   990  						Object: "user:jon",
   991  					},
   992  					{
   993  						Object:   "group:eng",
   994  						Relation: "member",
   995  					},
   996  				},
   997  			},
   998  		)
   999  		require.NoError(t, err)
  1000  
  1001  		objects := getObjects(t, tupleIterator)
  1002  
  1003  		require.ElementsMatch(t, []string{"document:doc1", "document:doc2", "document:doc4"}, objects)
  1004  	})
  1005  
  1006  	t.Run("returns_no_results_if_the_input_users_do_not_match_the_tuples", func(t *testing.T) {
  1007  		storeID := ulid.Make().String()
  1008  
  1009  		err := datastore.Write(ctx, storeID, nil, tuples)
  1010  		require.NoError(t, err)
  1011  
  1012  		tupleIterator, err := datastore.ReadStartingWithUser(
  1013  			ctx,
  1014  			storeID,
  1015  			storage.ReadStartingWithUserFilter{
  1016  				ObjectType: "document",
  1017  				Relation:   "viewer",
  1018  				UserFilter: []*openfgav1.ObjectRelation{
  1019  					{
  1020  						Object: "user:maria",
  1021  					},
  1022  				},
  1023  			},
  1024  		)
  1025  		require.NoError(t, err)
  1026  
  1027  		objects := getObjects(t, tupleIterator)
  1028  
  1029  		require.Empty(t, objects)
  1030  	})
  1031  
  1032  	t.Run("returns_no_results_if_the_input_relation_does_not_match_any_tuples", func(t *testing.T) {
  1033  		storeID := ulid.Make().String()
  1034  
  1035  		err := datastore.Write(ctx, storeID, nil, tuples)
  1036  		require.NoError(t, err)
  1037  
  1038  		tupleIterator, err := datastore.ReadStartingWithUser(
  1039  			ctx,
  1040  			storeID,
  1041  			storage.ReadStartingWithUserFilter{
  1042  				ObjectType: "document",
  1043  				Relation:   "non-existing",
  1044  				UserFilter: []*openfgav1.ObjectRelation{
  1045  					{
  1046  						Object: "user:jon",
  1047  					},
  1048  				},
  1049  			},
  1050  		)
  1051  		require.NoError(t, err)
  1052  
  1053  		objects := getObjects(t, tupleIterator)
  1054  
  1055  		require.Empty(t, objects)
  1056  	})
  1057  
  1058  	t.Run("returns_no_results_if_the_input_object_type_does_not_match_any_tuples", func(t *testing.T) {
  1059  		storeID := ulid.Make().String()
  1060  
  1061  		err := datastore.Write(ctx, storeID, nil, tuples)
  1062  		require.NoError(t, err)
  1063  
  1064  		tupleIterator, err := datastore.ReadStartingWithUser(
  1065  			ctx,
  1066  			storeID,
  1067  			storage.ReadStartingWithUserFilter{
  1068  				ObjectType: "nonexisting",
  1069  				Relation:   "viewer",
  1070  				UserFilter: []*openfgav1.ObjectRelation{
  1071  					{
  1072  						Object: "user:jon",
  1073  					},
  1074  				},
  1075  			},
  1076  		)
  1077  		require.NoError(t, err)
  1078  
  1079  		objects := getObjects(t, tupleIterator)
  1080  
  1081  		require.Empty(t, objects)
  1082  	})
  1083  }
  1084  
  1085  func ReadTestCorrectnessOfTuples(t *testing.T, datastore storage.OpenFGADatastore) {
  1086  	ctx := context.Background()
  1087  
  1088  	tuples := []*openfgav1.TupleKey{
  1089  		tuple.NewTupleKey("document:1", "reader", "user:anne"),
  1090  		tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1091  		tuple.NewTupleKey("document:1", "writer", "user:bob"),
  1092  		{
  1093  			Object:   "document:2",
  1094  			Relation: "viewer",
  1095  			User:     "user:anne",
  1096  			Condition: &openfgav1.RelationshipCondition{
  1097  				Name: "condition",
  1098  			},
  1099  		},
  1100  	}
  1101  
  1102  	storeID := ulid.Make().String()
  1103  
  1104  	err := datastore.Write(ctx, storeID, nil, tuples)
  1105  	require.NoError(t, err)
  1106  
  1107  	t.Run("empty_filter_returns_all_tuples", func(t *testing.T) {
  1108  		tupleIterator, err := datastore.Read(
  1109  			ctx,
  1110  			storeID,
  1111  			tuple.NewTupleKey("", "", ""),
  1112  		)
  1113  		require.NoError(t, err)
  1114  		defer tupleIterator.Stop()
  1115  
  1116  		expectedTupleKeys := []*openfgav1.TupleKey{
  1117  			tuple.NewTupleKey("document:1", "reader", "user:anne"),
  1118  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1119  			tuple.NewTupleKey("document:1", "writer", "user:bob"),
  1120  			{
  1121  				Object:   "document:2",
  1122  				Relation: "viewer",
  1123  				User:     "user:anne",
  1124  				Condition: &openfgav1.RelationshipCondition{
  1125  					Name:    "condition",
  1126  					Context: &structpb.Struct{},
  1127  				},
  1128  			},
  1129  		}
  1130  
  1131  		require.ElementsMatch(t, expectedTupleKeys, getTupleKeys(tupleIterator, t))
  1132  	})
  1133  
  1134  	t.Run("filter_by_user_and_relation_and_objectID", func(t *testing.T) {
  1135  		tupleIterator, err := datastore.Read(
  1136  			ctx,
  1137  			storeID,
  1138  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1139  		)
  1140  		require.NoError(t, err)
  1141  		defer tupleIterator.Stop()
  1142  
  1143  		expectedTupleKeys := []*openfgav1.TupleKey{
  1144  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1145  		}
  1146  
  1147  		require.ElementsMatch(t, expectedTupleKeys, getTupleKeys(tupleIterator, t))
  1148  	})
  1149  
  1150  	t.Run("filter_by_user_and_relation_and_objectType", func(t *testing.T) {
  1151  		tupleIterator, err := datastore.Read(
  1152  			ctx,
  1153  			storeID,
  1154  			tuple.NewTupleKey("document:", "reader", "user:bob"),
  1155  		)
  1156  		require.NoError(t, err)
  1157  		defer tupleIterator.Stop()
  1158  
  1159  		expectedTupleKeys := []*openfgav1.TupleKey{
  1160  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1161  		}
  1162  
  1163  		require.ElementsMatch(t, expectedTupleKeys, getTupleKeys(tupleIterator, t))
  1164  	})
  1165  
  1166  	t.Run("filter_by_user_and_objectType", func(t *testing.T) {
  1167  		tupleIterator, err := datastore.Read(
  1168  			ctx,
  1169  			storeID,
  1170  			tuple.NewTupleKey("document:", "", "user:bob"),
  1171  		)
  1172  		require.NoError(t, err)
  1173  		defer tupleIterator.Stop()
  1174  
  1175  		expectedTupleKeys := []*openfgav1.TupleKey{
  1176  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1177  			tuple.NewTupleKey("document:1", "writer", "user:bob"),
  1178  		}
  1179  
  1180  		require.ElementsMatch(t, expectedTupleKeys, getTupleKeys(tupleIterator, t))
  1181  	})
  1182  
  1183  	t.Run("filter_by_relation_and_objectID", func(t *testing.T) {
  1184  		tupleIterator, err := datastore.Read(
  1185  			ctx,
  1186  			storeID,
  1187  			tuple.NewTupleKey("document:1", "reader", ""),
  1188  		)
  1189  		require.NoError(t, err)
  1190  		defer tupleIterator.Stop()
  1191  
  1192  		expectedTupleKeys := []*openfgav1.TupleKey{
  1193  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1194  			tuple.NewTupleKey("document:1", "reader", "user:anne"),
  1195  		}
  1196  
  1197  		require.ElementsMatch(t, expectedTupleKeys, getTupleKeys(tupleIterator, t))
  1198  	})
  1199  
  1200  	t.Run("filter_by_objectID", func(t *testing.T) {
  1201  		tupleIterator, err := datastore.Read(
  1202  			ctx,
  1203  			storeID,
  1204  			tuple.NewTupleKey("document:1", "", ""),
  1205  		)
  1206  		require.NoError(t, err)
  1207  		defer tupleIterator.Stop()
  1208  
  1209  		expectedTupleKeys := []*openfgav1.TupleKey{
  1210  			tuple.NewTupleKey("document:1", "reader", "user:anne"),
  1211  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1212  			tuple.NewTupleKey("document:1", "writer", "user:bob"),
  1213  		}
  1214  
  1215  		require.ElementsMatch(t, expectedTupleKeys, getTupleKeys(tupleIterator, t))
  1216  	})
  1217  
  1218  	t.Run("filter_by_objectID_and_user", func(t *testing.T) {
  1219  		tupleIterator, err := datastore.Read(
  1220  			ctx,
  1221  			storeID,
  1222  			tuple.NewTupleKey("document:1", "", "user:bob"),
  1223  		)
  1224  		require.NoError(t, err)
  1225  		defer tupleIterator.Stop()
  1226  
  1227  		expectedTupleKeys := []*openfgav1.TupleKey{
  1228  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1229  			tuple.NewTupleKey("document:1", "writer", "user:bob"),
  1230  		}
  1231  
  1232  		require.ElementsMatch(t, expectedTupleKeys, getTupleKeys(tupleIterator, t))
  1233  	})
  1234  }
  1235  
  1236  func ReadPageTestCorrectnessOfContinuationTokensV2(t *testing.T, datastore storage.OpenFGADatastore) {
  1237  	ctx := context.Background()
  1238  	storeID := ulid.Make().String()
  1239  
  1240  	tuplesWritten := []*openfgav1.TupleKey{
  1241  		tuple.NewTupleKey("document:1", "reader", "user:anne"),
  1242  		// read should skip over these
  1243  		tuple.NewTupleKey("document:2", "a", "user:anne"),
  1244  		tuple.NewTupleKey("document:2", "b", "user:anne"),
  1245  		tuple.NewTupleKey("document:2", "c", "user:anne"),
  1246  		tuple.NewTupleKey("document:2", "d", "user:anne"),
  1247  		tuple.NewTupleKey("document:2", "e", "user:anne"),
  1248  		tuple.NewTupleKey("document:2", "f", "user:anne"),
  1249  		tuple.NewTupleKey("document:2", "g", "user:anne"),
  1250  		tuple.NewTupleKey("document:2", "h", "user:anne"),
  1251  		tuple.NewTupleKey("document:2", "j", "user:anne"),
  1252  		// end of skip
  1253  		tuple.NewTupleKey("document:1", "admin", "user:anne"),
  1254  	}
  1255  
  1256  	err := datastore.Write(ctx, storeID, nil, tuplesWritten)
  1257  	require.NoError(t, err)
  1258  
  1259  	t.Run("returns_2_results_and_no_continuation_token_when_page_size_2", func(t *testing.T) {
  1260  		tuplesRead, contToken, err := datastore.ReadPage(
  1261  			ctx,
  1262  			storeID,
  1263  			tuple.NewTupleKey("document:1", "", "user:anne"),
  1264  			storage.PaginationOptions{
  1265  				PageSize: 2,
  1266  			},
  1267  		)
  1268  		require.NoError(t, err)
  1269  
  1270  		expectedTuples := []*openfgav1.Tuple{
  1271  			{Key: tuple.NewTupleKey("document:1", "admin", "user:anne")},
  1272  			{Key: tuple.NewTupleKey("document:1", "reader", "user:anne")},
  1273  		}
  1274  
  1275  		requireEqualTuples(t, expectedTuples, tuplesRead)
  1276  		require.Empty(t, contToken)
  1277  	})
  1278  
  1279  	t.Run("returns_1_results_and_continuation_token_when_page_size_1", func(t *testing.T) {
  1280  		firstRead, contToken, err := datastore.ReadPage(
  1281  			ctx,
  1282  			storeID,
  1283  			tuple.NewTupleKey("document:1", "", "user:anne"),
  1284  			storage.PaginationOptions{
  1285  				PageSize: 1,
  1286  			},
  1287  		)
  1288  		require.NoError(t, err)
  1289  
  1290  		require.Len(t, firstRead, 1)
  1291  		require.Equal(t, "document:1", firstRead[0].GetKey().GetObject())
  1292  		require.Equal(t, "user:anne", firstRead[0].GetKey().GetUser())
  1293  		require.NotEmpty(t, contToken)
  1294  
  1295  		// use the token
  1296  
  1297  		secondRead, contToken, err := datastore.ReadPage(
  1298  			ctx,
  1299  			storeID,
  1300  			tuple.NewTupleKey("document:1", "", "user:anne"),
  1301  			storage.PaginationOptions{
  1302  				PageSize: 50, // fetch the remainder
  1303  				From:     string(contToken),
  1304  			},
  1305  		)
  1306  		require.NoError(t, err)
  1307  
  1308  		require.Len(t, secondRead, 1)
  1309  		require.Equal(t, "document:1", secondRead[0].GetKey().GetObject())
  1310  		require.Equal(t, "user:anne", secondRead[0].GetKey().GetUser())
  1311  		require.NotEqual(t, firstRead[0].GetKey().GetRelation(), secondRead[0].GetKey().GetRelation())
  1312  		require.Empty(t, contToken)
  1313  	})
  1314  }
  1315  
  1316  func ReadPageTestCorrectnessOfTuples(t *testing.T, datastore storage.OpenFGADatastore) {
  1317  	ctx := context.Background()
  1318  
  1319  	tuples := []*openfgav1.TupleKey{
  1320  		tuple.NewTupleKey("document:1", "reader", "user:anne"),
  1321  		tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1322  		tuple.NewTupleKey("document:1", "writer", "user:bob"),
  1323  		{
  1324  			Object:   "document:2",
  1325  			Relation: "viewer",
  1326  			User:     "user:anne",
  1327  			Condition: &openfgav1.RelationshipCondition{
  1328  				Name: "condition",
  1329  			},
  1330  		},
  1331  	}
  1332  
  1333  	storeID := ulid.Make().String()
  1334  
  1335  	err := datastore.Write(ctx, storeID, nil, tuples)
  1336  	require.NoError(t, err)
  1337  
  1338  	t.Run("empty_filter_returns_all_tuples", func(t *testing.T) {
  1339  		gotTuples, contToken, err := datastore.ReadPage(
  1340  			ctx,
  1341  			storeID,
  1342  			tuple.NewTupleKey("", "", ""),
  1343  			storage.PaginationOptions{
  1344  				PageSize: 50,
  1345  			},
  1346  		)
  1347  		require.NoError(t, err)
  1348  
  1349  		expectedTuples := []*openfgav1.Tuple{
  1350  			{Key: tuple.NewTupleKey("document:1", "reader", "user:anne")},
  1351  			{Key: tuple.NewTupleKey("document:1", "reader", "user:bob")},
  1352  			{Key: tuple.NewTupleKey("document:1", "writer", "user:bob")},
  1353  			{Key: tuple.NewTupleKeyWithCondition("document:2", "viewer", "user:anne", "condition", nil)},
  1354  		}
  1355  
  1356  		requireEqualTuples(t, expectedTuples, gotTuples)
  1357  		require.Empty(t, contToken)
  1358  	})
  1359  
  1360  	t.Run("filter_by_user_and_relation_and_objectID", func(t *testing.T) {
  1361  		gotTuples, contToken, err := datastore.ReadPage(
  1362  			ctx,
  1363  			storeID,
  1364  			tuple.NewTupleKey("document:1", "reader", "user:bob"),
  1365  			storage.PaginationOptions{
  1366  				PageSize: 50,
  1367  			},
  1368  		)
  1369  		require.NoError(t, err)
  1370  
  1371  		expectedTuples := []*openfgav1.Tuple{
  1372  			{Key: tuple.NewTupleKey("document:1", "reader", "user:bob")},
  1373  		}
  1374  
  1375  		requireEqualTuples(t, expectedTuples, gotTuples)
  1376  		require.Empty(t, contToken)
  1377  	})
  1378  
  1379  	t.Run("filter_by_user_and_relation_and_objectType", func(t *testing.T) {
  1380  		gotTuples, contToken, err := datastore.ReadPage(
  1381  			ctx,
  1382  			storeID,
  1383  			tuple.NewTupleKey("document:", "reader", "user:bob"),
  1384  			storage.PaginationOptions{
  1385  				PageSize: 50,
  1386  			},
  1387  		)
  1388  		require.NoError(t, err)
  1389  
  1390  		expectedTuples := []*openfgav1.Tuple{
  1391  			{Key: tuple.NewTupleKey("document:1", "reader", "user:bob")},
  1392  		}
  1393  
  1394  		requireEqualTuples(t, expectedTuples, gotTuples)
  1395  		require.Empty(t, contToken)
  1396  	})
  1397  
  1398  	t.Run("filter_by_user_and_objectType", func(t *testing.T) {
  1399  		gotTuples, contToken, err := datastore.ReadPage(
  1400  			ctx,
  1401  			storeID,
  1402  			tuple.NewTupleKey("document:", "", "user:bob"),
  1403  			storage.PaginationOptions{
  1404  				PageSize: 50,
  1405  			},
  1406  		)
  1407  		require.NoError(t, err)
  1408  
  1409  		expectedTuples := []*openfgav1.Tuple{
  1410  			{Key: tuple.NewTupleKey("document:1", "reader", "user:bob")},
  1411  			{Key: tuple.NewTupleKey("document:1", "writer", "user:bob")},
  1412  		}
  1413  
  1414  		requireEqualTuples(t, expectedTuples, gotTuples)
  1415  		require.Empty(t, contToken)
  1416  	})
  1417  
  1418  	t.Run("filter_by_relation_and_objectID", func(t *testing.T) {
  1419  		gotTuples, contToken, err := datastore.ReadPage(
  1420  			ctx,
  1421  			storeID,
  1422  			tuple.NewTupleKey("document:1", "reader", ""),
  1423  			storage.PaginationOptions{
  1424  				PageSize: 50,
  1425  			},
  1426  		)
  1427  		require.NoError(t, err)
  1428  
  1429  		expectedTuples := []*openfgav1.Tuple{
  1430  			{Key: tuple.NewTupleKey("document:1", "reader", "user:bob")},
  1431  			{Key: tuple.NewTupleKey("document:1", "reader", "user:anne")},
  1432  		}
  1433  
  1434  		requireEqualTuples(t, expectedTuples, gotTuples)
  1435  		require.Empty(t, contToken)
  1436  	})
  1437  
  1438  	t.Run("filter_by_objectID", func(t *testing.T) {
  1439  		gotTuples, contToken, err := datastore.ReadPage(
  1440  			ctx,
  1441  			storeID,
  1442  			tuple.NewTupleKey("document:1", "", ""),
  1443  			storage.PaginationOptions{
  1444  				PageSize: 50,
  1445  			},
  1446  		)
  1447  		require.NoError(t, err)
  1448  
  1449  		expectedTuples := []*openfgav1.Tuple{
  1450  			{Key: tuple.NewTupleKey("document:1", "reader", "user:anne")},
  1451  			{Key: tuple.NewTupleKey("document:1", "reader", "user:bob")},
  1452  			{Key: tuple.NewTupleKey("document:1", "writer", "user:bob")},
  1453  		}
  1454  
  1455  		requireEqualTuples(t, expectedTuples, gotTuples)
  1456  		require.Empty(t, contToken)
  1457  	})
  1458  
  1459  	t.Run("filter_by_objectID_and_user", func(t *testing.T) {
  1460  		gotTuples, contToken, err := datastore.ReadPage(
  1461  			ctx,
  1462  			storeID,
  1463  			tuple.NewTupleKey("document:1", "", "user:bob"),
  1464  			storage.PaginationOptions{
  1465  				PageSize: 50,
  1466  			},
  1467  		)
  1468  		require.NoError(t, err)
  1469  
  1470  		expectedTuples := []*openfgav1.Tuple{
  1471  			{Key: tuple.NewTupleKey("document:1", "reader", "user:bob")},
  1472  			{Key: tuple.NewTupleKey("document:1", "writer", "user:bob")},
  1473  		}
  1474  
  1475  		requireEqualTuples(t, expectedTuples, gotTuples)
  1476  		require.Empty(t, contToken)
  1477  	})
  1478  }
  1479  
  1480  // getObjects returns all the objects from an iterator.
  1481  // If the iterator throws an error, it fails the test.
  1482  func getObjects(t *testing.T, tupleIterator storage.TupleIterator) []string {
  1483  	var objects []string
  1484  	for {
  1485  		tp, err := tupleIterator.Next(context.Background())
  1486  		if err != nil {
  1487  			if err == storage.ErrIteratorDone {
  1488  				break
  1489  			}
  1490  
  1491  			t.Errorf(err.Error())
  1492  		}
  1493  
  1494  		objects = append(objects, tp.GetKey().GetObject())
  1495  	}
  1496  	return objects
  1497  }
  1498  
  1499  func getTupleKeys(tupleIterator storage.TupleIterator, t *testing.T) []*openfgav1.TupleKey {
  1500  	t.Helper()
  1501  	var tupleKeys []*openfgav1.TupleKey
  1502  	for {
  1503  		tp, err := tupleIterator.Next(context.Background())
  1504  		if err != nil {
  1505  			if errors.Is(err, storage.ErrIteratorDone) {
  1506  				break
  1507  			}
  1508  
  1509  			require.Fail(t, err.Error())
  1510  		}
  1511  
  1512  		tupleKeys = append(tupleKeys, tp.GetKey())
  1513  	}
  1514  	return tupleKeys
  1515  }
  1516  
  1517  func requireEqualTuples(t *testing.T, expectedTuples []*openfgav1.Tuple, actualTuples []*openfgav1.Tuple) {
  1518  	cmpOpts := []cmp.Option{
  1519  		protocmp.IgnoreFields(protoadapt.MessageV2Of(&openfgav1.Tuple{}), "timestamp"),
  1520  		testutils.TupleCmpTransformer,
  1521  		protocmp.Transform(),
  1522  	}
  1523  	diff := cmp.Diff(expectedTuples, actualTuples, cmpOpts...)
  1524  	require.Empty(t, diff)
  1525  }